Bulk email APIs compared: MailKite, SES, SendGrid, Mailgun, Resend
Reviewed
Bulk email APIs differ in what a batch represents, how personalization works, and what recovery information comes back. For consented application fan-out, compare the message model before comparing request size: MailKite has per-recipient results, SES has templated bulk entries, SendGrid has personalizations, Mailgun has recipient variables, and Resend accepts separate email objects. The suitable choice depends on your workload and operating boundary.
Disclosure: we build MailKite. This is a documentation-based comparison, checked October 14, 2026, not a delivery benchmark or a claim that we ran identical workloads on these services. “Undocumented here” means the cited sources didn’t establish that contract, not that the product cannot provide it.
Compare the actual batch unit
Every factual cell below links directly to its primary evidence. Limits describe request shape, not throughput or permission to send unsolicited mail.
For a newsletter audience, one email object with many visible recipients is usually the wrong model. Keep one intended recipient per message unless the job genuinely is a group conversation. SES destination arrays and Resend To arrays shouldn’t be treated as equivalent to independent private messages.
SendGrid’s 1,000-personalization limit also isn’t a promise about arbitrary payloads: the rest of the endpoint’s validation still applies. Its personalization guide limits substitution properties to 150 per personalization and restricts sender-domain overrides. Model your adapter against the endpoint, not just the headline count.
Choose by workload
Choose MailKite for consented app fan-out when per-recipient recovery and the surrounding email workflow fit your application. Its shared base message and recipient overrides suit “same template, different values.” Scheduled batches return individual scheduled-send IDs. Trade-offs: smaller request chunks than some alternatives, no throughput guarantee derived from that limit, and no documented exactly-once guarantee from arbitrary MIME headers.
Choose SES for an AWS-owned workflow when your team wants to assemble the surrounding system. SES v2 returns entry-level outcomes and supports default templates with replacement data. You must account for regional quotas, permissions, feedback handling, and production access. Sandbox sending is limited to 200 recipients per 24 hours and one recipient per second; production quotas depend on the account’s approved use case. Those constraints can matter more than request packing.
Choose SendGrid when its personalization model matches an existing integration. You can override subject, headers, metadata, and send time for each personalization. Keep a single To recipient in each personalization for private fan-out. The checked page doesn’t establish a synchronous outcome ledger like MailKite’s, so confirm how your chosen endpoint and delivery events will populate recipient state before implementing retries.
Choose Mailgun when shared content plus recipient variables is a natural fit, including SMTP-based integration. Its batch guide covers HTTP and SMTP. The recipient-variable requirement is operationally important: leaving it out can expose every To address. Verify downstream event correlation and the actual submission response before writing a selective-retry adapter; this guide doesn’t infer a partial-success response from the batch-size limit.
Choose Resend when a batch of distinct email payloads and documented request idempotency fit the job. You can submit different messages together and associate returned IDs by index. Its batch endpoint currently does not support attachments. If attachments are part of the workload, that limitation is a selection criterion, not a minor implementation detail. Idempotency expires after 24 hours, so retain your own durable message identities beyond that window.
Recovery changes the integration cost
Separate explicit rejection from an ambiguous timeout. If you receive recipient-level outcomes, store successful IDs and retry only eligible failures. If a request disappears after submission, mark it unknown. Reconcile using the provider’s documented lookup/event facilities or its idempotency contract before submitting another copy.
Request idempotency and event deduplication solve different problems. Reusing a Resend request key protects that API boundary within its documented window. MailKite’s application-event deduplication belongs to sequence triggering, not automatically to batch submission. Likewise, a custom header is message metadata unless a provider explicitly documents stronger semantics.
The bulk email architecture guide describes a recipient ledger and restart-safe worker. Preserve that ledger across provider changes. A migration should map acceptance states, permanent failures, suppression state, scheduled IDs, and unresolved requests before switching traffic.
Compare pricing models, not unqualified totals
Current primary pricing pages describe different billing boundaries:
Don’t calculate a universal price per million from a starting price. Fix the message count, average size, inbound traffic, subscriber count, retention, and optional features first. Then obtain a current quote under those assumptions. API request count is not necessarily the billing unit; packing more messages into one request doesn’t make those messages free.
Sending policies are part of suitability
All policy statements below were checked October 14, 2026. MailKite terms prohibit unsolicited bulk or commercial email and sending without consent or after opt-out. AWS’s AUP prohibits unsolicited mass email and other spam. Twilio’s Email Policy requires affirmative consent except for its defined transactional category, and prohibits unwanted bulk sending and purchased/rented lists. Mailgun’s AUP requires express, provable permission for non-transactional mail and prohibits bought, rented, or scraped third-party lists. Resend’s AUP explicitly prohibits cold outreach and requires opt-in recipients.
None belongs in an unsolicited-outreach shortlist merely because it has a batch API. Use the cold email platform comparison for that separate evaluation. For subscriber campaigns, start with programmatic newsletters; for application-triggered reminders, use programmatic email.
Make the decision executable
Pick the two candidates whose message model matches your application. Validate a controlled fixture with separate recipients, missing variables, explicit rejection, a simulated timeout, and cancellation. Inspect what your ledger can prove after each boundary. This article offers no winner on inbox placement, latency, or achieved throughput.
Implement private fan-out in JavaScript, Python, or PHP. Choose the provider whose contract you can operate, including its failures, rather than the largest number in the batch-size column.