Skip to Content
DocumentationBackendThird Party Integrations

Third-Party Integrations Documentation

Overview

This document describes all third-party services and integrations used in the Howden Backend (be-howden). These integrations provide essential functionality including email delivery, file storage, authentication, and database services.

Table of Contents


Email Services

Mandrill (Mailchimp Transactional)

Purpose: Primary transactional email service for sending templated emails including OTP codes, welcome emails, enrollment confirmations, and membership notifications.

Package: @mailchimp/mailchimp_transactional

Location: src/mail/template/mandrillTemplate/

Configuration

MANDRILL_API_KEY=md-YourApiKeyHere

Client Setup:

// src/mail/template/mandrillTemplate/mandrillClient.ts import Mailchimp from '@mailchimp/mailchimp_transactional'; const mailchimpTransactionalApiKey = process.env.MANDRILL_API_KEY; if (!mailchimpTransactionalApiKey) { throw new Error('Missing MANDRILL_API_KEY environment variable'); } export const mailchimpClient = Mailchimp(mailchimpTransactionalApiKey);

Usage Patterns

Sending Templated Emails:

import { MessagesSendTemplateRequest } from '@mailchimp/mailchimp_transactional'; import { mailchimpClient } from './mandrillClient'; export const sendTemplatedEmail = async ( recipientEmail: string, templateName: string, mergeVars: Array<{ name: string; content: string }> ): Promise<void> => { const response = await mailchimpClient.messages.sendTemplate({ template_name: templateName, template_content: [], message: { to: [{ email: recipientEmail }], merge: true, merge_vars: [ { rcpt: recipientEmail, vars: mergeVars, }, ], }, } as MessagesSendTemplateRequest); console.log('Email sent:', response); };

Example: OTP Request Email:

// src/mail/template/mandrillTemplate/otpRequest.ts export const otpLoginRequest = async ( recipientEmail: string, otp_code: string, fullName: string, ): Promise<any> => { const response = await mailchimpClient.messages.sendTemplate({ template_name: 'send-otp-code', template_content: [], message: { to: [{ email: recipientEmail }], merge: true, merge_vars: [ { rcpt: recipientEmail, vars: [ { name: 'USERNAME', content: fullName }, { name: 'OTP_CODE', content: otp_code }, { name: 'ENVIRONMENT', content: isDevelopment.toString() }, ], }, ], }, } as MessagesSendTemplateRequest); return response; };

Available Email Templates

