Access is invite-only during beta. Once invited, create a key to personalize these examples. Production sending requires a separate review.

Switching from Mailgun

Migrate Mailgun sending, metadata, delivery webhooks, and inbound Routes to PostShiba with a tested rollback path.

Keep sender addresses, content, and HTTP or SMTP clients. Authentication, credentials, requests, webhook signatures, and inbound routing change. Keep Mailgun ready until the rollback window closes.

Run both providers during the outbound migration

Mailgun and PostShiba can run together. Use a feature flag keyed by tenant or sender. Each production message must use exactly one provider. Do not dual-send. PostShiba uses shared IPs, so this is an integration ramp, not dedicated-IP warming.

Start with this adjustable 30-day ramp:

  1. Days 1 to 3: run controlled tests. Pending accounts may send only to team members or live inboxes. After approval, test external mailboxes.
  2. Days 4 to 10: route 10% of new outbound traffic to PostShiba.
  3. Days 11 to 17: increase the cohort to 25%.
  4. Days 18 to 24: increase the cohort to 50%.
  5. Days 25 to 30: move to 100% after the 50% stage stays healthy.

Hold a stage when traffic is low. Before each increase, confirm REST or SMTP acceptance, final delivery events, unique_args, webhook verification, and stable bounce, deferral, and complaint rates.

Keep Mailgun credentials, webhooks, Routes, and DNS for rollback. Outbound authentication records can coexist only when their hostnames or selectors differ. If they collide, use a separate PostShiba sending subdomain during the ramp.

Cut inbound receiving over separately. MX records cannot split traffic by percentage, and equal-priority records do not copy messages. Test a hosted inbox or new subdomain before production MX.

Inventory production first

Record these before you change traffic:

  • Mailgun domains, regional base URLs, and callers of POST /v3/{domain}/messages.
  • Primary or domain sending keys and per-domain SMTP credentials.
  • Each v: variable, X-Mailgun-Variables header, tag, delivery webhook, expected event, and signing key.
  • Inbound recipient domains, MX TTLs, and Route filters, priorities, and actions.
  • Mailing-list, stored-template, and sending-IP-pool dependencies.

You need control of sending and receiving DNS, a public HTTPS webhook endpoint, and a PostShiba team.

Map the concepts

Mailgun PostShiba
Account and API key Team and API key
Domain in the Messages API path Verified from domain
Subaccount Optional tenant
Per-domain SMTP credential Cluster-and-tenant SMTP credential
v: variables or X-Mailgun-Variables unique_args or X-Capsule-Unique-Args
Webhook and signing key Webhook endpoint and secret
Route forward() or store() Inbox webhook or stored-message polling
Lists, stored templates, and IP pools Hosted Liquid templates for substitution. Lists and IP pools stay application-owned

Set up PostShiba in order

  1. Create a shared cluster and wait for sending_ready: true. Customer IP pools are unsupported. Add extra shared or dedicated sending IPs from Network when you need more.
  2. Request production approval from the dashboard. Pending teams may send only to team members and live inboxes.
  3. Create an API key. Keep it server-side.
  4. Map Mailgun subaccounts to tenants before each domain. A tenant scopes resources and suppressions, not Mailgun users, reports, quotas, or billing. Otherwise use the default tenant.
  5. Add and verify the sending domain. Publish its DKIM and return-path CNAME records.
  6. Confirm an SMTP credential for each cluster and tenant pair. REST sends also need one.

Bring your suppressions

Import Mailgun bounces, unsubscribes, and complaints before you send. A missed row is a complaint on the new cluster.

Export each list from the Mailgun Suppressions pages, or pull bounces, unsubscribes, and complaints from the Mailgun API. Map subaccount lists to the matching tenant.

Upload the CSV on Suppressions, or post the addresses:

import-suppressions.sh
1 curl -sS --request POST \
2 --url https://app.postshiba.com/api/v1/teams/KjkAJW/suppressions/import \
3 --header "Authorization: Bearer $POSTSHIBA_API_KEY" \
4 --header "Content-Type: application/json" \
5 --data '{
6 "emails": ["unsubscribed@example.net", "bounced@example.net"],
7 "tenant_id": "WbLcFd"
8 }'

Use a column named email, address, or recipient, or one address per line. Existing rows are skipped. Max 10,000 addresses per request. See Suppressions.

Replace the Messages API

Mailgun posts form data to a domain URL with Basic api auth. PostShiba posts JSON to a fixed endpoint with a Bearer token. The from domain and optional tenant select the identity.

Send one canary with POST /api/v1/emails:

