Skip to content

Email Service Architecture & Developer Guide ​

Production-ready, modular email service for Oktivo. Provider-agnostic (swap Resend/SES/ZeptoMail with one env var change), queue-backed, fully logged.


Architecture Overview ​

File Structure ​

src/
├── config/
│   └── index.ts                          # EMAIL_PROVIDER, EMAIL_FROM, RESEND_API_KEY
├── models/
│   ├── EmailLog.ts                       # Audit log (90-day TTL)
│   ├── EmailToken.ts                     # Onboarding/verification tokens (30-min expiry)
│   └── User.ts                           # Added: emailVerified, isPasswordSet fields
├── services/
│   └── email/
│       ├── index.ts                      # Re-exports everything
│       ├── email.service.ts              # Core: BullMQ queue, send(), sendImmediate()
│       ├── email.actions.ts              # Business logic: all email trigger functions
│       ├── providers/
│       │   ├── email.provider.interface.ts  # IEmailProvider contract
│       │   ├── resend.provider.ts        # Resend REST API implementation
│       │   ├── ses.provider.ts           # AWS SES implementation
│       │   └── index.ts                  # Re-exports
│       └── templates/
│           ├── base.template.ts          # HTML layout, button, info table helpers
│           └── index.ts                  # All 15+ email templates
├── controllers/
│   └── emailAuth.controller.ts           # Token verify, password setup, forgot password
└── routes/
    ├── auth.routes.ts                    # Added: verify-token, setup-password, etc.
    ├── admin.routes.ts                   # Added: resend-onboarding
    └── superadmin.routes.ts              # Added: resend-onboarding

Database Changes ​

New Collections ​

CollectionPurposeTTL
EmailLogAudit trail for every email sent90 days
EmailTokenSecure tokens for onboarding/verification/password reset24h after expiry

Modified Collections ​

CollectionNew FieldsPurpose
UseremailVerified: BooleanWhether user verified their email
UserisPasswordSet: BooleanWhether user set their own password (vs temp)

Email Flow Diagrams ​

Onboarding Flow (Org Admin or Customer) ​

Direct Signup Flow (Website) ​

Low Balance Alert Flow (Non-Spamming) ​

All Email Triggers ​

SuperAdmin Emails ​

TriggerTemplateRecipientWhen
Org onboardedonboarding_orgOrg admin emailPOST /superadmin/organizations
Org suspendedorg_suspendedOrg admin emailPUT /superadmin/organizations/:id status=suspended
Org unsuspendedorg_unsuspendedOrg admin emailPUT /superadmin/organizations/:id status=active
Resend onboardingresend_onboarding_adminOrg admin emailPOST /superadmin/resend-onboarding/:userUID
SIP trunk createdsip_trunk_createdSuperadmin emailPOST /superadmin/sip-trunks
SIP trunk updatedsip_trunk_updatedSuperadmin emailPUT /superadmin/sip-trunks/:id
SIP trunk deletedsip_trunk_deletedSuperadmin emailDELETE /superadmin/sip-trunks/:id
Phone number deletedphone_number_deletedSuperadmin emailDELETE /superadmin/phone-numbers/:id
Provider capacity addedprovider_capacity_createdSuperadmin emailPOST /superadmin/provider-capacity
Provider capacity updatedprovider_capacity_updatedSuperadmin emailPUT /superadmin/provider-capacity/:id
Provider capacity deletedprovider_capacity_deletedSuperadmin emailDELETE /superadmin/provider-capacity/:id
Org email verifiedemail_verificationOrg admin emailPOST /auth/admin/signup (direct signup)

Organization (Admin) Emails ​

TriggerTemplateRecipientWhen
Customer onboardedonboarding_customerCustomer emailPOST /admin/customers
Customer suspendedcustomer_suspendedCustomer emailPUT /admin/customers/:id status=suspended
Customer unsuspendedcustomer_unsuspendedCustomer emailPUT /admin/customers/:id status=active
Resend onboardingresend_onboarding_customerCustomer emailPOST /admin/resend-onboarding/:userUID
Org pool balance < 500low_balance_organization_500Org admin emailAfter credit consumption

Customer Emails ​

TriggerTemplateRecipientWhen
Agent createdagent_createdCustomer emailPOST /create-agent
Agent deletedagent_deletedCustomer emailDELETE /delete-agent/:id
Campaign createdcampaign_createdCustomer emailPOST /upload-campaign
Campaign startedcampaign_startedCustomer emailPOST /campaigns/:id/start
Campaign stoppedcampaign_stoppedCustomer emailPOST /campaigns/:id/stop
Campaign deletedcampaign_deletedCustomer emailDELETE /campaigns/:id
Campaign auto-stoppedcampaign_auto_stoppedCustomer emailScheduler (9PM IST TRAI window)
Campaign auto-resumedcampaign_auto_startedCustomer emailScheduler (9AM IST TRAI window)
Campaign reportcampaign_reportCustomer emailPOST /export-campaign/:id/email (CSV attachment)
Number assignednumber_assignedCustomer emailPOST /admin/phone-numbers/:id/assign-customer
Credit balance < 500low_balance_customer_500Customer emailAfter credit consumption
Credit balance < 100low_balance_customer_100Customer emailAfter credit consumption (final warning)

Public Auth Emails ​

TriggerTemplateRecipientWhen
Email verificationemail_verificationUser emailPOST /auth/admin/signup
Password resetpassword_resetUser emailPOST /auth/forgot-password

Switching Email Providers ​

Change one env var, restart. Zero code changes.

bash
# Current: Resend
EMAIL_PROVIDER=resend
RESEND_API_KEY=re_xxxxx

# Switch to SES (when you get prod access)
EMAIL_PROVIDER=ses
# Uses existing AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY from .env
# SES_REGION=ap-south-1  (optional, defaults to AWS_REGION)

Adding a New Provider ​

  1. Create src/services/email/providers/zepto.provider.ts
  2. Implement IEmailProvider interface (just name and send())
  3. Add to server.ts initialization:
typescript
const emailProvider = config.email.provider === "zepto"
  ? new ZeptoProvider(config.email.zeptoApiKey, config.email.from)
  : config.email.provider === "ses"
  ? new SESProvider(...)
  : new ResendProvider(...);

Queue & Retry Behavior ​

SettingValue
Queue nameemail-queue
Max retries3
BackoffExponential, starting at 5s
Concurrency5 workers
Rate limit10 emails/sec
Completed job retentionLast 1000
Failed job retentionLast 5000

Critical emails (password reset, email verification) use sendImmediate() which bypasses the queue.

API Endpoints ​

Public (No Auth) ​

MethodPathPurpose
POST/auth/verify-tokenValidate an email token
POST/auth/setup-passwordSet password via onboarding/reset token
POST/auth/verify-emailVerify email via verification token
POST/auth/forgot-passwordRequest password reset email
POST/auth/resend-verificationResend verification email

SuperAdmin ​

MethodPathPurpose
POST/superadmin/resend-onboarding/:userUIDResend onboarding link to org admin

Admin (Org) ​

MethodPathPurpose
POST/admin/resend-onboarding/:userUIDResend onboarding link to customer

Customer ​

MethodPathPurpose
POST/export-campaign/:campaignId/emailEmail campaign report as CSV attachment

Last updated: