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

  1. 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.
  2. Swap the API call. Point your send call at POST https://sovmail.co.uk/api/v1/email with your Sovereign Mail key, and rename the fields. The guides below map every field for each provider.
  3. 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.
  4. 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
PostmarkSovereign Mail
POST https://api.postmarkapp.com/emailPOST /api/v1/email
X-Postmark-Server-Token: …Authorization: Bearer smk_live_…
Fromfrom — a bare address (no "Name <…>" yet)
To — up to 50, comma-separatedto — one address; use batch for several
Subject · TextBody · HtmlBodysubject · text · html
/email/withTemplate — TemplateId + TemplateModel/email — templateId + variables (string values)
/email/batch — an array, up to 500/email/batch — { "messages": [ … ] }, up to 500
MessageStreamNot needed — transactional only
TrackOpens · TrackLinksNot available — we never track recipients
Bounce & spam-complaint webhooksApplied 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
MailgunSovereign 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_KEYAuthorization: Bearer smk_live_…
Form fields (-F)A JSON body
fromfrom — a bare address (no "Name <…>" yet)
to — repeatable, up to 1,000 with recipient-variablesto — one address; use batch (up to 500) for several
subject · text · htmlsubject · text · html
template + t:variables (or h:X-Mailgun-Variables)templateId + variables (string values)
o:testmode=yesA test key (smk_test_…): checked and recorded, never delivered
o:tracking · o:tracking-clicks · o:tracking-opensNot available — we never track recipients
Bounce & complaint webhooksApplied 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
MailerSendSovereign Mail
POST https://api.mailersend.com/v1/emailPOST /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 50to — one address string; use batch for several
subject · text · htmlsubject · text · html
template_id + personalization [{ email, data }]templateId + variables (string values)
POST /v1/bulk-email — up to 500, processed laterPOST /api/v1/email/batch — up to 500, answered at once
cc · bcc · reply_to · attachmentsNot yet supported
Open & click tracking (domain settings)Not available — we never track recipients
Bounce & spam-complaint webhooksApplied 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
MailjetSovereign Mail
POST https://api.mailjet.com/v3.1/sendPOST /api/v1/email (one) or /api/v1/email/batch (several)
Basic auth — API key : secret keyAuthorization: Bearer smk_live_… (one key, no secret pair)
Messages: [ … ] — up to 50 per callbatch — { "messages": [ … ] }, up to 500
From — { Email, Name }from — a bare address string (no name yet)
To — [{ Email, Name }]to — one address string
Subject · TextPart · HTMLPartsubject · text · html
TemplateID + TemplateLanguage + VariablestemplateId + variables (string values)
SandboxMode: trueA test key (smk_test_…): checked and recorded, never delivered
TrackOpens · TrackClicksNot available — we never track recipients
Event API — bounce, spam, blockedApplied 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
SMTP2GOSovereign Mail
POST https://api.smtp2go.com/v3/email/sendPOST /api/v1/email
X-Smtp2go-Api-Key: api-… (or api_key in the body)Authorization: Bearer smk_live_…
sender — "Name <address>" allowedfrom — a bare address (no name yet)
to — an array, up to 100to — one address; use batch for several
subject · text_body · html_bodysubject · text · html
template_id + template_datatemplateId + variables (string values)
cc · bcc · custom_headers · attachmentsNot yet supported
Open & click trackingNot available — we never track recipients
Bounce & spam webhooksApplied 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
SendGridSovereign Mail
POST https://api.sendgrid.com/v3/mail/sendPOST /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_datatemplateId + variables (string values)
mail_settings.sandbox_mode.enableA test key (smk_test_…): checked and recorded, never delivered
tracking_settings — opens, clicks, subscriptionsNot available — we never track recipients
Event webhook — bounce, dropped, spamreportApplied 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 SESSovereign Mail
SESv2 SendEmail (SDK or AWS CLI), signed with IAM credentialsPOST /api/v1/email with Authorization: Bearer smk_live_…
FromEmailAddressfrom — a bare address
Destination.ToAddressesto — one address; use batch for several
Content.Simple — Subject.Data · Body.Text.Data · Body.Html.Datasubject · text · html
Content.Template — TemplateName + TemplateData (a JSON string)templateId + variables (an object of strings)
SendBulkEmail — up to 50 destinationsPOST /api/v1/email/batch — up to 500
Mailbox simulator (success@simulator.amazonses.com)A test key (smk_test_…): checked and recorded, never delivered
ConfigurationSetNameNot needed
SNS / EventBridge bounce & complaint notificationsApplied 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
ResendSovereign Mail
POST https://api.resend.com/emailsPOST /api/v1/email
Authorization: Bearer re_…Authorization: Bearer smk_live_…
from — "Name <address>" allowedfrom — a bare address (no name yet)
to — a string or an array, up to 50to — one address string; use batch for several
subject · text · htmlsubject · text · html
react (Node SDK)html — render the component to HTML first (@react-email/render)
POST /emails/batch — up to 100POST /api/v1/email/batch — up to 500
delivered@resend.dev test addressesA test key (smk_test_…): checked and recorded, never delivered
Webhooks — email.bounced, email.complainedApplied 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

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