Template FilePurposeMandrill Template Name
otpRequest.tsOTP authenticationsend-otp-code
welcomeHR.tsWelcome new HR usersCustom template
thankyouEmail.tsEnrollment thank youthank-you-email
beneficiaryThankyouEmail.tsBeneficiary thank youCustom template
resetPassword.tsPassword resetreset-password
principalResetPassword.tsPrincipal password resetCustom template
successFulEndorsement.tsEndorsement successCustom template
maxicare*.tsMaxicare-specific notificationsVarious
Icare/*.tsIcare-specific notificationsVarious
Intellicare/*.tsIntellicare notificationsVarious

Total Templates: 45+ email templates across all insurance providers

Best Practices

  1. Always use merge variables for dynamic content
  2. Handle errors gracefully - wrap in try-catch
  3. Log email responses for debugging
  4. Use environment-specific templates when needed
  5. Test templates in Mandrill dashboard before deployment

Nodemailer with SMTP

Purpose: Secondary/fallback email service using SMTP (primarily Gmail for development/testing).

Package: nodemailer

Location: src/mail/mailer.ts

Configuration

SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_AUTH_USER=your-email@gmail.com SMTP_AUTH_PASS=your-app-password SMTP_SECURE=false

Setup:

// src/mail/mailer.ts import nodemailer from 'nodemailer'; export const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST, port: Number(process.env.SMTP_PORT), secure: !!process.env.SMTP_SECURE, auth: { user: process.env.SMTP_AUTH_USER, pass: process.env.SMTP_AUTH_PASS, }, });

Usage

import { transporter } from './mailer'; const sendEmail = async () => { const info = await transporter.sendMail({ from: '"Howden" <noreply@howden.com>', to: 'user@example.com', subject: 'Subject', text: 'Plain text body', html: '<b>HTML body</b>', }); console.log('Message sent:', info.messageId); };

When to Use

  • Development: Quick testing without Mandrill setup
  • Fallback: When Mandrill is unavailable
  • Simple emails: Non-templated one-off emails

Cloud Storage

AWS S3

Purpose: File and image storage for uploads, documents, and assets.

Packages: @aws-sdk/client-s3, @aws-sdk/s3-request-presigner

Location: src/utils/uploadImage.ts, src/utils/uploadFile.ts

Configuration

AWS configuration is managed through the @howden/aws and @howden/common packages:

# AWS credentials configured via IAM roles or env vars AWS_BUCKET_NAME=howden-bucket-name

S3 Client Setup:

// Using @howden/aws package import { S3Client } from '@howden/aws'; import { AWS_BUCKET_NAME } from '@howden/common';

Image Upload with Processing

// src/utils/uploadImage.ts import { PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { S3Client } from '@howden/aws'; import sharp from 'sharp'; import cryptp from 'crypto'; export async function uploadImageToS3( file: Express.Multer.File, userId: string, ) { // Generate unique filename const imageName = `${userId}-${cryptp.randomBytes(32).toString('hex')}`; // Resize image with Sharp const buffer = await sharp(file.buffer) .resize({ height: 1920, width: 1080, fit: 'contain' }) .toBuffer(); // Upload to S3 const params = { Bucket: AWS_BUCKET_NAME, Key: imageName, Body: buffer, ContentType: file.mimetype, }; await S3Client.send(new PutObjectCommand(params)); // Generate signed URL (7 days expiration) const url = await getSignedUrl(S3Client, new GetObjectCommand(params), { expiresIn: 604800, }); return url; }

File Upload (Raw)

// src/utils/uploadFile.ts import { PutObjectCommand } from '@aws-sdk/client-s3'; import { S3Client } from '@howden/aws'; import cryptp from 'crypto'; export async function uploadFileToS3( file: Express.Multer.File, userId: string, ) { const fileName = `${userId}-${cryptp.randomBytes(32).toString('hex')}`; const params = { Bucket: AWS_BUCKET_NAME, Key: fileName, Body: file.buffer, ContentType: file.mimetype, }; await S3Client.send(new PutObjectCommand(params)); return fileName; }

Features

  • Image optimization with Sharp (resize, compression)
  • Signed URLs for secure access
  • Unique filenames with crypto random bytes
  • User-scoped storage (filename prefix)

Authentication

Google OAuth

Purpose: Social authentication allowing users to sign in with Google accounts.

Packages: passport, passport-google-oauth, @types/passport-google-oauth

Location: src/routes/public/googleAuth.router.ts

Configuration

GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=GOCSPX-your-client-secret CALLBACK_URL=http://localhost:8000/api/v1/public/auth/google/callback SESSION_SECRET=your-session-secret

Router Setup

// src/routes/public/googleAuth.router.ts import passport from 'passport'; import { OAuth2Strategy as GoogleStrategy } from 'passport-google-oauth'; passport.use( new GoogleStrategy( { clientID: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, callbackURL: process.env.CALLBACK_URL, }, function (accessToken, refreshToken, profile, done) { return done(null, profile); }, ), ); // Routes const googleAuthRouter = Router(); // Initiate OAuth flow googleAuthRouter.get( '/', passport.authenticate('google', { scope: ['profile', 'email'] }), ); // Callback handler googleAuthRouter.get( '/callback', passport.authenticate('google', { failureRedirect: '/error' }), (req, res) => { res.redirect('/api/v1/public/auth/google/success'); }, ); // Success handler googleAuthRouter.get('/success', async (req, res) => { const data = await socialAuthRepository.registerWithGoogle(userProfile); res .cookie('howdenjwt', data.refreshToken, { httpOnly: true, maxAge: 24 * 60 * 60 * 1000, }) .header('Authorization', data.accessToken) .json({ accessToken: data.accessToken, refreshToken: data.refreshToken, }); });

Service Integration

// src/services/soicalAuth.service.ts export class SocialAuthService { public async registerWithGoogle(oauthUser: SocialUserProfile) { // Check if user exists const isUserExists = await socialRepository.findOne({ id: oauthUser.id, provider: oauthUser.provider, }); if (isUserExists) { return this.processSelectedUser(oauthUser); } // Register new user with transaction const queryRunner = AppDataSource.createQueryRunner(); await queryRunner.startTransaction(); try { // Create user entity const user = new User(); user.email = oauthUser.id; user.firstName = oauthUser.name.familyName; user.lastName = oauthUser.name?.givenName || '-'; user.profilePicture = oauthUser.photos[0].value || '-'; await queryRunner.manager.save(User, user); // Create social login record const socialLogin = new Social(); socialLogin.accountId = oauthUser.id; socialLogin.provider = oauthUser.provider; socialLogin.email = oauthUser.emails[0].value; socialLogin.user = user; await queryRunner.manager.save(Social, socialLogin); await queryRunner.commitTransaction(); } catch (err) { await queryRunner.rollbackTransaction(); return { success: false, error: 'Transaction failed.' }; } return this.processSelectedUser(oauthUser); } private async processSelectedUser(oauthUser: SocialUserProfile) { // Generate JWT tokens const accessToken = jwt.sign( { id: user.userUuid, email: user.email }, process.env.JWT_SECRET_TOKEN, { expiresIn: '1h' }, ); const refreshToken = jwt.sign( { id: user.userUuid, email: user.email }, process.env.JWT_REFRESH_SECRET_TOKEN, { expiresIn: '1d' }, ); return { accessToken, refreshToken, statusCode: 201, success: true }; } }

Setup Instructions

  1. Create Google Cloud Project:

  2. Enable Google+ API:

    • Navigate to “APIs & Services” > “Library”
    • Search and enable “Google+ API”
  3. Create OAuth Credentials:

    • Go to “Credentials” > “Create Credentials” > “OAuth client ID”
    • Configure consent screen
    • Select “Web application”
    • Add authorized redirect URI: http://localhost:8000/api/v1/public/auth/google/callback
  4. Configure Environment:

    GOOGLE_CLIENT_ID=your-client-id GOOGLE_CLIENT_SECRET=your-client-secret CALLBACK_URL=http://localhost:8000/api/v1/public/auth/google/callback

Database

PostgreSQL with TypeORM

Purpose: Primary relational database for all application data.

Packages: typeorm, pg, reflect-metadata

Location: src/data-source.ts

Configuration

# Development/Staging POSTGRES_DB=howdenaces POSTGRES_PORT=5432 POSTGRES_HOST=dpg-xxx.oregon-postgres.render.com POSTGRES_USERNAME=howdenaces POSTGRES_PASSWORD=your-password POSTGRES_SYNC=false POSTGRES_SSL=true # Or local development via Docker POSTGRES_HOST=localhost POSTGRES_PORT=5432 POSTGRES_USERNAME=thirdsugian POSTGRES_PASSWORD= POSTGRES_SYNC=true POSTGRES_SSL=false

Data Source Setup

// src/data-source.ts import { DataSource } from 'typeorm'; export const AppDataSource = new DataSource({ type: 'postgres', host: process.env.POSTGRES_HOST, port: Number(process.env.POSTGRES_PORT), username: process.env.POSTGRES_USERNAME, password: process.env.POSTGRES_PASSWORD, database: process.env.POSTGRES_DB, synchronize: !!process.env.POSTGRES_SYNC, // Auto-migrate (dev only!) logging: !!process.env.POSTGRES_LOGGING, ssl: !!process.env.POSTGRES_SSL, entities: ['build/entities/*.js', 'build/entities/**/*.js'], migrations: ['build/migrations/*.js'], subscribers: ['build/subscriber/**/*.js'], });

