Migrate to Mailtrap from Amazon SES
A complete technical guide to switch from Amazon SES to Mailtrap. Most teams complete the migration in under an hour.
Migration Checklist
- Authenticate your domain
Add and verify your domain before sending. Follow the Domain Setup article at docs.mailtrap.io.
Need some help?
Contact our support and our developers will help you with it.
- Get your API token
Mailtrap auto-generates a token when you add a domain. Find it under Settings → API Tokens. Read more on API tokens. - Update your integration
Swap your Amazon SES endpoint and credentials for Mailtrap’s (API or SMTP). See the migration sections below. - Migrate templates
Both platforms use Handlebars, so content migrates directly. See the templates sections below. - Migrate suppressions
Export SES suppressions and import them into Mailtrap via CSV or manually. Click here for more information. - Migrate users
Add users from the User Management tab and review permissions during migration. - Set up webhooks
Follow the Mailtrap Webhooks step-by-step guide. - Security and compliance
Visit the Trust Center page to review Mailtrap’s security practices and compliance standards.
Mailtrap tip
You can use ActionMailer Balancer Ruby gem to proportionally distribute the email sending load between two different sending services (e.g. 70% Amazon SES and 30% Mailtrap) to mitigate the sending risks.
Concepts
Before going over the technical details, it’s important to clear up some key concepts.
Sending domains
SES equivalent: Verified Identities (Easy DKIM / BYODKIM)
Both platforms require DNS-based domain authentication with SPF, DKIM, and DMARC. SES calls these “verified identities” and supports:
- Easy DKIM (AWS generates keys, you add three CNAME records) or
- BYODKIM (you supply your own signing keys).
- Custom MAIL FROM domain for SPF alignment.
Mailtrap’s domain setup uses the same DNS standards – SPF, DKIM, DMARC. DKIM records can be added alongside SES’s (different selectors, no conflict), and one DMARC record covers both. For SPF, however, you must merge Mailtrap’s include into your existing SPF record rather than adding a separate one.
Separate sending streams
SES equivalent: N/A – single service, single endpoint per region
SES routes all email through one regional endpoint. You can use Configuration Sets to separate tracking and event routing, but the underlying infrastructure is shared.
Mailtrap separates transactional and bulk email into distinct infrastructure:
- Transactional Stream – For sending user-triggered emails like welcome emails and password resets.
- Bulk Stream – For sending promotional, marketing emails like newsletters and product updates.
By keeping the sending infrastructures separate, you are able to:
- Protect your transactional email reputation from the performance of your bulk campaigns
- Ensure each stream routes through the right IP pools
- Give mailbox providers the signals they need to categorize and deliver your emails correctly

Email categories
SES equivalent: Configuration Sets + Message Tags (EmailTags)
SES uses two mechanisms for categorizing email. Configuration Sets define where event data goes (CloudWatch, Kinesis Firehose, SNS) and apply sending rules.
- Message Tags (EmailTags) are name-value pairs attached to individual sends for filtering events and metrics.
- Mailtrap uses Email Categories — set via the category field in the API or the X-MT-Category header in SMTP.

Key difference: SES’s tagging system is more granular (multiple key-value pairs per message, tied into AWS’s observability stack). Mailtrap assigns one category string per message, which keeps things simpler for most use cases.
Organization & sub-accounts
SES equivalent: IAM Users, Roles, Policies + Sending Authorization
SES inherits AWS’s IAM model for access control – fine-grained policies, cross-account sending authorization, and role-based access. This is powerful but adds operational complexity, especially for teams that only need email.
Mailtrap offers Organization and sub-account management, which lets you manage complex setups involving multiple teams, clients, environments, or products under a single Organization. To start using the feature, you first need to enable it under the Organization tab.

