Uh oh!
There was an error while loading. Please reload this page.
feat(mailer): add AWS SES and SMTP providers with auto-detect fallback - #4710
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Adds new SES ( Extends env schema with the new variables (plus an Reviewed by Cursor Bugbot for commit 7705d31. Configure here. |
Greptile SummaryThis PR refactors the email subsystem into a provider-abstraction layer, adding AWS SES (via nodemailer +
Confidence Score: 5/5Safe 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
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
Reviews (4): Last reviewed commit: "fix(mailer): batch degrades isUnsubscrib..." | Re-trigger Greptile |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- 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
commented
May 22, 2026
waleedlatif1
commented
May 22, 2026
@cursor review |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- 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
commented
May 22, 2026
waleedlatif1
commented
May 22, 2026
@cursor review |
Uh oh!
There was an error while loading. Please reload this page.
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
commented
May 22, 2026
waleedlatif1
commented
May 22, 2026
@cursor review |
There was a problem hiding this comment.
✅ 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.
Uh oh!
There was an error while loading. Please reload this page.
Summary
MailProviderinterface, one file per provider, factory-based auto-detection)@aws-sdk/client-sesv2transport; credentials resolved through the standard AWS provider chainenvBooleanhelper next toenvNumberto fix thez.coerce.boolean("false") === truefootgun underskipValidation: true.env.example, helm values + schema, and self-hosting docsType of Change
Testing
tsc --noEmit,biome check,bun run check:api-validation,helm lintall cleanhelm templaterenders the 6 new env vars (AWS_SES_REGION,SMTP_HOST/PORT/USER/PASS/SECURE) into the app SecretenvBooleanverified against"false","FALSE","0","no","",undefinedChecklist