Sendara mirrors the surface you already use in Mailgun: an API key, a verified sending domain, a send call shaped like { from, to, subject, html }, an event timeline, and signed webhooks. The migration is mostly mechanical. This page maps each piece. For the why and the overall strategy, start with Switch to Sendara.
Why switch
You send from your own domain with your own DKIM, SPF, and DMARC, so your reputation moves with you. Switching providers neither resets nor repairs it. What changes is the developer experience around the send: idempotent requests, a full per-message event timeline, an open suppression model you import and control, and pricing that does not penalize growth. It is a clean API and an honest billing model. It is not a deliverability cure.
Concept map
Here is how the Mailgunprimitives you rely on line up with Sendara's.
| Mailgun | Sendara | Notes |
|---|---|---|
| Private API key | API key (sk_live_… / sk_test_…) | Create a key in the dashboard. A test key simulates the delivery, and Sendara charges nothing for it. |
| Sending domain | Domain | Mailgun takes the domain as the first argument to messages.create. Sendara reads the domain from your verified from address. |
| from | from | The shape is the same. Give a bare address, or 'Acme <[email protected]>'. The SDK sends it as metadata.from_email. |
| mg.messages.create(domain, …) | sendara.emails.send | Remove the domain argument. The { from, to, subject, html } fields stay the same. |
| Event webhooks / Events API | Webhook events | Subscribe at POST /v1/webhooks. Sendara signs each delivery with HMAC-SHA256. |
| Suppressions (bounces/unsubscribes/complaints) | Suppression list | Import bounces and complaints with state suppressed; import marketing opt-outs with state unsubscribed. |
| Mailing lists | Contact lists / Broadcasts | Call sendara.contacts.lists.* and sendara.broadcasts.*. |
Swap the env vars & SDK
Install the Sendara SDK and point your code at a Sendara API key. Create one in the dashboard. Test keys (sk_test_…) simulate delivery and never bill, so you can wire everything up before sending a single real message.
npm install sendara| Mailgun | Sendara |
|---|---|
| MAILGUN_API_KEY | SENDARA_API_KEY |
Before & after: sending
The send call keeps the same shape. Set the sender to an address on your verified domain with the SDK from parameter. On the wire, that value travels as metadata.from_email. Each SDK call generates an idempotency key. Automatic in-call retries reuse it; a retry started by your application must pass and reuse the same explicit key.
import Mailgun from "mailgun.js";
import FormData from "form-data";
const mailgun = new Mailgun(FormData);
const mg = mailgun.client({ username: "api", key: process.env.MAILGUN_API_KEY! });
await mg.messages.create("mg.acme.com", {
from: "Acme <[email protected]>",
to: ["[email protected]"],
subject: "Welcome to Acme",
html: "<strong>It works!</strong>",
});from: "Acme <[email protected]>" (name first, address in angle brackets). See sender name.DNS & domain verification
Verify your domain in Sendara with POST /v1/domains. Publish the 6 records that Sendara returns: 3 DKIM CNAMEs, an MX record and an SPF record on the MAIL FROM subdomain, and a DMARC record. Keep your Mailgun sending domain live, so that you can run both providers together. Move your traffic to Sendara when you are ready.
curl https://api.sendara.dev/v1/domains \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "domain": "mail.acme.com" }'Import your suppression list
Export your Mailgun suppressions. Import bounces and complaints with state suppressed and opt-outs with state unsubscribed. Sendara then suppresses every new hard bounce and complaint automatically.
Import in bulk with POST /v1/suppressions/import. It is an admin-scoped, idempotent endpoint that upserts each entry and returns { imported, skipped }, so re-running the import is safe. Use state: "suppressed" (the default) for hard bounces, complaints, and manual hard blocks; use state: "unsubscribed" for marketing opt-outs so transactional mail remains allowed. The import does not rewrite a matching contact record. After cutover, hard bounces and spam complaints are added to the list automatically. You can also manage individual entries with POST/DELETE /v1/suppressions.
curl https://api.sendara.dev/v1/suppressions/import \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"entries": [
{ "email": "[email protected]", "reason": "hard_bounce", "state": "suppressed" },
{ "email": "[email protected]", "reason": "provider_unsubscribe", "state": "unsubscribed" }
]
}'suppressed and never enqueued or billed. That is the safety net that stops a fresh provider from re-mailing the addresses your old one already learned to avoid.Map your webhook events
Mailgun wraps each event under `event-data`, with an `event` field. A `failed` event carries a severity of permanent or temporary. Map the `event` value to the matching Sendara event type. Sendara signs an HMAC-SHA256 of `${timestamp}.${rawBody}` in hex. Check the signature with verifyWebhook().
| Mailgun event | Sendara event |
|---|---|
| delivered | delivered |
| failed | bounced |
| complained | complained |
| opened | opened |
| clicked | clicked |
| unsubscribed | unsubscribed suppression stateSendara fires no webhook event. Import each Mailgun opt-out with POST /v1/suppressions/import and state unsubscribed. This blocks marketing but still permits transactional email; it does not rewrite the contact record. |
Subscribe with POST /v1/webhooks, then verify each delivery with verifyWebhook() from the SDK. See Webhooks and Events & the message timeline for the full payload schemas and delivery guarantees.
Cutover checklist
Run this in order. You can stop at any step and stay there as long as you like. Nothing is irreversible until you decommission Mailgun.
- Verify the domain in Sendara and publish the DKIM, SPF, DMARC, and MAIL FROM records, leaving Mailgun verified.
- Import your suppression list via
POST /v1/suppressions/importso no known-bad address is ever mailed. - Wire up the SDK and webhooks against a test key, remapping the events from the table above.
- Mirror a low-stakes stream through Sendara and watch it in the message log. Confirm each send with
GET /v1/messages?idempotency_key=…and compare delivered, bounced, and complained rates against Mailgun. - Ramp traffic up as the numbers hold. There are no IPs to warm when you stay on the same domain, so move as fast as your confidence allows.
- Cut over the remainder, keep Mailgun verified for a week as a fallback, then decommission it.
idempotency_key you already pass on each send is also your status handle. Behind a feature flag, you can replay a request against Sendara without double-sending, then look it up with GET /v1/messages?idempotency_key=… to confirm it delivered. See Message lifecycle.