Note: The feature is available to from Business plan onward.
Terminology comparison
- Verified Identities
- Event Publishing (Configuration Sets → Event Destinations)
- IAM Users / Roles / Policies
- Email Templates
- Configuration Sets + Message Tags (EmailTags)
- Headers + X-SES- headers
- Message Tags (EmailTags)
- Account-level Suppression List
- CloudTrail
- N/A (single regional endpoint)
- Event Destinations (SNS, CloudWatch, Kinesis Firehose)
API migration
Authentication
SES and Mailtrap use fundamentally different auth models:
| Amazon SES | Mailtrap | |
| Method | AWS Signature Version 4 (SigV4) | Bearer token in Authorization header |
| Credentials | IAM Access Key ID + Secret Access Key | Single API token |
| Header | Authorization: AWS4-HMAC-SHA256 Credential=… (auto-generated by AWS SDKs) | Authorization: Bearer YOUR_API_KEY |
| Scope | Per-region | Global |
SES auth is tightly coupled to AWS IAM. When migrating, you replace the SigV4 signing logic (or AWS SDK calls) with a single Bearer token header. This significantly simplifies your sending code.
API mapping
| API type | Amazon SES | Mailtrap | Notes |
| Transactional email | POST https://email.{region}.amazonaws.com/v2/email/outbound-emails | POST https://send.api.mailtrap.io/api/send | SES is regional; Mailtrap is global. Completely different payload structures |
| Bulk email | POST https://email.{region}.amazonaws.com/v2/email/outbound-bulk-emails | POST https://bulk.api.mailtrap.io/api/send | SES caps at 50 destinations/call; Mailtrap supports 500/batch |
| Template sending | Same SendEmail with Content.Template | Same send endpoint with template_uuid | SES supports inline templates (no pre-storage needed) via TemplateContent |
| Suppressions | PUT/GET/DELETE /v2/email/suppression/addresses | GET https://mailtrap.io/api/accounts/{account_id}/suppressions | SES also has an AWS-managed Global suppression list you can’t modify |
| Stats | Event Publishing → CloudWatch / Kinesis Firehose | GET /api/accounts/{account_id}/stats, /stats/domains, /stats/categories, /stats/email_service_providers, and /stats/date | SES requires external AWS services for analytics |
| Email logs | Event Destinations + Mail Manager Archive | GET https://mailtrap.io/api/accounts/{account_id}/email_logs | SES has no built-in email log viewer – requires CloudWatch, Kinesis, or Mail Manager |
Outbound Sending API JSON Field Mapping
| Field | Amazon SES (v2) | Mailtrap |
| From email | FromEmailAddress | from.email |
| From name | Included in FromEmailAddress (RFC 5322 format: “Name” <email>) | from.name |
| To email | Destination.ToAddresses[] | to[].email |
| To name | Included in address string (“Name” <email>) | to[].name |
| CC | Destination.CcAddresses[] | cc[].email |
| BCC | Destination.BccAddresses[] | bcc[].email |
| Subject | Content.Simple.Subject.Data | subject |
| HTML body | Content.Simple.Body.Html.Data | html |
| Text body | Content.Simple.Body.Text.Data | text |
| Character encoding | Content.Simple.Subject.Charset, Body.Html.Charset, Body.Text.Charset | N/A – UTF-8 assumed |
| Category/Tag | EmailTags[].{Name, Value} (multiple key-value pairs) | category (single string) |
| Configuration set | ConfigurationSetName | N/A |
| Custom variables | EmailTags | custom_variables |
| Reply-To | ReplyToAddresses[] | reply_to object |
| Custom headers | Content.Simple.Headers[].{Name, Value} | headers |
| Template identifier | Content.Template.TemplateName or Content.Template.TemplateArn | template_uuid |
| Template variables | Content.Template.TemplateData (escaped JSON string) | template_variables (object) |
| Inline template | Content.Template.TemplateContent.{Subject, Html, Text} | N/A – templates must be pre-stored |
| Open tracking | Via Configuration Set event destination | Via account settings or headers |
| Click tracking | Via Configuration Set event destination | Via account settings or headers |
| Attachments | Content.Simple.Attachments[].{FileName, RawContent, ContentType} | attachments:[{content, filename, type, disposition}] |
| Inline images | Content.Simple.Attachments[] with ContentId set | attachments:[{content, filename, type, disposition:”inline”,content_id}] |
| Bounce handling | FeedbackForwardingEmailAddress | Handled via webhooks |
| Sending authorization | FromEmailAddressIdentityArn, FeedbackForwardingEmailAddressIdentityArn | N/A – determined by API token scope |
| List management | ListManagementOptions.{ContactListName, TopicName} | N/A |
| Raw MIME | Content.Raw.Data (base64-encoded MIME) | N/A – use structured fields |
| Sending stream | N/A – single regional endpoint | Determined by endpoint: send.api (transactional) vs bulk.api (bulk) |
Code snippets
-
cURL
aws sesv2 send-email \
--from-email-address "Sender Name <sender@yourdomain.com>" \
--destination '{"ToAddresses": ["recipient@example.com"]}' \
--content '{
"Simple": {
"Subject": {"Data": "Welcome aboard"},
"Body": {
"Html": {"Data": "<h1>Hello</h1><p>Welcome to the platform.</p>"},
"Text": {"Data": "Hello. Welcome to the platform."}
}
}
}' \
--email-tags '[{"Name": "category", "Value": "welcome"}]' \
--region us-east-1
Copy
curl -X POST https://send.api.mailtrap.io/api/send \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"from": {"email": "sender@example.com"},
"to": [{"email": "recipient@example.com"}],
"subject": "Hello from Mailtrap",
"text": "Welcome to Mailtrap!"
}'
Note: SES nests content deeply (Content.Simple.Body.Html.Data), requires regional endpoints, and uses AWS SigV4 auth. Mailtrap uses a flat payload structure with a Bearer token – fewer lines of code, no SDK dependency for signing.
SMTP migration
| Setting | Amazon SES | Mailtrap (Transactional) | Mailtrap (Bulk) |
| Host | email-smtp.{region}.amazonaws.com | live.smtp.mailtrap.io | bulk.smtp.mailtrap.io |
| Port | 587, 2587 (STARTTLS); 465, 2465 (implicit TLS); 25 | 587 (recommended), 25, 2525 | 587 (recommended), 25, 2525 |
| TLS | Required (STARTTLS or implicit TLS) | Required (STARTTLS) | Required (STARTTLS) |
| Auth method | PLAIN, LOGIN | PLAIN, LOGIN | PLAIN, LOGIN |
| Username | IAM SMTP username (Access Key ID) | api (literal string) | api (literal string) |
| Password | Derived from IAM Secret Access Key via HMAC-SHA256 (not the raw key) | API token | API token |
Migration notes:
- Host: Replace
email-smtp.{region}.amazonaws.comwith the appropriate Mailtrap host. If you send both transactional and bulk email over SMTP, you now need two separate SMTP configurations. - Username: SES uses your IAM Access Key ID. Mailtrap requires the literal string
api. - Password: This is the biggest gotcha. SES SMTP passwords are not your IAM Secret Access Key – they’re derived from it via a signing algorithm. Mailtrap just uses the API token directly. No derivation step needed.
- Regional vs global: SES SMTP is regional (credentials are region-specific). Mailtrap SMTP is global.
- Ports 2587/2465: SES offers additional high-numbered ports. Mailtrap uses 587 (recommended), 25, and 2525.

