Developer guideMigration
Migrate from Mailgun
Guide to move from Mailgun to Couryo. Mapping of the form-based send (from, to, o:tag, v:, h:, o:deliverytime, template and recipient-variables) to JSON, of the webhook events, of suppressions and receiving routes, and the DNS change.
Mailgun takes the send as a form (multipart/form-data), with options under prefixes like o:, h: and v:. Couryo takes JSON, with one field for each thing. Follow the safe migration order and use this guide to translate each part.
The send call#
curl -s --user "api:$MAILGUN_API_KEY" \
https://api.mailgun.net/v3/example.com/messages \
-F from="Shop <orders@example.com>" \
-F to="ana@example.com" \
-F subject="Order 1042 confirmed" \
-F text="Thanks, Ana!" \
-F html="<p>Thanks, Ana!</p>" \
-F o:tag="order" \
-F v:order_id="1042"curl https://api.couryo.com/v1/emails \
-H "Authorization: Bearer $COURYO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042-confirmation" \
-d '{
"from": "Shop <orders@example.com>",
"to": "ana@example.com",
"subject": "Order 1042 confirmed",
"text": "Thanks, Ana!",
"html": "<p>Thanks, Ana!</p>",
"tags": { "category": "order" },
"metadata": { "order_id": "1042" }
}'At Mailgun, the domain goes in the URL and authentication is api: plus the key. At Couryo, the URL is always the same, the key goes in Authorization: Bearer and the domain comes from from. The answer is 202 { "id": "msg_...", "status": "queued" }.
Fields#
| Mailgun | Couryo | Note |
|---|---|---|
from, to, cc, bcc, subject, text, html |
the same names | as JSON; to takes a string or a list, up to 50 recipients counting cc and bcc |
h:Reply-To |
reply_to |
|
h:X-Anything |
headers: { "X-Anything": "..." } |
|
o:tag (many) |
tags |
key and value, e.g. { "category": "order" } |
v:key or h:X-Mailgun-Variables |
metadata |
back in every webhook (at Mailgun, user-variables) |
o:deliverytime (RFC 2822) |
scheduled_at (ISO 8601, up to 30 days) |
reschedule and cancel |
o:testmode |
a ck_test_ key |
captures without delivering, with simulated events |
o:tracking, o:tracking-opens, o:tracking-clicks |
project tracking | on for marketing, off for transactional by default |
attachment, inline (files) |
attachments[] with content in Base64; inline images with content_id |
|
template + t:variables |
template + variables |
recreate the template in Couryo (see below) |
recipient-variables (batch sending) |
POST /v1/emails/batch |
one item per recipient, up to 100 per call |
o:require-tls, o:dkim |
always | TLS and your domain's DKIM on every send |
Templates#
Mailgun templates use Handlebars. At Couryo, variables are {{name}} and {{order.total}}, with an optional fallback ({{name|there}}); blocks like {{#if}} and {{#each}} do not exist. Recreate each template under Templates (HTML, MJML or the visual editor) and send template with the name or the id and variables.
Events and webhooks#
| Mailgun | Couryo |
|---|---|
accepted |
accepted and queued |
delivered |
delivered |
failed with severity: temporary |
deferred |
failed with severity: permanent |
bounced (or suppressed, when the address was already suppressed) |
complained |
complained |
opened |
opened |
clicked |
clicked |
unsubscribed |
no event: the address goes to the marketing suppression list (GET /v1/suppressions?reason=unsubscribe) |
Mailgun signs with timestamp, token and signature inside the body. Couryo signs following Standard Webhooks, in the webhook-id, webhook-timestamp and webhook-signature headers (HMAC-SHA256), and retries for up to 3 days. A Couryo webhook takes the events you choose, for every domain of the project.
Suppressions, lists and receiving#
- Export the Bounces and Complaints lists from Mailgun and add them to Couryo with
stream: "all"; Unsubscribes go withstream: "marketing". The loop is in copy the suppressions. - Mailing lists: export the members as CSV and import them as contacts, with the consent confirmation. Each list becomes a tag or a topic.
- Receiving routes: at Couryo, inbound (Pro plans and above) receives on a subdomain, like
reply.example.com, and delivers each message to theemail.receivedwebhook.
DNS#
Mailgun asks for an include: in the domain SPF, a DKIM key with its own selector, the tracking CNAME (email.) and, to receive, MX records pointing to it. Couryo uses couryo1._domainkey, couryo2._domainkey and bounces, and asks for nothing in the root domain SPF, so both stay published during the migration.
- Publish Couryo's records and wait for the domain to be verified.
- Keep the DMARC record you already have; to get the reports in the deliverability agent, add
mailto:reports@dmarc.couryo.comtorua. - If you receive email through Mailgun (routes), do not change the root domain MX before the messages have somewhere else to go. Couryo inbound uses its own subdomain.
- Once all traffic is on Couryo and 7 days have passed, delete the Mailgun DKIM key, the tracking CNAME and its SPF
include:(if nothing else uses it).