---
title: Transactional Email Migration Playbook | SendHQ
description: A technical guide to migrating transactional email providers while maintaining observability, event tracking, and deliverability.
canonical: https://sendhq.cc/blog/transactional-email-migration-playbook
last-updated: 2026-09-21
---
# The Transactional Email Migration Playbook

Migrating transactional email providers without losing observability requires a phased approach: dual-sending, event parity mapping, and gradual DNS cutovers.

## The Core Challenge of Migration

To migrate transactional email without losing observability, you must decouple the sending trigger from the provider implementation. The strategy is to implement a provider abstraction layer that allows for dual-sending (shadowing) and event mapping. By routing a small percentage of traffic to the new provider while continuing to track delivery events via webhooks, you can verify that the new provider accepts the mail and that your observability pipeline captures the results before switching the primary flow.

## Why Migrations Happen

Most migrations are driven by cost, developer experience, or compliance. For example, the cost delta between providers is significant. According to [Amazon SES pricing](https://aws.amazon.com/ses/pricing/), a la carte sending costs 0.10 USD per 1,000 emails. In contrast, [Postmark pricing](https://postmarkapp.com/pricing) starts at 15 USD per month for 10,000 emails with overages between 1.80 and 1.20 USD per 1,000. Sending 50,000 emails costs roughly 5 USD on SES a la carte, compared to approximately 66 USD on Postmark tiers.

Other drivers include the shift toward EU-only privacy-minimized telemetry or the need for better agent readiness (such as MCP server support). Regardless of the reason, the risk is the same: a blind spot in your delivery pipeline during the transition.

## Phase 1: The Abstraction Layer

If your application calls a provider SDK directly in your business logic, you are locked in. You need a wrapper that standardizes the request and the response.

### The Unified Payload

Define a internal schema that is provider agnostic. This prevents your application from caring whether the underlying API expects `to` as an array or a single string.

`{ "message_id": "msg_12345", "recipient": "user@example.com", "template_id": "welcome_email", "variables": { "name": "Alex" }, "idempotency_key": "unique_request_id_789" }`

When dealing with AI agents or automated workflows, treating email as an external side effect is critical. You must use an [idempotency key](https://sendhq.cc/terms/idempotency-key) to ensure that a retried agent loop does not send the same transactional email five times to one user.

## Phase 2: DNS and Identity Setup

Before sending a single email, you must establish your identity. This is where most migrations fail due to DNS propagation delays or misconfigurations.

1. **Verify Domains**: Add the new provider's DKIM and SPF records. Use a tool like the [SendHQ Email DNS Checker](https://sendhq.cc/tools/email-dns-checker.md) to verify that your records are live and correctly formatted.
2. **Understand the Records**: Ensure you understand the difference between [SPF](https://sendhq.cc/terms/spf) (which authorizes the server) and DKIM (which signs the message). If you are using multiple providers during a migration, your SPF record must include both.
3. **DMARC Alignment**: Ensure your DMARC policy is set to `p=none` during the initial migration phase to avoid hard bounces if alignment is slightly off. Refer to the [SendHQ guide on DKIM, SPF, and DMARC](https://sendhq.cc/guides/email-dkim-spf-dmarc.md) for detailed setup steps.

## Phase 3: The Shadow Send (Dual-Sending)

Do not flip a switch. Instead, implement a routing logic that sends to the primary provider and asynchronously sends a duplicate (or a sampled percentage) to the new provider.

### Implementation Logic

`async function sendEmail(payload) { // Primary send (Current Provider) const primaryResult = await primaryProvider.send(payload); // Shadow send (New Provider) - do not await or block the main thread if (Math.random() < 0.1) { // 10% sample newProvider.send(payload).catch(err => console.error("Shadow send failed", err) ); } return primaryResult; }`

During this phase, you are testing **provider acceptance**. This is the moment the provider says "Yes, I will take this message." This is distinct from **delivery** (the message reaching the receiving server) and **inbox placement** (the message avoiding the spam folder).

## Phase 4: Observability and Event Parity

Observability is the ability to track a message from `sent` to `delivered` or `bounced`. Every provider has a different webhook schema.

### Mapping the Events

Create a mapping table to normalize events into your internal database:

Internal Event | Amazon SES | Resend | Postmark | SendHQ

`sent` | Send | sent | Sent | sent

`delivered` | Delivery | delivered | Delivered | delivered

`bounced` | Bounce | bounced | Bounced | bounced

`complaint` | Complaint | complained | Complaint | complaint

### Handling Webhook Payloads

Your webhook listener should be generic. If you receive a payload from a new provider, it should be processed through a transformer before hitting your analytics engine.

`function transformWebhook(provider, payload) { switch(provider) { case 'resend': return { event: payload.data.delivered ? 'delivered' : 'failed', id: payload.data.id }; case 'sendhq': return { event: payload.event, id: payload.message_id }; default: throw new Error("Unknown provider"); } }`

## Phase 5: The Gradual Cutover

Once you have verified that the new provider accepts the mail and your webhooks are correctly mapping events, move to a weighted distribution.

1. **1% Traffic**: Route 1% of all transactional mail to the new provider. Monitor the bounce rates.
2. **10% Traffic**: Increase the load. Check for rate limits. For example, [Resend's free tier](https://resend.com/pricing) is capped at 100 emails per day, which can be a bottleneck during testing.
3. **50% Traffic**: This is the stability test. Ensure your latency remains acceptable.
4. **100% Traffic**: Final cutover.

## Troubleshooting Common Migration Failures

### The "Silent Drop"

Some providers accept the email (202 Accepted) but drop it internally due to content filters or unverified sender identities. This is why the shadow send phase is non-negotiable. If your `sent` events are high but `delivered` events are low, you have a delivery issue, not an API issue.

### Rate Limit Spikes

Different providers have different burst limits. [Mailgun pricing](https://www.mailgun.com/pricing/) and [SendGrid pricing](https://sendgrid.com/pricing) (which now uses a 60-day trial for free tiers) often come with different throughput quotas. If you migrate from a high-limit account to a new account, you may be throttled. Implement a queue (like RabbitMQ or SQS) to smooth out spikes.

### Idempotency Failures

When switching providers, you might accidentally trigger a retry of a batch. If you are using AI agents to trigger emails, ensure the agent provides a unique request ID. If the agent is using an MCP server to interact with your email API, the API should reject duplicate `idempotency_key` values within a 24-hour window.

## Migration Checklist

- Abstraction layer implemented (Provider agnostic payload).
- DNS records (SPF, DKIM) added for the new provider.
- DNS verified via [sendhq.cc/tools/email-dns-checker](https://sendhq.cc/tools/email-dns-checker.md).
- Webhook listener updated to handle new provider schemas.
- Event mapping table completed (Sent, Delivered, Bounced, Complaint).
- Shadow sending active at 1% to 10%.
- Idempotency keys verified for agent-driven sends.
- Gradual ramp-up (1%, 10%, 50%, 100%).
- Old provider API keys revoked after 7 days of 100% stability.

## Final Thoughts on Provider Choice

Choosing a provider is a tradeoff between cost and developer velocity. If you need the absolute lowest cost, [Amazon SES](https://aws.amazon.com/ses/pricing/) is hard to beat at 0.10 USD per 1,000 emails a la carte, though their new tiered plans (Essentials at 0.16 USD, Pro at 0.22 USD) introduce different cost structures as of July 21, 2026. If you need a modern API with built-in agent readiness and EU-only privacy-minimized telemetry, SendHQ provides a streamlined alternative.

Regardless of the provider, the goal is to ensure that your engineering team is not tethered to a specific vendor's SDK. By treating email as a standardized side effect, you turn a high-risk migration into a routine configuration change.

Learn more about building reliable email workflows at https://sendhq.cc.

## Keep reading

- [Idempotency Keys for Email APIs](https://sendhq.cc/blog/idempotency-keys-email-api): Prevent duplicate emails during network retries by implementing idempotency keys. Learn how to handle distributed system failures without sp Read article →
- [Designing Email Webhooks for At-Least-Once Delivery](https://sendhq.cc/blog/email-webhook-reliability-patterns): Learn how to build resilient webhook consumers for email events using retries, idempotency keys, and signature verification to ensure no del Read article →
- [Transactional Email API Production Checklist](https://sendhq.cc/blog/transactional-email-api-production-checklist): A technical guide for engineers launching transactional email systems. Covers DNS verification, idempotency, error handling, and cost analys Read article →
