Switch to Us
Moving to Sovereign Mail takes an afternoon
Every transactional email API does the same job: from, to, subject, body. Switching is a different URL, a different key and a few renamed fields. Your DNS serves both providers at once, so nothing stops while you move.
Let your AI assistant do it
Paste this into Claude, ChatGPT, Gemini, Copilot or Cursor in your project. It works out which provider you use now, reads our machine-readable guide, and shows you the change before making it.
Switch this codebase's transactional email from whichever provider it uses now (Postmark, Mailgun, MailerSend, Mailjet, SMTP2GO, SendGrid, Amazon SES, Resend, or plain SMTP) to Sovereign Mail. Find every place it sends email first. Read https://sovmail.co.uk/llms-full.txt and follow its contract exactly. The API key is in SOVMAIL_API_KEY and the from-address in SOVMAIL_FROM. Keep the old provider working behind a config switch until I've checked the new path. Tell me about anything the code relies on that Sovereign Mail doesn't support (several recipients in one message, cc/bcc, reply-to, attachments, display names, open/click tracking) instead of silently dropping it. Show me a diff before changing anything.Four steps, whoever you're leaving
- Create an account and add your sending domain. Publish the DNS records we show you. They sit alongside your current provider's (each provider signs with its own DKIM key), so both keep working during the switch. If your domain already has an SPF record, add our
include:to it rather than creating a second one. - Swap the API call. Point your send call at
POST https://sovmail.co.uk/api/v1/emailwith your Sovereign Mail key, and rename the fields. The guides below map every field for each provider. - Test without sending anything. Use a test key (
smk_test_…): every request is checked and recorded exactly as live, but nothing is delivered and nothing counts against your plan. - Switch over. Put the new path behind a config switch, send a share of your traffic, then all of it. Keep your current provider for a week as a fallback.
Guides by provider
From Postmarkfield mapping & example
| Postmark | Sovereign Mail |
|---|---|
POST https://api.postmarkapp.com/email | POST /api/v1/email |
X-Postmark-Server-Token: … | Authorization: Bearer smk_live_… |
From | from — a bare address (no "Name <…>" yet) |
To — up to 50, comma-separated | to — one address; use batch for several |
Subject · TextBody · HtmlBody | subject · text · html |
/email/withTemplate — TemplateId + TemplateModel | /email — templateId + variables (string values) |
/email/batch — an array, up to 500 | /email/batch — { "messages": [ … ] }, up to 500 |
MessageStream | Not needed — transactional only |
TrackOpens · TrackLinks | Not available — we never track recipients |
Bounce & spam-complaint webhooks | Applied automatically: the address is suppressed |
Before · Postmark
curl https://api.postmarkapp.com/email \
-H "X-Postmark-Server-Token: $POSTMARK_TOKEN" \
-H "Content-Type: application/json" \
-d '{"From":"noreply@example.co.uk","To":"user@example.org",
"Subject":"Reset your password","TextBody":"…","MessageStream":"outbound"}'After · Sovereign Mail
curl https://sovmail.co.uk/api/v1/email \
-H "Authorization: Bearer $SOVMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from":"noreply@example.co.uk","to":"user@example.org",
"subject":"Reset your password","text":"…"}'Specific to Postmark
- Postmark templates use {{name}} placeholders, like ours. Mustachio sections ({{#name}}…{{/name}}) become {{#if name}}…{{/if}}, and {{#each}} loops aren't supported yet.
- Your Postmark suppressions live per message stream. Export them from each stream you send on.
More on the two side by side: Sovereign Mail vs Postmark.
From Mailgunfield mapping & example
| Mailgun | Sovereign Mail |
|---|---|
POST https://api.mailgun.net/v3/{domain}/messages (EU: api.eu.mailgun.net) | POST /api/v1/email — the domain comes from your from-address |
Basic auth — api:YOUR_API_KEY | Authorization: Bearer smk_live_… |
Form fields (-F) | A JSON body |
from | from — a bare address (no "Name <…>" yet) |
to — repeatable, up to 1,000 with recipient-variables | to — one address; use batch (up to 500) for several |
subject · text · html | subject · text · html |
template + t:variables (or h:X-Mailgun-Variables) | templateId + variables (string values) |
o:testmode=yes | A test key (smk_test_…): checked and recorded, never delivered |
o:tracking · o:tracking-clicks · o:tracking-opens | Not available — we never track recipients |
Bounce & complaint webhooks | Applied automatically: the address is suppressed |
Before · Mailgun
curl -s --user "api:$MAILGUN_API_KEY" \ https://api.eu.mailgun.net/v3/mg.example.co.uk/messages \ -F from="Example <noreply@example.co.uk>" \ -F to=user@example.org \ -F subject="Reset your password" \ -F text="…"
After · Sovereign Mail
curl https://sovmail.co.uk/api/v1/email \
-H "Authorization: Bearer $SOVMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from":"noreply@example.co.uk","to":"user@example.org",
"subject":"Reset your password","text":"…"}'Specific to Mailgun
- Mailgun takes form fields; we take JSON. Most Mailgun SDK calls become one fetch() or HTTP-client call.
- Mailgun often sends from a subdomain such as mg.example.co.uk. You can keep that subdomain or verify the main domain with us instead.
- Mailgun's batch sending (one call, many recipients with %recipient.name% substitution) maps to our batch endpoint: one message object per recipient.
From MailerSendfield mapping & example
| MailerSend | Sovereign Mail |
|---|---|
POST https://api.mailersend.com/v1/email | POST /api/v1/email |
Authorization: Bearer mlsn.… | Authorization: Bearer smk_live_… |
from — { email, name } | from — a bare address string (no name yet) |
to — [{ email, name }], up to 50 | to — one address string; use batch for several |
subject · text · html | subject · text · html |
template_id + personalization [{ email, data }] | templateId + variables (string values) |
POST /v1/bulk-email — up to 500, processed later | POST /api/v1/email/batch — up to 500, answered at once |
cc · bcc · reply_to · attachments | Not yet supported |
Open & click tracking (domain settings) | Not available — we never track recipients |
Bounce & spam-complaint webhooks | Applied automatically: the address is suppressed |
Before · MailerSend
curl https://api.mailersend.com/v1/email \
-H "Authorization: Bearer $MAILERSEND_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"from":{"email":"noreply@example.co.uk","name":"Example"},
"to":[{"email":"user@example.org"}],
"subject":"Reset your password","text":"…"}'After · Sovereign Mail
curl https://sovmail.co.uk/api/v1/email \
-H "Authorization: Bearer $SOVMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from":"noreply@example.co.uk","to":"user@example.org",
"subject":"Reset your password","text":"…"}'Specific to MailerSend
- Objects become strings: MailerSend's from and to are { email, name } objects, ours are plain addresses.
- MailerSend's bulk-email returns an id you poll later. Our batch answers straight away, with a result per message.
From Mailjetfield mapping & example
| Mailjet | Sovereign Mail |
|---|---|
POST https://api.mailjet.com/v3.1/send | POST /api/v1/email (one) or /api/v1/email/batch (several) |
Basic auth — API key : secret key | Authorization: Bearer smk_live_… (one key, no secret pair) |
Messages: [ … ] — up to 50 per call | batch — { "messages": [ … ] }, up to 500 |
From — { Email, Name } | from — a bare address string (no name yet) |
To — [{ Email, Name }] | to — one address string |
Subject · TextPart · HTMLPart | subject · text · html |
TemplateID + TemplateLanguage + Variables | templateId + variables (string values) |
SandboxMode: true | A test key (smk_test_…): checked and recorded, never delivered |
TrackOpens · TrackClicks | Not available — we never track recipients |
Event API — bounce, spam, blocked | Applied automatically: the address is suppressed |
Before · Mailjet
curl https://api.mailjet.com/v3.1/send \
--user "$MJ_APIKEY_PUBLIC:$MJ_APIKEY_PRIVATE" \
-H "Content-Type: application/json" \
-d '{"Messages":[{"From":{"Email":"noreply@example.co.uk"},
"To":[{"Email":"user@example.org"}],
"Subject":"Reset your password","TextPart":"…"}]}'After · Sovereign Mail
curl https://sovmail.co.uk/api/v1/email \
-H "Authorization: Bearer $SOVMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from":"noreply@example.co.uk","to":"user@example.org",
"subject":"Reset your password","text":"…"}'Specific to Mailjet
- Field names go from PascalCase to lowercase: TextPart becomes text, HTMLPart becomes html.
- Mailjet's template placeholders ({{var:name}}) become {{name}} in our templates.
- Sending through Mailjet's SMTP relay (in-v3.mailjet.com)? Replace the SMTP transport with one HTTPS call to our API.
From SMTP2GOfield mapping & example
| SMTP2GO | Sovereign Mail |
|---|---|
POST https://api.smtp2go.com/v3/email/send | POST /api/v1/email |
X-Smtp2go-Api-Key: api-… (or api_key in the body) | Authorization: Bearer smk_live_… |
sender — "Name <address>" allowed | from — a bare address (no name yet) |
to — an array, up to 100 | to — one address; use batch for several |
subject · text_body · html_body | subject · text · html |
template_id + template_data | templateId + variables (string values) |
cc · bcc · custom_headers · attachments | Not yet supported |
Open & click tracking | Not available — we never track recipients |
Bounce & spam webhooks | Applied automatically: the address is suppressed |
Before · SMTP2GO
curl https://api.smtp2go.com/v3/email/send \
-H "X-Smtp2go-Api-Key: $SMTP2GO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"sender":"noreply@example.co.uk","to":["user@example.org"],
"subject":"Reset your password","text_body":"…"}'After · Sovereign Mail
curl https://sovmail.co.uk/api/v1/email \
-H "Authorization: Bearer $SOVMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from":"noreply@example.co.uk","to":"user@example.org",
"subject":"Reset your password","text":"…"}'Specific to SMTP2GO
- Many SMTP2GO customers send over SMTP (mail.smtp2go.com) rather than the API. We're REST-only for now, so replace the SMTP transport with one HTTPS call. Most frameworks' mailers have an HTTP transport, or it's a short function.
- to is a single string with us, not an array.
From SendGridfield mapping & example
| SendGrid | Sovereign Mail |
|---|---|
POST https://api.sendgrid.com/v3/mail/send | POST /api/v1/email |
Authorization: Bearer SG.… | Authorization: Bearer smk_live_… |
personalizations[].to[] — { email, name } | to — one address; use batch for several |
from — { email, name } | from — a bare address string (no name yet) |
content: [{ type: "text/plain" | "text/html", value }] | text · html |
template_id + dynamic_template_data | templateId + variables (string values) |
mail_settings.sandbox_mode.enable | A test key (smk_test_…): checked and recorded, never delivered |
tracking_settings — opens, clicks, subscriptions | Not available — we never track recipients |
Event webhook — bounce, dropped, spamreport | Applied automatically: the address is suppressed |
Before · SendGrid
curl https://api.sendgrid.com/v3/mail/send \
-H "Authorization: Bearer $SENDGRID_API_KEY" \
-H "Content-Type: application/json" \
-d '{"personalizations":[{"to":[{"email":"user@example.org"}]}],
"from":{"email":"noreply@example.co.uk"},"subject":"Reset your password",
"content":[{"type":"text/plain","value":"…"}]}'After · Sovereign Mail
curl https://sovmail.co.uk/api/v1/email \
-H "Authorization: Bearer $SOVMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from":"noreply@example.co.uk","to":"user@example.org",
"subject":"Reset your password","text":"…"}'Specific to SendGrid
- SendGrid's nesting flattens out: one message is just from, to, subject and text or html.
- Dynamic templates use Handlebars {{name}} placeholders, like ours. {{#each}} loops aren't supported yet. Pass prepared strings instead.
- SendGrid answers a send with an empty 202. We answer with the message id and status, so you can log them.
From Amazon SESfield mapping & example
| Amazon SES | Sovereign Mail |
|---|---|
SESv2 SendEmail (SDK or AWS CLI), signed with IAM credentials | POST /api/v1/email with Authorization: Bearer smk_live_… |
FromEmailAddress | from — a bare address |
Destination.ToAddresses | to — one address; use batch for several |
Content.Simple — Subject.Data · Body.Text.Data · Body.Html.Data | subject · text · html |
Content.Template — TemplateName + TemplateData (a JSON string) | templateId + variables (an object of strings) |
SendBulkEmail — up to 50 destinations | POST /api/v1/email/batch — up to 500 |
Mailbox simulator (success@simulator.amazonses.com) | A test key (smk_test_…): checked and recorded, never delivered |
ConfigurationSetName | Not needed |
SNS / EventBridge bounce & complaint notifications | Applied automatically: the address is suppressed |
Before · Amazon SES
aws sesv2 send-email --region eu-west-2 \
--from-email-address noreply@example.co.uk \
--destination ToAddresses=user@example.org \
--content 'Simple={Subject={Data="Reset your password"},Body={Text={Data="…"}}}'After · Sovereign Mail
curl https://sovmail.co.uk/api/v1/email \
-H "Authorization: Bearer $SOVMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from":"noreply@example.co.uk","to":"user@example.org",
"subject":"Reset your password","text":"…"}'Specific to Amazon SES
- No AWS SDK or IAM policy is needed: an API key and one HTTPS call replace them.
- SES in eu-west-2 is in London, but Amazon is a US company, so the data still falls under the US CLOUD Act. That's usually the reason teams move.
- You can stop handling SES bounce and complaint notifications through SNS. We suppress those addresses ourselves.
From Resendfield mapping & example
| Resend | Sovereign Mail |
|---|---|
POST https://api.resend.com/emails | POST /api/v1/email |
Authorization: Bearer re_… | Authorization: Bearer smk_live_… |
from — "Name <address>" allowed | from — a bare address (no name yet) |
to — a string or an array, up to 50 | to — one address string; use batch for several |
subject · text · html | subject · text · html |
react (Node SDK) | html — render the component to HTML first (@react-email/render) |
POST /emails/batch — up to 100 | POST /api/v1/email/batch — up to 500 |
delivered@resend.dev test addresses | A test key (smk_test_…): checked and recorded, never delivered |
Webhooks — email.bounced, email.complained | Applied automatically: the address is suppressed |
Before · Resend
curl https://api.resend.com/emails \
-H "Authorization: Bearer $RESEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from":"Example <noreply@example.co.uk>","to":"user@example.org",
"subject":"Reset your password","text":"…"}'After · Sovereign Mail
curl https://sovmail.co.uk/api/v1/email \
-H "Authorization: Bearer $SOVMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from":"noreply@example.co.uk","to":"user@example.org",
"subject":"Reset your password","text":"…"}'Specific to Resend
- Resend's request body is nearly ours already. The usual changes are the URL, the key and dropping the display name.
- Using React Email? Keep it. Render to HTML with @react-email/render and send that as html.
- scheduled_at and tags aren't supported. Schedule the call in your own job queue instead.
What works differently, whoever you're coming from
- One recipient per message. Where you sent to several addresses at once, send one message each, or use batch (up to 500 per call).
- No display name in the from-address yet: noreply@example.co.uk, not "Example <noreply@…>".
- No cc, bcc, reply-to, custom headers or attachments yet. Unknown fields are ignored, not rejected, so check your code doesn't rely on them.
- REST API only: no SMTP relay yet.
- Template variables are strings. Format numbers and dates before you send them.
- Bounces and spam complaints aren't sent to you as webhooks. We suppress the address automatically, so you don't need to act on them.
- Your old provider's suppression list doesn't carry over automatically. Export it and add the addresses on the Suppressions page (there's no bulk import yet).
- Our sending IPs are newer, so start by moving part of your traffic. Limits rise as your sending history builds.
Frequently asked questions
How long does switching take?
For most apps the code change is under an hour: one HTTPS call with a different URL, key and field names. DNS verification usually completes within minutes of publishing the records.
Do I have to move everything at once?
No. Both providers can send from the same domain at the same time, each with its own DKIM key. Move a share of your traffic, check it, then move the rest.
Will my emails stop while I switch?
No. Adding our DNS records doesn't affect your current provider, and your code keeps using it until you flip the switch.
Start with the free plan
No card needed. Build and test the switch with a test key, then go live when you're ready.
Create an account