send-canary.sh
1 curl -sS --request POST \
2 --url https://app.postshiba.com/api/v1/emails \
3 --header "Authorization: Bearer $POSTSHIBA_API_KEY" \
4 --header "Content-Type: application/json" \
5 --header "X-Capsule-Cluster-Id: $POSTSHIBA_CLUSTER_ID" \
6 --data '{
7 "from": "Acme Receipts <receipts@mail.example.com>",
8 "to": ["migration-canary@example.net"],
9 "subject": "Order ord_123 confirmed",
10 "text": "Thanks for your order.",
11 "html": "<p>Thanks for your order.</p>",
12 "tenant": "store-42",
13 "unique_args": {
14 "order_id": "ord_123",
15 "mail_type": "receipt"
16 }
17 }'

Replace placeholders and remove tenant for the default tenant. Require HTTP 201 with queued: true, then store message_id. Queued does not mean delivered.

POST /api/v1/emails has no idempotency key. For ambiguous-response retries, use cluster-specific POST /sends with Idempotency-Key, or reconcile the first attempt before retrying.

For SMTP, use the cluster's returned smtp_endpoint on port 587 with STARTTLS and AUTH PLAIN. Issue an smtp_... credential per cluster and tenant. AUTH LOGIN fails. The tenant must own the verified from domain.

Move metadata and unsupported features

Move a Mailgun v:name tracking value to unique_args.name. If it feeds a stored template, port it to a PostShiba template or render text and html in your application. SMTP clients use JSON in X-Capsule-Unique-Args. Limits are 32 scalars, 64-byte keys, and 256-byte values. Do not include secrets.

Move mailing-list membership and consent into your application. If you require an IP pool, stop because PostShiba cannot preserve that behavior. See Network for dedicated egress IPs.

Before the first production cohort, export hard bounces, complaints, and global unsubscribes into the matching tenant suppressions. Do not import temporary failures as permanent suppressions. Keep tag-specific preferences in your application. While both providers run, write new complaints and unsubscribes to the same consent source used by both send paths.

Replace delivery webhooks

Create a PostShiba webhook endpoint and store its secret. The body is a SendGrid-shaped JSON array with processed, delivered, deferred, bounce, dropped, spamreport, or unsubscribe. PostShiba does not emit open or click events.

Map Mailgun events. accepted becomes processed; rejected becomes dropped; temporary and permanent failed become deferred and bounce; complained becomes spamreport; and unsubscribed becomes unsubscribe. delivered stays delivered. Remove dependencies on opened, clicked, and stored.

Give PostShiba its own verifier. Unlike Mailgun's timestamp + token, PostShiba signs {timestamp}.{raw body} with the endpoint secret. Verify X-Capsule-Timestamp and X-Capsule-Signature before parsing.

Replace inbound Routes

Create a PostShiba inbox per local part, or use a domain catch-all. Move Route logic into your application. Choose a signed receiving webhook, or poll stored messages before expires_at.

A domain-bound inbox receives at {local_part}@inbound.{sending_domain}. Publish MX only on that host. If the Mailgun host differs, test a hosted inbox or new subdomain, then update addresses or aliases.

Never use equal-priority Mailgun and PostShiba MX records to split traffic. Sending servers choose one target rather than copying the message, so delivery becomes unpredictable.

Validate, cut over, and roll back

Follow the outbound ramp above. Test inbound signatures, bodies, attachments, and polling on a separate inbox. Lower the host's TTL one old TTL in advance. If Mailgun already receives on the exact PostShiba inbound hostname, replace that MX with the inbox target. Otherwise keep the Mailgun MX and move Reply-To values or aliases to the new PostShiba address. Keep old Routes active for late deliveries.

To roll back outbound, send only new messages through Mailgun. Do not replay a request that returned queued: true without delivery evidence. Keep both event receivers running.

To roll back inbound, restore Mailgun as the only preferred MX target. Keep both inbound handlers alive through the old TTL for cached senders.

Cutover checklist

  • Production approval is complete.
  • The cluster is sending-ready, and each tenant has a credential.
  • DKIM and return-path verification pass.
  • Canary sends return queued: true and final delivery events.
  • unique_args reach the webhook.
  • Hard bounces, complaints, and global unsubscribes are present in the matching tenant.
  • Templates, lists, and IP-pool dependencies have replacements.
  • Inbound webhook or polling works before the MX change.
  • Bounce, unsubscribe, and complaint lists are imported per tenant.
  • Mailgun credentials, webhooks, Routes, and DNS remain available for rollback.

About

PostShiba is the email platform that powers Bento behind the scenes. You can build your own products, like Bento, on top of it.

© 2026 PostShiba by Backpack Internet Pty. Ltd. All rights reserved.

The same policies that govern Bento are applied to PostShiba Privacy | Terms | Security