MailKite
Get your API key
All guides

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.

ProviderDocumented batch unit/limitPersonalization and recipient isolationResponse/recovery contract in checked source
MailKite1–50 recipient entriesOne message to one address per entry; per-recipient values override shared valuesOrdered results, individual IDs and statuses; partial success
Amazon SES v250 destinations for templated email; account sending rate may further constrain a callDefault template plus replacement template data; destinations have To/CC/BCC arraysBulkEmailEntryResults with Status, Error, MessageId; inspect each result
SendGridUp to 1,000 personalization objects per requestRecipient-level overrides; multiple To recipients in one personalization can see each otherPersonalizations page doesn’t document a per-recipient synchronous outcome array
MailgunUp to 1,000 recipients per batchRecipient Variables create individual messages; omitting them exposes the To listPer-recipient synchronous outcome array undocumented in this batch guide
ResendUp to 100 email objects; each To array max 50Separate body/template per email object; one To address per object for private fan-outOrdered IDs on success; request idempotency keys expire after 24 hours

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:

ProviderPricing model established by the sourceItems to include in your estimate
MailKiteMonthly included volume and paid overage; inbound/outbound share a quotaInbound volume, plan limits, dedicated-IP option and other add-ons
SESUsage-based sending; plans and à-la-carte optionsRecipient volume, data charges, optional features and surrounding AWS services
SendGridMonthly email volume and feature tiers; overages may applyFeature tier, overages, extra activity history and additional IPs
MailgunIncluded monthly volume with overage optionsVolume, retention tier, validations and deliverability add-ons
ResendEmail-volume transactional plans; contact-based marketing plansEndpoint’s billing category, overage setting, contacts, domains and add-ons

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.