Skip to content
couryo

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.

View as Markdown

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#

Mailgun: POST https://api.mailgun.net/v3/example.com/messages
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"
Couryo: POST https://api.couryo.com/v1/emails
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 with stream: "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 the email.received webhook.

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.

  1. Publish Couryo's records and wait for the domain to be verified.
  2. Keep the DMARC record you already have; to get the reports in the deliverability agent, add mailto:reports@dmarc.couryo.com to rua.
  3. 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.
  4. 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).