Integrating · Published 2026-11-07 · Updated 2026-11-07 · AgentiSend
Migrating from Mailgun, Postmark, SendGrid or Amazon SES
How a Mailgun, Postmark, SendGrid or Amazon SES send and its webhooks map onto POST /emails, field by field, and what is refused on day one.
To move from Mailgun, Postmark, SendGrid or Amazon SES, send the same message to POST /emails with from, to, subject, html and text, branch on our error code, and point your webhooks at our signed events. The four moves share every step except the field names, so the steps are written once and each provider's field map has its own section. AgentiSend does not send through Amazon SES; that section is the field map for a move off that API. Moving from Resend has its own guide, because our API accepts Resend's requests as they are.
The request shape is the same
Each of the four takes a sender, recipients, a subject and a body, and so does ours. The full shape is on the POST /emails page.
- Mailgun. Mailgun's HTTP send takes form fields
from,to,subject,textandhtml. - Postmark. Postmark's send is
POST /emailwithFrom,To,Subject,HtmlBody,TextBodyandMessageStream. The names differ; the job is the same single message. - SendGrid. SendGrid's mail send takes a
fromobject whoseemailis required, asubject, andpersonalizations. - Amazon SES. Amazon SES
SendEmailtakesFromEmailAddress,DestinationandContent(withSubject, HTML and text).
Headers
headers takes a map of name to value. From, To, Subject and Date are refused in headers. Message-ID passes through when from is on a domain you have verified here, as one message ID not on agentisend.com. List-Unsubscribe and List-Unsubscribe-Post pass through when from is on a domain you have verified here.
Error names resolve
Error names from Mailgun, Postmark, SendGrid and Amazon SES are not aliases here. Branch on our code. Every refusal carries message, fix and docs_url. The catalogue is at /docs/errors.
Payload map
Each table was read from that provider's own send reference on 2026-09-28; the URL is under each heading.
Mailgun
Fetched from Mailgun's HTTP send page on 2026-09-28 (https://documentation.mailgun.com/docs/mailgun/user-manual/sending-messages/send-http).
| Mailgun | AgentiSend |
|---|---|
from | from |
to | to |
subject | subject |
html | html |
text | text |
Postmark
Fetched from Postmark's send guide on 2026-09-28 (https://postmarkapp.com/developer/user-guide/send-email-with-api).
| Postmark | AgentiSend |
|---|---|
From | from |
To | to |
Subject | subject |
HtmlBody | html |
TextBody | text |
MessageStream | No equivalent field. Transactional and broadcast sends are different calls. |
| Idempotency key | Not on their API. Send Idempotency-Key here; it is remembered for 7 days. |
SendGrid
Fetched from the mail-send reference on 2026-09-28 (https://www.twilio.com/docs/sendgrid/api-reference/mail-send/mail-send). That page does not document an idempotency key.
| SendGrid | AgentiSend |
|---|---|
from.email | from |
personalizations | to, and one message per personalization if you were batching inside one call |
subject | subject |
| Idempotency key | Not on that page. Send Idempotency-Key here; it is remembered for 7 days. |
Amazon SES
Fetched from the SendEmail reference on 2026-09-28 (https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html). That page does not document an idempotency key.
| Amazon SES | AgentiSend |
|---|---|
FromEmailAddress | from |
Destination | to |
Content subject | subject |
Content HTML | html |
Content text | text |
| Account pause | PUT /v2/email/account/sending with SendingEnabled false. Here that job is POST /limits/kill-all. |
Webhook events
Our webhooks are signed; see Webhooks for the scheme. Of the four, only Postmark's overview, fetched 2026-09-28, names its event types: Delivery, Bounce, Spam complaint, Open, Click, Subscription change and Inbound. It also says Postmark does not sign webhooks with HMAC. Mailgun's securing-webhooks page describes an HMAC-SHA256 signature over timestamp and token and lists no event names. The SendGrid and Amazon SES pages fetched that day name no webhook event types either; Amazon SES publishes delivery events to a destination you configure. So the table maps only what a provider named, and does not guess a name for the rest.
| Our event | Postmark's name |
|---|---|
email.delivered | Delivery |
email.bounced | Bounce |
email.complained | Spam complaint |
email.opened | Open tracking |
email.clicked | Click |
contact.unsubscribed | Subscription change |
email.received | Inbound webhook. Receiving is open here: turn it on for a verified domain and publish the MX record its page shows. |
email.received.held | No Postmark name. Ours: mail labeled review on a domain that holds suspect mail; the arrival webhook waits until a person releases it. |
inbound.refused_by_rule | No Postmark name. Ours: mail for a domain was not stored because a sender rule refused it. |
Every event type on our webhooks, 51 in all: email.queued, email.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.opened, email.clicked, email.failed, email.scheduled, email.canceled, email.suppressed, domain.created, domain.verified, domain.failed, domain.updated, domain.deleted, domain.blocklisted, suppression.added, suppression.removed, limit.warning, limit.exceeded, agent.approval_requested, agent.approved, agent.rejected, agent.killed, contact.created, contact.updated, contact.deleted, segment.joined, segment.left, segment.created, segment.updated, segment.deleted, broadcast.scheduled, broadcast.sent, broadcast.updated, automation.triggered, email.received, email.received.held, inbound.refused_by_rule, topic.created, topic.updated, topic.deleted, contact.subscribed, contact.unsubscribed, trust.warning, trust.throttled, trust.paused, trust.appealed, trust.restored. Each one's payload is on the events page.
What will refuse on day one
- Receiving is open: turn it on for a verified domain, publish the MX record its page shows, and mail to that domain arrives on your account. A migration that depends on receiving mail at your domain waits on that.
- Keeping customer data in the EU is not offered. Data is processed in the United States.
- There is no business associate agreement for health data.
- A key is created with a send budget. A key that sends past the budget is refused with
agent_budget_exceededrather than invoiced.
The compatibility client, when the import line is all you want to change
That client mirrors one provider's SDK (agentisend/compat-resend). It does not mirror Mailgun, Postmark, SendGrid or Amazon SES. Call POST /emails with from, to, subject, html and text.
Contacts
POST /contacts accepts first_name, last_name, unsubscribed and properties. unsubscribed: true records that the person asked not to receive marketing mail. GET, PATCH and DELETE /contacts/{id} take the contact id or the email address. None of the provider pages fetched on 2026-09-28 documents a contact-import field map: Mailgun (pricing, webhook signing and the HTTP send); Postmark (sending and webhooks); SendGrid (pricing, the mail-send reference and the docs MCP server); Amazon SES (pricing, SendEmail and the account-sending call). So this guide does not invent one. Add each person with POST /contacts. An API key cannot lift an unsubscribe.
The bounce return path
A domain that already sends from somewhere else often has an MX record on send. Creating it here without a return-path label then uses bounce instead of refusing the domain. To choose the label, pass custom_return_path on POST /domains.
What differs
Keys have budgets, and the budget is enforced. Set the budget to your real volume before you cut over.
An EU data region is not part of the move. If the old account uses one, that part stays on the old provider.
What you gain in the move
- Per-key budgets and one kill switch for the account, on Budgets and the kill switch.
- An approval queue for a send the loop guard flags, or one an agent asks about with request_approval, on Approvals.
- Signed webhooks, a replay call, and a dead-letter list, on Webhooks.
Order of operations
- Add and verify your domains here while the old provider still sends.
- Create keys with budgets, one per sender, and point webhooks at the new signing scheme.
- Move a low-volume stream first.
- Move the rest, and keep the old keys alive until you have a week of clean data.