Migrate from Mailgun to an EU-hosted email API
Postscale keeps the developer shape familiar while combining transactional sending, inbound routing, masked aliases, DMARC reporting, and warming under one account.
Migration scope
- Convert Messages API calls from multipart form data to JSON.
- Replace Routes with aliases, inbound rules, and webhooks.
- Move bounces, complaints, and unsubscribes before production traffic.
- Use SMTP relay where legacy systems cannot switch to REST immediately.
What changes when you move
| Mailgun | Postscale | Migration note |
|---|---|---|
| Messages API | POST /v1/send | Map html/text fields to html_body/text_body and move o:tag values to tags. |
| Routes | Aliases and inbound rules | Use per-address aliases plus domain-level rules for sender, subject, and authentication conditions. |
| Webhooks | Postscale webhooks | Create one endpoint with selected event types and verify HMAC signatures. |
| Template Storage | Postscale templates | Import Handlebars-style HTML and send using template slug or ID. |
| Suppressions | Suppression API | Export bounces, complaints, and unsubscribes separately so reasons remain auditable. |
Cut over in controlled steps
Mirror DNS
Add Postscale verification, DKIM, return-path, and optional MX records while the old provider keeps sending.
Convert code
Swap API credentials and normalize the send payload. Keep the old provider path behind a feature flag for rollback.
Move state
Import suppressions and template HTML, then configure delivery, bounce, complaint, and inbound webhooks.
Ramp traffic
Start at 10 percent, watch bounces and complaints by ISP, then increase volume as warming limits allow.
Send call conversion
const form = new FormData();
form.append("from", "Acme <alerts@example.com>");
form.append("to", "user@example.com");
form.append("subject", "Welcome");
form.append("html", "<p>Hello from Acme</p>");
form.append("o:tag", "onboarding");
form.append("v:user_id", "usr_123");
await fetch("https://api.mailgun.net/v3/example.com/messages", {
method: "POST",
headers: { Authorization: `Basic ${mailgunToken}` },
body: form,
});await fetch("https://api.postscale.io/v1/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.POSTSCALE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "Acme <alerts@example.com>",
to: ["user@example.com"],
subject: "Welcome",
html_body: "<p>Hello from Acme</p>",
tags: ["onboarding"],
metadata: { user_id: "usr_123" },
}),
});Production checklist
Frequently asked questions
What replaces Mailgun Routes?
Use Postscale aliases for address-level routing and inbound rules for conditional routing by sender, subject, headers, authentication results, or target webhook.
Can I keep SMTP during the migration?
Yes. Postscale supports SMTP relay, so legacy apps can change host, username, and password before you rewrite REST calls.
How should suppressions move from Mailgun?
Export bounces, complaints, unsubscribes, and manual suppressions as separate CSV files, dedupe them, then import before sending through Postscale.