For more information on migrating your SMTP configuration, click this link. ⬅️
Rate limits & quotas
| Limit | Amazon SES | Mailtrap |
| API rate limit | Account-dependent (starts at 1/sec in sandbox, scales with reputation) | 150 requests per 10 seconds per API token |
| Batch size | 50 destinations per SendBulkEmail call | 500 emails per batch call |
| Message size | 40 MB (v2 API/SMTP) | 10 MB default (extendable to 30 MB on request) |
| Max recipients per send | 50 (To + CC + BCC combined) | Single send ( /api/send ) → 1 email, up to 1,000 recipients per field ( to / cc / bcc ) Batch send ( /api/batch ) → up to 500 separate emails per API call |
| Daily sending quota | Account-dependent (200/day in sandbox; request increase for production) | Plan-dependent |
| Non-sending API rate | 1 request/sec for management APIs | Stats API: 10 requests / 60 seconds Suppressions API: 10 requests / 60 seconds Contacts API: 200 requests / 60 seconds Email Logs API: 60/min for list, 1000/min for get-by-ID |
Key differences:
- SES counts recipients, not messages, against your quota. One email to 10 recipients counts as 10.
- SES quotas are per-region. You can send from multiple regions to multiply your effective quota, but each requires separate configuration.
- SES starts in sandbox mode (200 emails/day, 1/sec) — you must request production access. Mailtrap’s free plan starts at 1,000 emails/month with no sandbox restrictions.
- SES has more several non-sending API calls (IAM roles, Lambda functions, etc.) and they follow the same rate as expressed above.
Email templates
Both platforms use Handlebars. If your SES templates use {{variable}} syntax, the markup migrates directly.
Syntax comparison
| Pattern | Amazon SES | Mailtrap |
| Syntax | Handlebars | Handlebars |
| Variable insertion | {{variable_name}} | {{variable_name}} |
| Conditionals | {{#if var}}…{{else}}…{{/if}} | {{#if var}}…{{else}}…{{/if}} |
| Iteration | {{#each items}}…{{/each}} | {{#each items}}…{{/each}} |
| Default/fallback | {{#if var}}{{var}}{{else}}default{{/if}} | {{#if var}}{{var}}{{else}}default{{/if}} |
| Date formatting | Not supported – format before passing | Not supported – format before passing |
| Pass variables via API | TemplateData (escaped JSON string) | template_variables (JSON object) |
| Template identifier | TemplateName (string, up to 64 chars) or TemplateArn | template_uuid |
| Inline templates | Supported via TemplateContent (no pre-storage needed) | N/A – templates must be pre-stored |
| Max templates | 20,000 per region | 200 |
| Inline partials | {{#* inline “name”}}…{{/inline}}+ {{> name}} | Standard Handlebars support |
Migration notes: SES requires TemplateData as an escaped JSON string ("{"name": "John"}"). Mailtrap accepts template_variables as a regular JSON object ({"name": "John"}). This simplifies your code – no double-encoding.
Frequently Asked Questions
-
Do I need to re-verify my domain if I’ve already set it up in Amazon SES?
Yes, you’ll need to re-verify your domain. See the Mailtrap Knowledge Base for up-to-date guidance.
-
Why does Mailtrap have two separate SMTP hosts?
Mailtrap separates transactional and bulk streams to protect your sender reputation and ensure proper delivery routing for each email type.
-
Does Mailtrap offer migration assistance?
Yes, Mailtrap offers migration assistance from Business plan onwards.
-
How do I migrate templates from SES?
Both platforms use Handlebars, so template markup copies over directly. Update your API calls to use
template_uuidinstead ofTemplateName, and pass variables viatemplate_variables(a JSON object) instead ofTemplateData(an escaped JSON string). If you use SES inline templates (TemplateContent), you’ll need to pre-store them in Mailtrap first. -
Do I need to change my SMTP password?
Yes. SES SMTP passwords are derived from your IAM Secret Access Key via a signing algorithm. Mailtrap uses the API token directly as the password – no derivation needed.
-
What about SES sandbox restrictions?
SES starts in sandbox mode (200 emails/day, verified recipients only). Mailtrap has no sandbox; the free plan gives you 1,000 emails/month to any recipient immediately.
-
Can I migrate gradually?
Yes. If you’re on Rails, Mailtrap’s ActionMailer Balancer lets you split traffic proportionally (e.g., 80% SES / 20% Mailtrap) and shift over time. For other frameworks, you can route by email type; e.g., move transactional to Mailtrap first, then bulk.