Which Email APIs Support Idempotency Keys
If an email send times out, clicking send again is safe on some providers and a second email on others. Here is what each provider's docs said in October 2026.
Key takeaways
- -In this article we explore which email APIs support idempotency keys, using each provider's public docs as they stood in October 2026.
- -To understand how an email API idempotency key works: it is a label that says this is the same message, so a repeat is not a second email.
- -Resend supports an Idempotency-Key header on single and batch sends for 24 hours. The same key and the same email return the first result.
- -Brevo supports an idempotency key on batch sends for 30 minutes. A repeat returns the error duplicate_parameter and does not send the batch again.
- -Postmark, Amazon SES, SendGrid, and Mailgun do not document an idempotency key that stops a second email. On those email APIs, a timeout is not proof the first send failed.
You sent an email. The screen spun, or the assistant said the call failed, and you do not know if the person received it. The natural next move is to send it again.
On some email services, that second click is recognized as the same email. On others, it is a second email. A password reset, a receipt, or "your invoice is ready" is a bad thing to receive twice.
The label that makes the second click safe is called an idempotency key. You do not need the jargon to use the table. It is a note you attach to the request that says "this is the same message as last time." If the service stored that note, it will not mail the person again. If it did not, it will.
The notes below come from each company's public docs, read in October 2026. Docs change. If you are about to rely on a row, open their page and confirm it still says this.
What each provider documents
| Provider | Do they remember a repeat? | What happens if you send again |
|---|---|---|
| Resend | Yes. You send a header called `Idempotency-Key` on a single email or a batch. They keep it for 24 hours. The same key and the same email return the first answer. The same key with a different email is rejected. | The person is not mailed twice, inside 24 hours, when the email itself matches. |
| Brevo | For batch sends, yes. The label is a UUID, a random-looking id, kept for 30 minutes. A repeat in that window returns an error called `duplicate_parameter` and is not sent again. | No second email inside 30 minutes. You get an error, not a copy of the first success. After 30 minutes the same label can be used for a new batch. |
| Postmark | No. Their own help article, updated on 6 October 2026, says they do not offer this. They suggest you keep your own record, and use webhooks to see what was delivered. | A second request can send a second email. |
| Amazon SES | No field for this on the send call. Amazon writes that a timeout or a server error can happen after they have already accepted the message. A retry can create a second message. | A second email. Tags help you match events to a message. They do not stop the second send. |
| SendGrid | Their Mail Send reference does not describe this kind of label. You can attach your own note for your logs. | A second email. |
| Mailgun | Their send docs use a label only for inbox-placement tests, which check where a message lands. The same test label will not start a second test. It does not say the same label stops a second email to the person. | A second email. The label only avoids a second test. |
Sending through SMTP, the older mail protocol, has the same trap with fewer knobs. Hand the provider the message twice and you have handed them two messages. In this list, Resend is the one that also documents a header for that path. Do not assume the others added one because their website API has something similar.
What actually happens when the answer gets lost
- Your app, or your assistant, asks the provider to send the email.
- The provider accepts it. Some of them store your label.
- The answer never gets back to you. You do not know that step 2 worked.
- Something tries again. A queue, a person, or an assistant that thinks the first try failed.
On Resend, step 4 with the same label and the same email returns the first answer. The person gets one email. If you keep the label but change the email, Resend refuses, which is what you want. That label already meant a different message.
On Brevo's batch send, step 4 inside 30 minutes does not send again. It also does not give you the first receipt. You see duplicate_parameter. If your assistant treats every error as "try a new send," it will create a new label and the protection is gone. After 30 minutes the label expires, and reusing it sends a new batch.
On Postmark, Amazon SES, SendGrid, and Mailgun, step 4 is another email, as far as their send docs describe it. Amazon is explicit that the first call may have succeeded anyway. A successful second response can mean two emails. A failed first response is not proof that zero emails went out.
The label has to mean the same thing every time: this receipt, this password reset, this invite. Create it once, from that event, and reuse it. A fresh random value on each try is a new email on every row of the table.
When to leave it alone
Only retry when the provider will recognize the repeat.
Resend is the clearest case here: same label, same email, inside 24 hours. Brevo's batch label is narrower. It lasts 30 minutes, and the repeat looks like an error. Treat that error as "already accepted," not as "failed, so send a new one."
For Postmark, Amazon SES, SendGrid, and Mailgun, the safer habit is one attempt, then a status you can read. If you cannot read a status, do not let an assistant send again because the first call timed out. A timeout is exactly the case Amazon says can already be on its way.
A webhook, the ping a provider sends you later, does not fix a second email you already sent. It arrives after the fact. It cannot unsend anything.
If an assistant is the one clicking send
Swytchcode is a small program that makes the call for your assistant. It will retry a send only when that call already has a stable label, and it slows down when the provider says you are going too fast. That stops Swytchcode from inventing a second email on its own. It does not add memory to a provider that never built any. If the table says a retry is a second email, do not turn on a retry for that send and call it safe.
Do not let the assistant invent a new label every time it tries. A new label is a new email. A longer explanation is Idempotency Explained. If the assistant is built with Anthropic's Agent SDK, the same trap is in Making Anthropic Agent SDK Tool Calls Idempotent.
Connecting Brevo from Claude Code, and the difference between one email and adding someone to a list, is How to Connect Brevo to Claude Code.
FAQ
Which email services remember a repeat send?
Resend does, for 24 hours, if you send the same label and the same email. Brevo does for batch sends, for 30 minutes, and a repeat comes back as an error. Postmark said in October 2026 that they do not. Amazon SES, SendGrid, and Mailgun do not document a label that stops a second email.
Does a success response mean exactly one email?
It means this response said success. An earlier call that timed out may already have been accepted. Amazon documents that double send. On any provider in the "second email" rows, assume it can happen.
What should I use as the label?
Something stable for that one event, such as the order id plus the word "receipt." Not a random value created at the moment the request is built. The retry has to be able to produce the same label.
Is Mailgun's test label the same feature?
No. That label stops a second inbox-placement test. It is not documented as stopping a second email to the recipient.
Brevo returned duplicate_parameter. Should I try again with a new label?
No. That error means this label was already used in the last 30 minutes. Treat it as already accepted, or look the message up another way. A new label is a new batch.
Follow these steps to try one test email
Before you let an assistant mail customers, send one message to yourself and see what a second attempt does. If Swytchcode is new to you, start here. swy is the short name for the program. Do these steps from the project folder.
1. Follow this step to install the CLI
Pick the one that matches your computer.
If you already use Node.js:
npm install -g swytchcodeOn Windows, open PowerShell and run:
irm https://cli.swytchcode.com/install.ps1 | iexOn macOS or Linux:
curl -fsSL https://cli.swytchcode.com/install.sh | shThen check that the install worked:
swy --versionA version number means the CLI is on your machine. The rest of the install walkthrough is in the CLI quickstart.
2. Follow this step to sign in
swy loginA browser window opens so you can create an account or sign in. Your first two calls that actually reach a service can run before you have an account. After those, you need to be signed in. How accounts and service logins work is in the authentication docs.
3. Follow this step to start a test project
Use the line for the editor you already have.
swy init --editor=claude --mode=sandboxIf you use Cursor, change claude to cursor. If you use Codex, change it to codex. --mode=sandbox means test mode. That choice is saved for this project. What the command creates is in Set up a project.
4. Follow this step to download the email service
swy get <provider>Replace <provider> with the service you picked from the table, such as brevo. This downloads it. It does not turn sending on.
5. Follow this step to find the send action
swy discover "send an email" --project <provider>This prints the current name. Use that name. Do not copy one from an old note, including this article. Read that provider's row in the table above before the next two steps. A single test to yourself is the goal. If the row says a retry is a second email, do not send it again after a timeout.
6. Follow this step to turn that one action on
swy add method <id from discover>Replace <id from discover> with the name from step 5. This allows that one action. It does not make a retry safe.
7. Follow this step to store the provider key
swy auth connect <provider>This stores the key on your machine, outside the chat. If you use Brevo, the same path is written out with more care about lists and campaigns in this article: How to Connect Brevo to Claude Code.
Send the first email to yourself. Then read the row for that provider again before anyone retries a real send.
Read next
- How Swytchcode treats a repeat is in the idempotency guide and the retries guide.
- Explore this article for the same idea in plain language: Idempotency Explained.
Try a single test send first
You can also give Claude Code, Cursor, or Codex skills.md and ask it to follow the steps in this article, in test mode.
If the provider docs have changed since October 2026, or a retry still produced two emails, tell us what you saw. Discord is the quick conversation. The contact form works if you would rather write it down. You can describe it in ordinary words. The provider name and what the second attempt did is enough.
More content
How to Log Every API Call an AI Agent Makes
Log the call where it is sent, not where it is described. Record the operation, the actor, the idempotency key, the policy decision and the outcome. Never record the credential. If the agent writes its own log, the calls that were blocked quietly disappear.
How to Connect GitHub to an AI Agent (2026)
Most of what you want an agent to do on GitHub needs no push access at all. Working that out first is the difference between a useful teammate and a token that can rewrite your main branch.
Composio Pricing Compared with Nango, Arcade, Pipedream, and Swytchcode
Composio is free for 100,000 tool calls a month, then $29. Nango is $50 pay-as-you-go. Arcade Team is $25 plus a cent a call. Pipedream Startup is $99 billed annually. Swytchcode is $149 for a million. The headline prices are the least useful part, so here is the arithmetic.
