Engineering · September 21, 2026
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, a la carte sending costs 0.10 USD per 1,000 emails. In contrast, Postmark 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 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.
- Verify Domains: Add the new provider's DKIM and SPF records. Use a tool like the SendHQ Email DNS Checker to verify that your records are live and correctly formatted.
- Understand the Records: Ensure you understand the difference between 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.
- DMARC Alignment: Ensure your DMARC policy is set to
p=noneduring the initial migration phase to avoid hard bounces if alignment is slightly off. Refer to the SendHQ guide on DKIM, SPF, and DMARC 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% Traffic: Route 1% of all transactional mail to the new provider. Monitor the bounce rates.
- 10% Traffic: Increase the load. Check for rate limits. For example, Resend's free tier is capped at 100 emails per day, which can be a bottleneck during testing.
- 50% Traffic: This is the stability test. Ensure your latency remains acceptable.
- 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 and SendGrid 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.
- 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 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.