Skip to content

feat(mailer): add AWS SES and SMTP providers with auto-detect fallback#4710

Merged
waleedlatif1 merged 6 commits into
stagingfrom
waleedlatif1/add-smtp-ses-email
May 22, 2026
Merged

feat(mailer): add AWS SES and SMTP providers with auto-detect fallback#4710
waleedlatif1 merged 6 commits into
stagingfrom
waleedlatif1/add-smtp-ses-email

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

Summary

  • Refactor mailer into a provider abstraction (MailProvider interface, one file per provider, factory-based auto-detection)
  • Add AWS SES via nodemailer + @aws-sdk/client-sesv2 transport; credentials resolved through the standard AWS provider chain
  • Add SMTP via nodemailer; auth is optional so MailHog and internal relays work
  • Auto-detect priority: Resend → SES → SMTP → Azure. First configured wins, rest are automatic fallbacks
  • Add envBoolean helper next to envNumber to fix the z.coerce.boolean("false") === true footgun under skipValidation: true
  • Document all four providers in .env.example, helm values + schema, and self-hosting docs

Type of Change

  • New feature

Testing

  • All 67 existing email tests pass
  • tsc --noEmit, biome check, bun run check:api-validation, helm lint all clean
  • helm template renders the 6 new env vars (AWS_SES_REGION, SMTP_HOST/PORT/USER/PASS/SECURE) into the app Secret
  • envBoolean verified against "false", "FALSE", "0", "no", "", undefined
  • Manual MailHog and live SES smoke recommended before enabling in prod

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 22, 2026 2:15am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 22, 2026

PR Summary

Medium Risk
Moderate risk because it refactors the core email-sending path and introduces new provider selection/fallback behavior that could affect delivery, batching, and unsubscribe handling if misconfigured.

Overview
Refactors mailer.ts to use a MailProvider abstraction with startup-time provider auto-detection and sequential fallback dispatch (Resend → SES → SMTP → Azure), including batch-send support when available.

Adds new SES (AWS_SES_REGION) and SMTP (SMTP_HOST/PORT/USER/PASS/SECURE) providers (via nodemailer), factors email validation/unsubscribe/header injection into prepare.ts, and updates batch behavior to treat per-email preparation/unsubscribe errors as per-entry results.

Extends env schema with the new variables (plus an envBoolean helper to correctly parse string booleans), and updates .env.example, Helm values/schema, and self-hosting docs to document the expanded email provider configuration.

Reviewed by Cursor Bugbot for commit 7705d31. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR refactors the email subsystem into a provider-abstraction layer, adding AWS SES (via nodemailer + @aws-sdk/client-sesv2) and SMTP as new providers alongside the existing Resend and Azure backends. An envBoolean helper is introduced to fix z.coerce.boolean("false") === true under skipValidation: true, and all four providers are documented in .env.example, Helm values, and self-hosting docs.

  • Provider abstraction: a MailProvider interface, one file per provider, and a factory-based activeProviders array with auto-detection in priority order (Resend → SES → SMTP → Azure). Each provider implements send and optionally sendBatch.
  • Batch send refactor: sendBatchEmails now prepares all emails via prepareBatch (deduplicating unsubscribe checks), routes to a sendBatch-capable provider when available, and falls back to per-message sends on failure.
  • New SMTP provider: authenticates when both SMTP_USER/SMTP_PASS are set, logs a warning for partial-credential misconfigurations, and auto-enables TLS on port 465.

Confidence Score: 5/5

Safe to merge — the new SES and SMTP providers are well-guarded at startup, the provider fallback chain works correctly, and the existing test suite passes without modification.

The refactor cleanly separates concerns with no breaking changes. The one finding is a timing-race edge case in the per-message batch path where data.count can over-report by the number of users who unsubscribe in the very small window between batch preparation and dispatch — it requires both a race condition and a non-batch provider to trigger, so it has negligible real-world impact.

apps/sim/lib/messaging/email/mailer.ts — specifically the per-message fallback in sendBatchEmails.

Important Files Changed

Filename Overview
apps/sim/lib/messaging/email/mailer.ts Core mailer refactored to use provider abstraction; batch fallback path calls sendEmail (re-running unsubscribe check) instead of dispatchWithFallback, which can over-count data.count.
apps/sim/lib/messaging/email/providers/index.ts Provider registry — wraps each factory in safeCreate to prevent startup errors from propagating; clean and correct.
apps/sim/lib/messaging/email/providers/ses.ts Thin SES provider using nodemailer SES transport; credentials resolved lazily via AWS SDK default chain. Straightforward and correct.
apps/sim/lib/messaging/email/providers/smtp.ts SMTP provider with port validation, partial-credential warnings, and TLS auto-detect on port 465. Improvements from prior review threads are reflected.
apps/sim/lib/messaging/email/providers/resend.ts Resend provider extracted into its own file; implements both send and sendBatch. Note: batch results carry synthetic IDs rather than real Resend message IDs — pre-existing behavior.
apps/sim/lib/messaging/email/prepare.ts Extracted email preparation and unsubscribe-skip logic from mailer.ts; processEmailData is now synchronous and shouldSkipForUnsubscribe is async — clean separation of concerns.
apps/sim/lib/core/config/env.ts Added 6 new env vars for SES and SMTP; envBoolean helper added alongside envNumber. SMTP_SECURE uses z.boolean() consistent with other booleans in the file given skipValidation: true.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[sendEmail / sendBatchEmails] --> B{shouldSkipForUnsubscribe?}
    B -- yes --> C[Return SKIPPED_UNSUBSCRIBED_RESULT]
    B -- no --> D[processEmailData]
    D --> E{activeProviders.length === 0?}
    E -- yes --> F[Return MOCK_EMAIL_RESULT - console log only]
    E -- no --> G[dispatchWithFallback]
    G --> H{Try Resend}
    H -- success --> I[Return result]
    H -- fail --> J{Try SES}
    J -- success --> I
    J -- fail --> K{Try SMTP}
    K -- success --> I
    K -- fail --> L{Try Azure}
    L -- success --> I
    L -- fail --> M[Return all-providers-failed error]

    subgraph sendBatchEmails
        N[prepareBatch - check unsubscribe per email] --> O{sendable.length === 0?}
        O -- yes --> P[Return skipped/failed result]
        O -- no --> Q{Provider with sendBatch?}
        Q -- yes Resend --> R[batchProvider.sendBatch]
        R -- success --> S[mergeBatchResults]
        R -- fail --> T[Per-message fallback via sendEmail]
        Q -- no --> T
        T --> S
    end
Loading

Reviews (4): Last reviewed commit: "fix(mailer): batch degrades isUnsubscrib..." | Re-trigger Greptile

Comment thread apps/sim/lib/messaging/email/mailer.ts
Comment thread apps/sim/lib/messaging/email/providers/smtp.ts
Comment thread apps/sim/lib/messaging/email/providers/smtp.ts
Comment thread apps/sim/lib/core/config/env.ts Outdated
Comment thread apps/sim/lib/messaging/email/mailer.ts Outdated
- Force a single @aws-sdk/client-sesv2 install via root package.json overrides; @types/nodemailer pulled in a nested copy whose nominal class brand made the two SDK type identities incompatible, breaking the CI build. With one install the cast disappears.
- Batch result message now reports successCount instead of sendable.length when entries are skipped, so "5 emails sent" no longer overstates delivery on partial failures.
- SMTP provider now warns when SMTP_HOST is set without SMTP_PORT, and when only one of SMTP_USER/SMTP_PASS is set — both previously silent misconfigurations.
- SMTP_SECURE schema is z.boolean() to match every other boolean in env.ts; runtime parsing is still handled by envBoolean.
- Strip the verbose TSDoc comments I had added.
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/messaging/email/mailer.ts Outdated
Comment thread apps/sim/lib/messaging/email/mailer.ts Outdated
- mergeBatchResults: data.count and the message now report only emails that were actually delivered, not skipped-unsubscribed ones (they returned success: true and inflated the count). Empty-sendable branch distinguishes "all unsubscribed" from "mixed skip/failure" so the message stops lying when some entries fail validation.
- ses.ts: revert the package.json override approach (bun honors it locally but CI still installs a nested @types/nodemailer copy). Reinstate the `as unknown as` cast with a single-line WHY comment.
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/messaging/email/mailer.ts
A transient DB error in isUnsubscribed used to abort the whole batch
because the call sat outside the per-email try/catch in prepareBatch.
Wrap the unsubscribe check inside the same catch so a rejection becomes
a per-recipient failure, matching sendEmail's behavior. Lock it in
with a regression test.
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 7705d31. Configure here.

@waleedlatif1 waleedlatif1 merged commit 952eb12 into staging May 22, 2026
14 checks passed
@waleedlatif1 waleedlatif1 deleted the waleedlatif1/add-smtp-ses-email branch May 22, 2026 02:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant