Transactional Email Architecture: Templates, Retries, Bounces
Transactional email architecture is the system that sends, retries, tracks, and manages the lifecycle of emails triggered by user actions: signup confirmations, password resets, invoices, alerts, and notifications. I've seen teams lose customers to silent delivery failures and deliverability blacklists from simple omissions in bounce handling. The correct design separates template management, delivery, retry logic, and suppression into distinct concerns.
Written by Yashveer Singh, founder of Yashveer Labs.
What you actually need to know
- Transactional and marketing email must share no infrastructure. Different IP pools, different subdomains.
- Store templates in version control, not in the provider's UI editor.
- Process bounce webhooks and maintain your own suppression list. Do not rely solely on the provider's list.
- Route all email through a background queue. A failed API call should not fail the user's request.
- In my experience, the SaaS teams that treat email as a library call discover their deliverability problems from angry customer support tickets, not from dashboards.
| Provider | Best fit | Pricing model | Bounce handling | Template support |
|---|---|---|---|---|
| Resend | Developer-first SaaS | Per email | Webhook based | React Email native |
| Postmark | Reliability-focused teams | Per email | Message streams | Templating API |
| AWS SES | High volume, AWS shops | Per email, very low | SNS bounce notifications | External |
| SendGrid | Full lifecycle marketing + transactional | Per email tier | Event webhooks | Built-in editor |
| Mailgun | Flexible routing needs | Per email | Webhook based | Template API |
The core argument
Every SaaS sends email. Welcome messages, password resets, invoices, notifications, digest emails, alerts. Most of the time, the email works fine. The edge cases are where the architecture matters. The provider API is temporarily unavailable. The user's address has bounced before. The user's inbox is full. The email takes thirty seconds to deliver and the user clicks "resend" twice. The invoice email fires twice because the webhook retried. The password reset email goes to the spam folder because the DKIM record is wrong.
None of these are exotic scenarios. They happen weekly at any meaningful scale. The teams that have wired up their email as a proper system handle them quietly. The teams that called the provider SDK directly in the request handler deal with them as user complaints.
The correct architecture has four distinct layers. The trigger layer decides that an email should be sent and enqueues a job. The job layer calls the provider SDK, handles API errors, and retries. The template layer renders the email from version-controlled source. The lifecycle layer processes bounces, complaints, and unsubscribes and updates the suppression list.
None of these layers are expensive to build. The templates take the most time if you care about visual quality. The rest is a few hundred lines of infrastructure that runs quietly forever.
The template problem
Templates stored in a provider's UI editor are not in your codebase. They cannot be reviewed. They cannot be tested in a CI pipeline. They disappear if you change providers. They drift from the product's visual identity because they are maintained separately.
The right approach is React Email or MJML. Both produce HTML that renders correctly across email clients including Outlook, which is notoriously difficult. Both integrate with a build step. Both allow component composition so the header, footer, and button components are shared across all templates.
The template renders at send time with data injected from the job context. The rendered HTML goes to the provider's send API. The template source lives in the repository alongside the rest of the product code.
A secondary benefit: the email template is now in code review. Product managers can review the copy. Designers can review the visual structure. A typo in a password reset email gets caught before it ships.
How much does it cost
| Volume | Provider cost | Engineering setup | Ongoing maintenance |
|---|---|---|---|
| Under 10k emails per month | Under 20 USD | Two to five days | Near zero |
| 10k to 100k emails per month | 20 to 200 USD | Same | Near zero |
| 100k to 1M emails per month | 200 to 2000 USD | Add dedicated IP | Quarterly review |
| Over 1M emails per month | Negotiated | Add warm-up plan | Monthly review |
| AWS SES at any volume | Roughly 0.10 per thousand | Higher integration cost | Near zero |
The engineering setup cost covers template infrastructure, background queue integration, bounce webhook processing, and the suppression list. Done once, it runs without maintenance. The ongoing cost is provider fees and occasional template updates.
Features to demand from the implementation
- All sends routed through a background job queue, never inline in the request handler.
- Exponential backoff retry on provider API failures, with a maximum attempt count.
- Bounce and complaint webhook processing with immediate suppression list updates.
- Hard bounce addresses never retried, ever.
- Templates in version-controlled source code rendered at send time.
- Separate subdomains and IP pools for transactional and marketing email.
- SPF, DKIM, and DMARC configured and verified before the first production send.
- A development mail trap that prevents external delivery outside production.
- Idempotency keys on sends to prevent duplicates from queue retries.
Expert opinion
Transactional email has the highest user expectation of any notification channel. The user requested the email. They are waiting for it. When it does not arrive, or arrives twice, or goes to spam, the trust damage is immediate and personal. The engineering investment to get it right is modest. A background queue, bounce processing, version-controlled templates, and correct DNS records is a week of work. The teams that skip that week spend months on support tickets and deliverability investigations.
>
Yashveer Singh, founder of Yashveer Labs
How this played out on a real project
A client was sending transactional email directly in their API request handlers using a provider SDK. The password reset endpoint took four to eight seconds because it waited for the email API call to complete. When the provider had a partial outage, password resets failed with a 500 error, which meant users who had forgotten their password were also locked out during the outage. The support queue filled in under an hour.
We moved all email sends to a background job queue in three days. The API endpoint now enqueues the job and returns immediately. The job retries on provider errors with exponential backoff. Password resets work during provider partial outages because the queue absorbs the delay. We also discovered the team had no bounce processing at all. Hard bounced addresses were being retried on every send, which was hurting deliverability scores. Adding the bounce webhook handler and suppression list took one additional day.
The second project involved a SaaS that had been sending transactional and marketing email from the same subdomain. A marketing campaign with a higher than normal spam complaint rate briefly affected the inbox placement of their password reset emails. Separating the infrastructure fixed the deliverability. For more on the async infrastructure that supports this, see background job queues the architecture decision founders skip and the outbox pattern a SaaS reliability cheat code.
Common mistakes teams make
- Sending email synchronously in the request handler. The email provider's latency becomes the user's latency.
- No retry logic. A transient provider error causes silent delivery failure.
- No bounce processing. Hard bounced addresses accumulate and hurt sender reputation.
- Templates stored only in the provider's UI editor. Unreviewed, untested, and lost if you switch providers.
- Shared infrastructure for transactional and marketing email. A marketing campaign complaint rate affects password reset deliverability.
- No idempotency keys. Queue retries send the invoice twice.
- Missing or misconfigured DKIM. The email signs incorrectly and lands in spam.
- No development mail trap. A configuration mistake in staging sends real emails to real users.
A two week plan for clean email infrastructure
- Day one. Audit every email send in the codebase. List each trigger, volume estimate, and current implementation.
- Days two and three. Choose the provider. Configure SPF, DKIM, DMARC, and separate subdomains for transactional and marketing.
- Days four and five. Build the background job integration. Route all sends through the queue with retry and dead letter handling.
- Days six and seven. Migrate templates to React Email or MJML. Add them to the repository and CI pipeline.
- Day eight. Implement the bounce and complaint webhook handler. Build the suppression list table and logic.
- Day nine. Add idempotency keys to all sends. Test with forced retries.
- Days ten and eleven. Set up the development mail trap. Add the environment guard.
- Day twelve. Review deliverability configuration with the provider's tools. Confirm all DNS records are passing.
- Days thirteen and fourteen. Load test the queue under realistic send volume. Confirm retry and dead letter behavior.
For related infrastructure reading, async job failure recovery patterns that actually work covers the retry patterns in detail, and resend vs AWS SES vs Mailgun for transactional email covers the provider selection decision.
Frequently asked
Closing note from the author
I keep these closing notes short on purpose. Most engineers writing about this topic are not the engineer you want to hire. I might be. Yashveer Singh, founder of Yashveer Labs. The contact channel is Instagram. The proof is the portfolio. The standard is in the work. If we are aligned, you will know within five minutes of the first message.
Posts that line up with this one.
- SaaS Architecture and Scaling
Idempotency in API Design: Why It Matters More Than You Think
An idempotent API is one that handles repeated requests gracefully. Building it in from the start is far cheaper than retrofitting it after your first double-charge incident.
- SaaS Architecture and Scaling
Internal Admin Tools: Build vs Buy vs Retool
Every SaaS needs internal tools. The question is whether to build them, buy a platform like Retool, or use a lighter alternative. Here is the decision framework that saves engineering hours without creating tool debt.
- SaaS Architecture and Scaling
Job Failure Recovery: How Good SaaS Companies Sleep at Night
Every background job will fail eventually. The companies that sleep at night are the ones that built failure recovery into the system from day one, not as an afterthought when something broke in production.
- SaaS Architecture and Scaling
Monolith vs Microservices: Why Most Startups Get It Wrong
Microservices are the architecture that works at Netflix and fails at early-stage startups. Here is why the monolith is the right default, when microservices become rational, and how to make the transition without breaking everything.