Initialization

// In your main server file (index.ts/app.ts) import { AppDataSource } from './data-source'; AppDataSource.initialize() .then(() => { console.log('Database connected successfully'); }) .catch((error) => { console.error('Database connection failed:', error); });

Usage in Services

import { AppDataSource } from '../data-source'; import { User } from '../entities'; // Repository pattern const userRepository = AppDataSource.getRepository(User); // Query with relations const user = await userRepository.findOne({ where: { id: userId }, relations: ['company', 'insuranceProvider'], }); // Raw query when needed const results = await AppDataSource.query(` SELECT * FROM users WHERE created_at > $1 `, [date]); // Transactions const queryRunner = AppDataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { await queryRunner.manager.save(User, newUser); await queryRunner.manager.save(Profile, newProfile); await queryRunner.commitTransaction(); } catch (err) { await queryRunner.rollbackTransaction(); } finally { await queryRunner.release(); }

Entity Structure

Entities are located in src/entities/:

// Example entity import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn('uuid') userUuid: string; @Column({ unique: true }) email: string; @Column() firstName: string; @Column() lastName: string; @ManyToOne(() => Company, company => company.users) company: Company; }

Development Infrastructure

Docker

Purpose: Local development environment with PostgreSQL and pgAdmin.

File: docker-compose.yml

Services

ServiceImagePortsPurpose
postgrespostgres:14.55550:5432PostgreSQL database
pg-admindpage/pgadmin4:6.108082:80Database management UI

Configuration

version: '3.8' services: postgres: image: 'postgres:14.5' ports: - '5550:5432' environment: POSTGRES_USER: 'howden' POSTGRES_PASSWORD: '' POSTGRES_DB: 'behowden' pg-admin: image: dpage/pgadmin4:6.10 volumes: - pgadmin_volume:/var/lib/pgadmin ports: - 8082:80 environment: PGADMIN_DEFAULT_EMAIL: admin@admin.com PGADMIN_DEFAULT_PASSWORD: volumes: pgadmin_volume:

Usage

# Start services docker-compose up -d # Stop services docker-compose down # View logs docker-compose logs -f postgres

Access

  • PostgreSQL: localhost:5550

    • Database: behowden
    • Username: howden
    • Password: howden@2023!
  • pgAdmin: http://localhost:8082 

    • Email: admin@admin.com
    • Password: admin

Environment Variables Summary

Required Variables

VariableServiceRequiredDescription
MANDRILL_API_KEYMandrillYes (if using Mandrill)Mailchimp Transactional API key
SMTP_HOSTNodemailerNoSMTP server hostname
SMTP_PORTNodemailerNoSMTP server port
SMTP_AUTH_USERNodemailerNoSMTP username
SMTP_AUTH_PASSNodemailerNoSMTP password
GOOGLE_CLIENT_IDGoogle OAuthNoGoogle OAuth client ID
GOOGLE_CLIENT_SECRETGoogle OAuthNoGoogle OAuth client secret
CALLBACK_URLGoogle OAuthNoOAuth callback URL
POSTGRES_HOSTPostgreSQLYesDatabase host
POSTGRES_PORTPostgreSQLYesDatabase port
POSTGRES_USERNAMEPostgreSQLYesDatabase username
POSTGRES_PASSWORDPostgreSQLYesDatabase password
POSTGRES_DBPostgreSQLYesDatabase name
JWT_SECRET_TOKENAuthenticationYesJWT signing secret
JWT_REFRESH_SECRET_TOKENAuthenticationYesJWT refresh token secret
AWS_BUCKET_NAMEAWS S3NoS3 bucket name

Development .env Example

# Environment NODE_ENV=development # Email - Mandrill (Primary) MANDRILL_API_KEY=md-YourApiKeyHere # Email - SMTP (Fallback/Development) SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_AUTH_USER=your-email@gmail.com SMTP_AUTH_PASS=your-app-password SMTP_SECURE=false # Authentication JWT_SECRET_TOKEN=your-jwt-secret JWT_REFRESH_SECRET_TOKEN=your-refresh-secret SESSION_SECRET=your-session-secret # Google OAuth GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=GOCSPX-your-secret CALLBACK_URL=http://localhost:8000/api/v1/public/auth/google/callback # Database - Local Docker POSTGRES_HOST=localhost POSTGRES_PORT=5550 POSTGRES_USERNAME=howden POSTGRES_PASSWORD= POSTGRES_DB=behowden POSTGRES_SYNC=true POSTGRES_SSL=false # AWS S3 AWS_BUCKET_NAME=howden-dev-bucket

Troubleshooting

Mandrill Email Not Sending

  1. Check API key validity:

    console.log('Mandrill key:', process.env.MANDRILL_API_KEY?.slice(0, 8));
  2. Verify template exists in Mandrill dashboard

  3. Check merge variable names match template exactly

  4. Review Mandrill logs for delivery status

Google OAuth Errors

  1. Verify redirect URI matches exactly in Google Console
  2. Check client ID/secret are correct
  3. Enable Google+ API in Google Cloud Console
  4. Test in incognito to avoid cached credentials

Database Connection Issues

  1. Check if container is running:

    docker-compose ps
  2. Verify connection string:

    psql postgresql://howden:howden@2023!@localhost:5550/behowden
  3. Check logs:

    docker-compose logs postgres

Last updated on