7 Receive Email APIs Compared: Webhooks, Mailboxes, and Pricing (2026)
Compare 7 receive email APIs by delivery model, webhook payload, whether mail is stored, size limits, plan gating, and the best fit for each job.
- engineering
- inbound-email
- webhooks
- comparison
- email-api

TL;DR
A receive email API is an MX record plus a delivery path into your code. The real choice is the delivery model: a parsed webhook, raw MIME in storage, an edge function, or a stored mailbox with threads. The comparison covers seven options by payload, retention, size limits, and which plan turns inbound on.
Your app needs email that arrives as data, not as a folder someone opens by hand. A user replies to a ticket. A form lands in support@. An agent waits for a confirmation. In each case the message has to reach code first.
A provider accepts the message on the public internet after you point a hostname at their servers. What you get next is a webhook, a storage object, an edge function, or a mailbox. Pick the delivery model first. The audience here is app developers choosing that model, including plan gating and size limits, not only reply parsing for agents.
What is a receive email API?
A receive email API is an MX record pointed at a provider plus a delivery path into your app. The seven options compared here are Postmark, Mailgun, SendGrid, Amazon SES, Cloudflare Email Workers, Nylas, and AgentMail.
The MX step is ordinary DNS. You publish a mail exchanger for a hostname you control, and the provider's servers accept SMTP for that hostname. What differs is the next hop.
Some products parse the message and POST fields to your HTTPS endpoint. Some write the untouched MIME object into object storage and tell you the key. Some invoke your function while the SMTP session is still open. Some store a durable inbox and treat the webhook as a notification that something new is ready to fetch or already attached to a thread.
Four delivery models cover almost every listing you will see. A parsed webhook POSTs JSON or form fields to your URL and often keeps little or nothing afterward. Raw MIME to storage drops the full message in S3 or a similar bucket and leaves parsing to you. An edge Worker runs your code at SMTP receipt time with forward, reply, or reject. A stored mailbox keeps the message, groups replies into threads, and notifies you with an event.
Postmark, Mailgun, and SendGrid sit in the parsed webhook lane. Amazon SES is the storage and Lambda lane. Cloudflare Email Workers are the edge lane. AgentMail and Nylas Agent Accounts are the mailbox lane. Nylas connected accounts are a fifth job: reading a Gmail or Outlook mailbox a person already owns.
If you only need a one way pipe into a queue, a webhook is enough. If the next message in the conversation has to land on the same object, you need a mailbox. Mixing the two without noticing is how teams end up rebuilding threads on top of a pipe that never stored them.
Receive email APIs compared
The table below ranks the seven options by what happens after the message is accepted, not by brand familiarity. Best for is the job each one actually fits.
| Provider | Delivery model | Payload | Stored after delivery | Size limit | Inbound on which plan | Best for |
|---|---|---|---|---|---|---|
| Postmark | Parsed webhook | JSON, including StrippedTextReply | 45 day default, up to 365 on Pro | 35 MB attachments cumulative | Pro $16.50/mo and Platform | JSON webhooks and stripped plain text replies |
| Mailgun | Routes: forward, store, stop | Form encoded or multipart; stripped text fields | store() up to 3 days | Provider docs; large attachments favor store() | Free 1 route, Basic 5, Foundation full routing | Teams already on Mailgun Routes |
| SendGrid | Inbound Parse webhook | Multipart form; optional raw MIME | No mailbox; POSTs to your URL | 30 MB total message | Inbound Parse on the Email API | Existing SendGrid multipart pipelines |
| Amazon SES | Receipt rules to S3, SNS, Lambda | Raw MIME; you parse | S3 if you choose that action | 40 MB via S3, 150 KB via SNS | Receiving where the Region supports it; billed per email | AWS teams that want to own the stack |
| Cloudflare Email Workers | email() handler at the edge | Raw stream; parse with postal mime | Nothing unless you write it | 25 MiB inbound | Email Routing with a Worker destination | Edge logic already on Cloudflare |
| Nylas | Stored mailbox or connected account | message.created webhook, then API | Yes; Agent Accounts and grants | Plan storage and bandwidth caps | Free 3 Agent Accounts; Essentials $15 with 10 | Hosted agents plus connected human mailboxes |
| AgentMail | Stored inbox with threads | message.received over webhook or WebSocket | Yes; threads created automatically | Webhook payload capped at 1 MB; fetch full body if omitted | Free 3 inboxes; Developer $20 with 10 | Per agent inboxes with reply in thread |
Parsed webhooks win when you already own conversation state. Edge Workers win when the decision is accept, reject, or forward in under a second. Mailboxes win when the product is the conversation.
For agent reply parsing across a wider vendor set, see 14 inbound reply parsers for agent inboxes. For identity and inbox primitives, see email APIs for AI agents. Real time webhook delivery is covered in building real time agents with AgentMail webhooks. Head to heads live on the comparisons hub.
Which providers POST a parsed webhook?
Three transactional providers turn inbound mail into an HTTP POST your app already knows how to handle.
Postmark. Mail to a hash@inbound.postmarkapp.com address is processed and delivered as JSON. Most teams forward a public address such as inbound@yourapp.com to that hash rather than publishing the hash to users.
The StrippedTextReply field tries to isolate the new plain text reply without quoted history. It is English only, plain text only, and needs In Reply To or References headers. MailboxHash splits a plus tag out of the local part so you can correlate a reply with a ticket without parsing headers yourself.
Inbound processing is listed on Pro at $16.50 per month for 10,000 emails, not on Basic at $15. Default activity retention is 45 days, customizable up to 365 days on Pro. Non 200 responses retry up to 10 times; a 403 stops retries.
Mailgun. Routes match a message and run forward(), store(), or stop(). forward() can POST to a URL or to another address. store() keeps the message up to 3 days and can notify a callback with a retrieval URL, which helps when attachments would time out a single POST.
The payload carries body plain, stripped text, and stripped signature over form encoding, or multipart when attachments are present. Free includes 1 inbound route. Basic at $15 includes 5. Foundation at $35 includes full inbound routing with 1 day message retention. Without stop(), lower priority routes keep evaluating, so route order is part of the product behavior.
SendGrid. Inbound Parse needs MX pointed at mx.sendgrid.net on a dedicated hostname, then POSTs to your URL. Use a hostname that exists only for parse traffic. You can ask for the raw full MIME. Twilio documents a 30 MB total message size limit for delivery. There is no documented stripped reply field, so your worker owns quote removal. Optional spam scoring is available for messages at or under 2.5 MB.
Use these three when the message is an event, not an inbox. Your database still stores who the reply belongs to. Deduplicate on the provider message id, because webhook retries are normal.
How do you receive email on AWS with SES?
SES receiving is a pipeline you assemble, not a webhook that arrives ready to read. Receipt rules match envelope recipients on the SMTP conversation, not the To or Cc headers the sender wrote. That matters for BCC and for mailing lists.
Rules run ordered actions: deliver to S3, publish to SNS, invoke Lambda, return a bounce, add a header, stop the rule set, or hand off to WorkMail. The email content SES hands you is raw MIME in the sense of RFC 2045. Direct Lambda notifications do not include the body unless you also store or publish it.
Size is the surprise that breaks first demos. Delivery to S3 allows up to 40 MB including headers. Delivery through SNS allows up to 150 KB. Attachments and HTML blow past 150 KB quickly, so production paths almost always land in S3 first, then notify a worker with the object key.
You still own every layer after the receipt rule. MIME parsing, HTML sanitization, attachment scanning, quoted reply extraction, Message ID storage, and In Reply To construction all sit in your account. SES authenticates SPF, DKIM, and DMARC and can scan for malware, then exposes the results as attributes. It does not turn those results into a product policy for you.
Pricing for SES is plan based or a la carte. On a la carte, inbound email is listed at $0.10 per 1,000 emails, with chunk charges for larger messages. Mail Manager is a separate inbound product with its own open ingress endpoints and processing fees. You also pay for S3, SNS, and Lambda. Receiving is only available in some Regions, so check the SES endpoints list before you point MX at a Region that cannot accept mail.
SES fits teams that already live in one AWS Region that supports receiving and that want to own MIME parsing, spam policy, and thread storage. Everyone else is buying those layers again in application code.
Can you receive email at the Cloudflare edge?
Cloudflare Email Routing can send a matching address to a Worker that exports an email() handler. The message object exposes from, to, headers, a raw MIME stream, and rawSize. Your code can forward to a verified destination, reply with an EmailMessage, or call setReject with a permanent SMTP error.
Forward only accepts destinations you have already verified. Reply has strict guards: valid DMARC on the inbound message, one reply per event, recipient must match the original sender, and the outgoing sender domain must match the domain that received the mail. More than 100 References entries causes reply to throw, which exists to stop reply loops.
Nothing is stored unless you write it to KV, R2, or another binding. Inbound messages larger than 25 MiB are rejected. Each routing rule maps one email pattern to one destination address or one Worker. To fan out one address to several destinations, call forward once per destination inside the Worker. Workers on the Free plan can hit CPU limits on complex handlers and fail with EXCEEDED_CPU.
This model is ideal when the decision is local to the edge: drop known spam domains, auto reply, or fan out to three team addresses. It is a poor fit when you need searchable history or a reply six days later against the same thread object. You would have to build that store yourself.
When do you need a mailbox instead of a webhook?
You need a mailbox when the second message in the conversation has to find the first one without your own thread table.
AgentMail provisions inboxes on a shared domain or a verified custom domain. Free includes 3 inboxes, 3,000 emails a month, and 100 emails a day. Developer is $20 a month for 10 inboxes, 10,000 emails a month, and 10 custom domains. Threads are created automatically when a message is not a reply, and subsequent replies join the same thread so you can load the conversation in one request.
Inbound arrives as message.received over a webhook or a WebSocket. Spam, blocked, and unauthenticated arrivals are separate opt in events, so they do not land in the default message.received stream. Webhook payloads are capped at 1 MB; when text and html are omitted, fetch the full message through the API. Prefer webhooks in production. Prefer WebSockets when you do not want a public URL.
Register the webhook with event_types, which is the field the Python SDK accepts:
Python
# register inbound webhook (agentmail==2.0.12)import osfrom agentmail import AgentMailclient = AgentMail(api_key=os.environ["AGENTMAIL_API_KEY"])webhook = client.webhooks.create( url="https://api.northwind.dev/hooks/mail", event_types=["message.received"],)print(webhook.webhook_id)# wh_...Handle the event, reply on the same message, and return 200 quickly:
Python
# handle message.received and reply in the same threadimport osfrom flask import Flask, requestfrom agentmail import AgentMailapp = Flask(__name__)client = AgentMail(api_key=os.environ["AGENTMAIL_API_KEY"])@app.post("/hooks/mail")def handle_mail(): payload = request.get_json(force=True) if payload.get("event_type") == "message.received": message = payload["message"] reply_text = your_agent.process(message) # application code client.inboxes.messages.reply( inbox_id=message["inbox_id"], message_id=message["message_id"], text=reply_text, ) return "OK", 200In production, verify the webhook signature, enqueue work, and return 200 immediately. For a deeper walkthrough of real time delivery, see building real time agents with AgentMail webhooks.
Nylas now sells Agent Accounts alongside connected accounts. Free includes 3 Agent Accounts with 3,000 emails per month. Essentials at $15 includes 10 Agent Accounts with 10,000 emails per month. Hosted mailboxes store the message, then fire message.created. Clean Messages is a separate call for quote removal.
Choose a mailbox when email is part of the product state machine. Choose a webhook when email is a sensor that writes one row and is done.
How do you read an inbox you already have?
Reading a person's existing Gmail or Outlook mailbox is a different product from receiving mail on a domain you control. Nylas Connect links a grant to that mailbox and delivers message.created webhooks when sync sees new mail. Free includes 5 connected email and calendar accounts. Essentials includes 10. You inherit the provider's auth, folders, and retention. You do not get a new address for the agent unless you also create an Agent Account.
AgentMail does not read a Gmail or Outlook mailbox you already have. Forward into an AgentMail inbox or move MX for a domain you own. That split keeps agent identity separate from a human's personal mail.
If the requirement is "act as this user inside their existing mailbox," use a connect API. If the requirement is "give this agent its own address," use a hosted inbox API.
What does receiving email cost?
Entry price for turning inbound on differs more than per email rates suggest. Postmark keeps inbound off Basic and turns it on at Pro. Mailgun caps routes on Free and Basic before Foundation opens full routing. SES looks cheap per thousand until you add S3, SNS, Lambda, and optionally Mail Manager. Cloudflare inbound cost is mostly your Workers plan and the engineering to store what the edge does not keep.
SendGrid Inbound Parse rides on the Email API plan you already pay for. Nylas and AgentMail price by hosted inbox count on Free and the first paid tier. Exact plan numbers sit in the comparison table above so they are not repeated here.
A cheap webhook can become expensive in engineering hours when you add MIME parsing, deduplication, attachment scanning, and thread headers. A mailbox shifts those costs into the subscription.
Postmark, Mailgun, and SendGrid fit teams that already send through them and want a parsed POST. Amazon SES and Cloudflare Email Workers fit teams that want to own storage and parsing. Nylas Connect fits agents that assist inside a human mailbox. Nylas Agent Accounts and AgentMail fit products that need a real address, stored mail, and threads without building that layer.
What AgentMail does not do
AgentMail hosts inboxes. It does not read a Gmail or Outlook mailbox you already have. Forward into it or move MX for a domain you control. The Free plan caps at 3 inboxes and 100 emails a day. If you only need a one way pipe from email to a database row, a parsed webhook is less to run.




