Postmark to AWS SES Migration
Migrating from Postmark to AWS SES: When and How
Postmark is the best deliverability-focused ESP on the market. SES is 5–10x cheaper at high volume. This guide helps you decide whether the cost savings justify the operational trade-offs — and how to execute the migration if they do.
Postmark is the premium option in transactional email — priced accordingly, and genuinely worth it for teams that prioritize deliverability above all else. AWS SES is a commodity infrastructure service priced at cost. The decision to migrate is fundamentally about whether the cost savings justify building the operational scaffolding that Postmark handles for you.
This guide gives you the honest trade-off analysis and the step-by-step migration path if you decide to move.
## The Cost Difference Is Stark
No other comparison in the transactional email space has this wide a price gap at volume.
| Volume | Postmark | AWS SES | Monthly Savings | Annual Savings |
| ---------------- | ----------- | ------------- | --------------- | -------------- |
| 10,000 emails | $15/month | $1.00/month | $14.00 | $168 |
| 50,000 emails | $50/month | $5.00/month | $45.00 | $540 |
| 125,000 emails | $100/month | $12.50/month | $87.50 | $1,050 |
| 500,000 emails | ~$400/month | $50.00/month | $350.00 | $4,200 |
| 1,000,000 emails | ~$800/month | $100.00/month | $700.00 | $8,400 |
SES dedicated IPs ($24.95/IP/month) add some cost, but even with two dedicated IPs, SES is 3–6x cheaper than Postmark at 500K+ emails per month.
## Message Streams vs SES Configuration Sets
Postmark's message streams are its most distinctive feature. Each stream has its own dedicated IP pool, separate bounce/complaint tracking, and isolated reputation. You create a transactional stream and a broadcast (marketing) stream, and Postmark ensures that a complaint spike on your broadcast stream cannot damage your transactional inbox placement.
SES does not have a native message stream concept, but you can replicate the isolation:
| Postmark Concept | SES Equivalent | Setup Required |
| ------------------------------ | --------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Message stream (transactional) | Dedicated Configuration Set + IP pool | Create Config Set, request dedicated IPs, assign to transactional sends |
| Message stream (broadcast) | Separate Configuration Set + IP pool | Second Config Set with separate dedicated IPs; enforce separation in app code |
| Per-stream bounce tracking | Per-Configuration-Set SNS event destination | Create SNS topic per Config Set; subscribe Lambda to aggregate bounce metrics |
| Per-stream complaint tracking | Per-Configuration-Set SNS event destination | Same SNS topic or separate; Lambda processes complaint events per Config Set |
| Stream-level suppression | Account-level suppression list + custom store | SES account-level list handles hard bounces; application database for stream-level unsubscribes |
| Message stream isolation | IP pool isolation + Config Set enforcement | No automatic enforcement — application must select correct Config Set per send |
## API Migration — Postmark → AWS SES
The biggest operational change is moving from Postmark's HTTP-based API to SES's SDK calls. Here's the mapping:
| Postmark | AWS SES | Migration Notes |
| ------------------------------------ | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `POST /email` | `SendEmail()` or `SendTemplatedEmail()` | Postmark accepts JSON; SES uses SDK methods. Use boto3 or AWS SDK for your language. |
| `MessageStream: "transactional"` | `ConfigurationSetName: "transactional"` | Set this on every `SendEmail()` call to route to the right IP pool |
| Template system (Mustache `{{var}}`) | No template engine | **Move rendering to application layer.** Use React Email, MJML, or Handlebars in your app. Pre-render the full HTML, send via SES. |
| `Metadata: { key: value }` | `MessageAttributes` | SES equivalent for custom headers; same pattern for tracking. |
| Webhook pushes to HTTP endpoint | SNS topic subscriptions | SES events → SNS → Lambda → your application. More infrastructure but more control. |
**Key difference:** Postmark templates live in Postmark's UI. SES has no template engine. This forces you to move template logic to your application layer, which is actually cleaner long-term (you control versioning, you can test templates with your code).
## Bounce and Complaint Handling
Postmark pushes bounce and complaint webhooks directly to an HTTP endpoint you provide. SES routes these through SNS, requiring a Lambda subscriber.
### Architecture:
```
SES sends email
↓
SES detects bounce or complaint
↓
SES publishes to SNS topic
↓
Lambda is subscribed to SNS topic
↓
Lambda processes event and calls your app (webhook)
```
### Mapping Postmark Webhook Fields to SES:
| Postmark Bounce Event | SES JSON | SES Bounce Type |
| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------ |
| `Type: "Permanent"` | `"bounce": { "bounceType": "Permanent" }` | Hard bounce — recipient address doesn't exist |
| `Type: "Transient"` | `"bounce": { "bounceType": "Transient" }` | Soft bounce — mailbox full, server temporarily unavailable |
| `Details: { Status: 422 }` | `"bounce": { "bounceSubType": "MailFromDomainNotVerified" }` | Bounce reason embedded in `bounceSubType` |
| Complaint webhook | `"complaint": { ... }` | Recipient marked your email as spam (IMPORTANT: add to suppression list immediately) |
### Lambda Handler Pattern:
```typescript
exports.handler = async (event) => {
// SNS wraps the SES event in a Message field
const message = JSON.parse(event.Records[0].Sns.Message);
if (message.bounce) {
console.log(`Hard bounce: ${message.bounce.bounceSubType}`);
// Update your suppression list
}
if (message.complaint) {
console.log(`Complaint from: ${message.complaint.complainedRecipients}`);
// Update your suppression list immediately
}
};
```
## Activity Log Replacement
This is the most painful part of the Postmark → SES transition. Postmark keeps 45 days of searchable email activity — every send, bounce, click, open, complaint — all searchable by recipient, subject, time range, etc.
**SES has no equivalent UI.** You get SNS events, but no built-in searchable history.
### DIY replacement architecture:
1. **SNS → Lambda** — capture all SES events (send, bounce, complaint, open, click)
2. **Lambda → DynamoDB** — store each event with TTL set to 45 days
3. **DynamoDB Streams → optional OpenSearch** — for full-text search on subjects and recipient emails
4. **Query layer** — Lambda API to search by email, date range, event type
**Estimated effort:** 20–30 hours to build; 1–2 hours/month to maintain.
**Cost impact:** DynamoDB for 45 days of email events:
- 500K emails/month = ~17K events/day = ~750K events in storage at any time
- DynamoDB on-demand: ~$0.25/month (read/write heavily discounted for low traffic)
- OpenSearch cluster: $50–200/month (optional, for advanced search)
**Bottom line:** This is a one-time engineering investment that pays back in 2–3 months versus Postmark's premium. For teams with engineering capacity, the ROI is clear.
## IP Warming Schedule
Moving to dedicated IPs on SES requires warming to establish sender reputation. Follow this conservative schedule to ramp sending volume gradually:
| Day Range | Daily Volume Cap | Rationale |
| ---------- | ------------------- | ------------------------------------------------------------------- |
| Days 1–3 | 200 emails/day | Mailbox providers observe sender behavior; start conservatively |
| Days 4–6 | 500 emails/day | Gradual increase; mailbox providers are building reputation profile |
| Days 7–9 | 1,000 emails/day | Consistent sending establishes trust |
| Days 10–12 | 2,000 emails/day | Continue gradual ramp |
| Days 13–15 | 5,000 emails/day | Halfway through warming period |
| Days 16–18 | 10,000 emails/day | Reputation is building; can accelerate |
| Days 19–21 | 20,000 emails/day | Final ramping phase |
| Days 22–24 | 50,000 emails/day | Approaching full volume |
| Days 25–27 | 100,000 emails/day | Nearly at target |
| Days 28–30 | Full sending volume | Reputation established; send at full capacity |
**Critical metrics during warming:**
- **Bounce rate:** Keep below 2%. Hard bounces (permanent) are expected; transient bounces should drop over time.
- **Complaint rate:** Keep below 0.1%. One complaint per 1,000 emails is typical; higher signals list quality issues.
- **Seed list monitoring:** Send to role addresses (postmaster@, admin@) — these should never bounce or complain.
If bounce or complaint rates spike, **pause the ramp and investigate**. A spike at day 15 typically signals a bad list or authentication issue.
## Related Comparisons
Explore other technical comparisons:
- [SendGrid to AWS SES](/compare/sendgrid-to-aws-ses)
- [Mailgun to AWS SES](/compare/mailgun-to-aws-ses)
- [SparkPost to AWS SES](/compare/sparkpost-to-aws-ses)
## Why Choose FactualMinds for Your Email Migration
FactualMinds is an **AWS Select Tier Consulting Partner** specializing in email infrastructure migration. We have executed SendGrid, Mailgun, Postmark, and SparkPost to AWS SES migrations and know exactly where teams get stuck.
- **Email migration experts** — we handle domain verification, DKIM, bounce architecture, IP warming
- **Assessment-first approach** — we map your current state before writing a line of infrastructure code
- **Zero-downtime cutover planning included** — no failed deliveries during migration
- **AWS Select Tier Partner** — [verified on AWS Partner Network](https://partners.amazonaws.com/partners/001aq000008su2EAAQ/Factual%20Minds)
---Postmark is the premium option in transactional email — priced accordingly, and genuinely worth it for teams that prioritize deliverability above all else. AWS SES is a commodity infrastructure service priced at cost. The decision to migrate is fundamentally about whether the cost savings justify building the operational scaffolding that Postmark handles for you.
This guide gives you the honest trade-off analysis and the step-by-step migration path if you decide to move.
The Cost Difference Is Stark
No other comparison in the transactional email space has this wide a price gap at volume.
| Volume | Postmark | AWS SES | Monthly Savings | Annual Savings |
|---|---|---|---|---|
| 10,000 emails | $15/month | $1.00/month | $14.00 | $168 |
| 50,000 emails | $50/month | $5.00/month | $45.00 | $540 |
| 125,000 emails | $100/month | $12.50/month | $87.50 | $1,050 |
| 500,000 emails | ~$400/month | $50.00/month | $350.00 | $4,200 |
| 1,000,000 emails | ~$800/month | $100.00/month | $700.00 | $8,400 |
SES dedicated IPs ($24.95/IP/month) add some cost, but even with two dedicated IPs, SES is 3–6x cheaper than Postmark at 500K+ emails per month.
Message Streams vs SES Configuration Sets
Postmark’s message streams are its most distinctive feature. Each stream has its own dedicated IP pool, separate bounce/complaint tracking, and isolated reputation. You create a transactional stream and a broadcast (marketing) stream, and Postmark ensures that a complaint spike on your broadcast stream cannot damage your transactional inbox placement.
SES does not have a native message stream concept, but you can replicate the isolation:
| Postmark Concept | SES Equivalent | Setup Required |
|---|---|---|
| Message stream (transactional) | Dedicated Configuration Set + IP pool | Create Config Set, request dedicated IPs, assign to transactional sends |
| Message stream (broadcast) | Separate Configuration Set + IP pool | Second Config Set with separate dedicated IPs; enforce separation in app code |
| Per-stream bounce tracking | Per-Configuration-Set SNS event destination | Create SNS topic per Config Set; subscribe Lambda to aggregate bounce metrics |
| Per-stream complaint tracking | Per-Configuration-Set SNS event destination | Same SNS topic or separate; Lambda processes complaint events per Config Set |
| Stream-level suppression | Account-level suppression list + custom store | SES account-level list handles hard bounces; application database for stream-level unsubscribes |
| Message stream isolation | IP pool isolation + Config Set enforcement | No automatic enforcement — application must select correct Config Set per send |
API Migration — Postmark → AWS SES
The biggest operational change is moving from Postmark’s HTTP-based API to SES’s SDK calls. Here’s the mapping:
| Postmark | AWS SES | Migration Notes |
|---|---|---|
POST /email | SendEmail() or SendTemplatedEmail() | Postmark accepts JSON; SES uses SDK methods. Use boto3 or AWS SDK for your language. |
MessageStream: "transactional" | ConfigurationSetName: "transactional" | Set this on every SendEmail() call to route to the right IP pool |
Template system (Mustache {{var}}) | No template engine | Move rendering to application layer. Use React Email, MJML, or Handlebars in your app. Pre-render the full HTML, send via SES. |
Metadata: { key: value } | MessageAttributes | SES equivalent for custom headers; same pattern for tracking. |
| Webhook pushes to HTTP endpoint | SNS topic subscriptions | SES events → SNS → Lambda → your application. More infrastructure but more control. |
Key difference: Postmark templates live in Postmark’s UI. SES has no template engine. This forces you to move template logic to your application layer, which is actually cleaner long-term (you control versioning, you can test templates with your code).
Bounce and Complaint Handling
Postmark pushes bounce and complaint webhooks directly to an HTTP endpoint you provide. SES routes these through SNS, requiring a Lambda subscriber.
Architecture:
SES sends email
↓
SES detects bounce or complaint
↓
SES publishes to SNS topic
↓
Lambda is subscribed to SNS topic
↓
Lambda processes event and calls your app (webhook)Mapping Postmark Webhook Fields to SES:
| Postmark Bounce Event | SES JSON | SES Bounce Type |
|---|---|---|
Type: "Permanent" | "bounce": { "bounceType": "Permanent" } | Hard bounce — recipient address doesn’t exist |
Type: "Transient" | "bounce": { "bounceType": "Transient" } | Soft bounce — mailbox full, server temporarily unavailable |
Details: { Status: 422 } | "bounce": { "bounceSubType": "MailFromDomainNotVerified" } | Bounce reason embedded in bounceSubType |
| Complaint webhook | "complaint": { ... } | Recipient marked your email as spam (IMPORTANT: add to suppression list immediately) |
Lambda Handler Pattern:
exports.handler = async (event) => {
// SNS wraps the SES event in a Message field
const message = JSON.parse(event.Records[0].Sns.Message);
if (message.bounce) {
console.log(`Hard bounce: ${message.bounce.bounceSubType}`);
// Update your suppression list
}
if (message.complaint) {
console.log(`Complaint from: ${message.complaint.complainedRecipients}`);
// Update your suppression list immediately
}
};Activity Log Replacement
This is the most painful part of the Postmark → SES transition. Postmark keeps 45 days of searchable email activity — every send, bounce, click, open, complaint — all searchable by recipient, subject, time range, etc.
SES has no equivalent UI. You get SNS events, but no built-in searchable history.
DIY replacement architecture:
- SNS → Lambda — capture all SES events (send, bounce, complaint, open, click)
- Lambda → DynamoDB — store each event with TTL set to 45 days
- DynamoDB Streams → optional OpenSearch — for full-text search on subjects and recipient emails
- Query layer — Lambda API to search by email, date range, event type
Estimated effort: 20–30 hours to build; 1–2 hours/month to maintain.
Cost impact: DynamoDB for 45 days of email events:
- 500K emails/month = ~17K events/day = ~750K events in storage at any time
- DynamoDB on-demand: ~$0.25/month (read/write heavily discounted for low traffic)
- OpenSearch cluster: $50–200/month (optional, for advanced search)
Bottom line: This is a one-time engineering investment that pays back in 2–3 months versus Postmark’s premium. For teams with engineering capacity, the ROI is clear.
IP Warming Schedule
Moving to dedicated IPs on SES requires warming to establish sender reputation. Follow this conservative schedule to ramp sending volume gradually:
| Day Range | Daily Volume Cap | Rationale |
|---|---|---|
| Days 1–3 | 200 emails/day | Mailbox providers observe sender behavior; start conservatively |
| Days 4–6 | 500 emails/day | Gradual increase; mailbox providers are building reputation profile |
| Days 7–9 | 1,000 emails/day | Consistent sending establishes trust |
| Days 10–12 | 2,000 emails/day | Continue gradual ramp |
| Days 13–15 | 5,000 emails/day | Halfway through warming period |
| Days 16–18 | 10,000 emails/day | Reputation is building; can accelerate |
| Days 19–21 | 20,000 emails/day | Final ramping phase |
| Days 22–24 | 50,000 emails/day | Approaching full volume |
| Days 25–27 | 100,000 emails/day | Nearly at target |
| Days 28–30 | Full sending volume | Reputation established; send at full capacity |
Critical metrics during warming:
- Bounce rate: Keep below 2%. Hard bounces (permanent) are expected; transient bounces should drop over time.
- Complaint rate: Keep below 0.1%. One complaint per 1,000 emails is typical; higher signals list quality issues.
- Seed list monitoring: Send to role addresses (postmaster@, admin@) — these should never bounce or complain.
If bounce or complaint rates spike, pause the ramp and investigate. A spike at day 15 typically signals a bad list or authentication issue.
Related Comparisons
Explore other technical comparisons:
Why Choose FactualMinds for Your Email Migration
FactualMinds is an AWS Select Tier Consulting Partner specializing in email infrastructure migration. We have executed SendGrid, Mailgun, Postmark, and SparkPost to AWS SES migrations and know exactly where teams get stuck.
- Email migration experts — we handle domain verification, DKIM, bounce architecture, IP warming
- Assessment-first approach — we map your current state before writing a line of infrastructure code
- Zero-downtime cutover planning included — no failed deliveries during migration
- AWS Select Tier Partner — verified on AWS Partner Network
Frequently Asked Questions
Is AWS SES as reliable as Postmark?
How do I migrate Postmark templates to AWS SES?
Does AWS SES have message streams like Postmark?
Is Postmark worth the premium over SES?
What does Postmark charge vs AWS SES?
Need Help Migrating to AWS SES?
FactualMinds is an AWS Select Tier Partner specializing in email infrastructure migration. We handle domain verification, Configuration Set architecture, bounce handling, IP warming, and cutover.
