Engineering · 21 september 2026

Idempotentiesleutels voor e-mail-API's

Voorkom dubbele e-mails bij netwerkretries door idempotentiesleutels te implementeren. Lees hoe je fouten in gedistribueerde systemen afhandelt zonder je gebruikers te spammen.

Het probleem van dubbele e-mails

Dubbele e-mails ontstaan wanneer een client een request verstuurt, de server die verwerkt, maar het netwerk uitvalt voordat de client de succesrespons ontvangt. De client ziet een timeout of 5xx-fout en probeert de request opnieuw. Zonder idempotentie behandelt de server de retry als een nieuwe request en verstuurt hij de e-mail nog een keer. Idempotentiesleutels voorkomen dit: de server herkent een herhaalde request en retourneert het oorspronkelijke resultaat zonder het neveneffect opnieuw uit te voeren.

Als engineer die verantwoordelijk is voor de incidentqueue is er niets erger dan een "storm van dubbele e-mails". Die ontstaat meestal tijdens een gedeeltelijke storing bij een upstreamprovider of een database-deadlock die de responstijden vertraagt. Je retrylogica, bedoeld voor betrouwbaarheid, wordt een wapen dat je gebruikers spamt en je afzenderreputatie beschadigt.

Waarom retries zonder idempotentie misgaan

In een gedistribueerd systeem kan elke API-call op drie punten misgaan:

  1. De request bereikt de server nooit.
  2. De server verwerkt de request, maar de respons gaat verloren.
  3. De server crasht halverwege de verwerking.

Probeer je het opnieuw in geval 1, dan zit je goed. Probeer je het opnieuw in geval 2, dan verstuur je een duplicaat. Probeer je het opnieuw in geval 3, dan verstuur je mogelijk een duplicaat, afhankelijk van waar de crash plaatsvond.

Een e-mail versturen is een extern neveneffect. Anders dan het bijwerken van de naam van een gebruiker in een database (wat vanzelf idempotent is als je SET name = 'Alice' gebruikt), is een e-mail versturen een additieve actie. Elke call naar een send-endpoint zet een nieuw bericht de wereld in. Om dit idempotent te maken, moet je een unieke identifier invoeren voor de intentie om te verzenden: een idempotentiesleutel.

Idempotentiesleutels implementeren

Een idempotentiesleutel is een unieke waarde (meestal een UUID v4) die de client genereert en in de requestheader meestuurt. De server gebruikt deze sleutel om de status van de request bij te houden.

De server-side workflow

  1. Request ontvangen: de server controleert of de header Idempotency-Key aanwezig is.
  2. Lookup: de server zoekt die sleutel op in een snelle opslag (zoals Redis).
  3. Cache hit: bestaat de sleutel al, dan retourneert de server direct de gecachte respons zonder de bezorgengine voor e-mail aan te roepen.
  4. Cache miss: de server vergrendelt de sleutel, verwerkt de verzending, slaat de respons op en retourneert die aan de client.
  5. Verloop: de sleutel verloopt na een bepaalde periode (bijv. 24 uur), zodat de database niet eindeloos groeit.

Concreet voorbeeld van een payload

Zo ziet een request eruit wanneer je een API zoals SendHQ gebruikt:

POST /v1/send Host: api.sendhq.cc Content-Type: application/json Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 Authorization: Bearer YOUR_API_KEY { "to": "user@example.com", "template_id": "welcome-email", "variables": { "name": "Alex" } }

Foutgevallen afhandelen

Niet alle retries moet je hetzelfde behandelen. Je moet onderscheid maken tussen clientfouten en serverfouten.

  • 4xx-fouten: retourneert de server een 400 (Bad Request) of 422 (Unprocessable Entity), dan is de request ongeldig. Een retry met dezelfde sleutel hoort dezelfde 4xx-fout te retourneren. Pas de payload niet aan om daarna dezelfde sleutel te hergebruiken, want dat veroorzaakt een conflict.
  • 5xx-fouten: retourneert de server een 500 of 503, dan moet de client het opnieuw proberen. Had de server de e-mail al succesvol aan de MTA (Mail Transfer Agent) overgedragen, dan zorgt de idempotentiesleutel ervoor dat de retry 200 OK retourneert in plaats van een tweede e-mail te versturen.
  • Gelijktijdige requests: komen twee identieke requests met dezelfde sleutel op exact dezelfde milliseconde binnen, dan hoort de server voor de tweede request 409 Conflict te retourneren om aan te geven dat de eerste nog wordt verwerkt.

Idempotentie voor AI-agents

AI-agents (via MCP-servers of A2A-cards) brengen een nieuwe risicolaag met zich mee. LLM's kunnen non-deterministisch zijn en dezelfde toolcall meerdere keren uitvoeren als ze denken dat er in de lus iets misging.

Laat bij agentgerichte integraties een agent nooit een send-actie starten zonder goedkeuringsstap of zonder een deterministische idempotentiesleutel die door de orchestrator wordt gegenereerd. De orchestrator hoort de intentie van de agent (bijv. "Stuur het weekrapport naar Bob") te koppelen aan een stabiele sleutel op basis van het rapport-ID en de datum. Zo voorkom je dat de agent per ongeluk vijf keer hetzelfde rapport verstuurt omdat hij "dacht" dat de eerste call mislukt was.

De kosten van fouten: providers vergeleken

Implementeer je geen idempotentie, dan irriteer je niet alleen gebruikers, maar verspil je ook geld. Sommige providers zijn goedkoper, maar de kosten van duplicaten lopen snel op.

Volgens de officiële prijspagina's (per september 2026):

  • Amazon SES: kost a la carte 0.10 USD per 1.000 e-mails (prijzen van Amazon SES). De nieuwe abonnementsniveaus die op 21 juli 2026 zijn ingevoerd, zijn Essentials (0.16 USD per 1.000), Pro (0.22 USD per 1.000 plus 105 USD per maand per regio) en Enterprise (0.23 USD per 1.000 plus 500 USD per maand).
  • Resend: het gratis niveau biedt 3.000 e-mails per maand met een maximum van 100 per dag. Pro kost 20 USD per maand voor 50.000 e-mails, met overschrijdingen tegen 0.90 USD per 1.000 (prijzen van Resend).
  • SendGrid: het gratis niveau is nu een proefperiode van 60 dagen en Essentials-abonnementen beginnen bij 19.95 USD per maand (prijzen van SendGrid).
  • Mailgun: kost 15 USD per maand voor 10.000 e-mails, met overschrijdingen van 1.80 tot 1.10 USD per 1.000 (prijzen van Mailgun).
  • Postmark: kost 15 USD per maand voor 10.000 e-mails, met overschrijdingen van 1.80 tot 1.20 USD per 1.000 (prijzen van Postmark).

Ter vergelijking: 50.000 e-mails versturen kost ongeveer 5 USD bij SES a la carte, tegenover ongeveer 66 USD bij de niveaus van Postmark. Vermenigvuldigt een retrylus zonder idempotentie je volume per ongeluk met 10x, dan wordt het financiële verschil tussen providers een flinke post in je incidentrapport.

Deliverability vs. acceptatie

Het is essentieel om te begrijpen dat idempotentie alleen het probleem van acceptatie oplost.

  1. Acceptatie: de API accepteert je request en retourneert 200 OK. Hier horen idempotentiesleutels thuis.
  2. Bezorging: de API draagt de e-mail over aan de ontvangende server (bijv. Gmail). Hier doen SPF en DKIM/DMARC ertoe.
  3. Inboxplaatsing: de ontvangende server beslist of de e-mail in de inbox of in de spammap belandt.

Een idempotentiesleutel zorgt ervoor dat je de request maar één keer accepteert. Het garandeert niet dat de e-mail wordt afgeleverd of dat hij de spammap ontloopt. Om te controleren of je infrastructuur goed is ingericht voor bezorging, gebruik je tools zoals de E-mail-DNS-checker van SendHQ om je records te verifiëren.

Implementatiechecklist voor engineers

Controleer je vandaag je logica voor het versturen van e-mail? Gebruik dan deze checklist:

  • Client-side sleutelgeneratie: Genereer je een UUID v4 voor elke unieke e-mailintentie?
  • Headerimplementatie: Wordt de sleutel doorgegeven in een standaardheader (bijv. Idempotency-Key) in plaats van de request body?
  • Opslaglaag: Heb je een TTL (Time To Live) op je idempotentiesleutels om opslagwildgroei te voorkomen?
  • Atomair vergrendelen: Gebruikt je server een distributed lock (zoals SET NX in Redis) om race conditions op dezelfde sleutel te voorkomen?
  • Responscaching: Sla je de volledige respons (statuscode en body) op om die bij retries aan de client terug te geven?
  • Guardrails voor agents: Als je AI-agents gebruikt, wordt de sleutel dan door de systeemorchestrator gegenereerd in plaats van door de LLM?

Codevoorbeeld: idempotentiemiddleware in Node.js

Hier is een vereenvoudigd voorbeeld van hoe je deze logica in een Node.js-omgeving met Redis kunt implementeren.

const redis = require('redis'); const client = redis.createClient(); async function sendEmailHandler(req, res) { const idempotencyKey = req.headers['idempotency-key']; if (!idempotencyKey) { return res.status(400).json({ error: 'Idempotency-Key header is required' }); } // Try to acquire a lock and check for existing response const cachedResponse = await client.get(`idempotency:${idempotencyKey}`); if (cachedResponse) { const { status, body } = JSON.parse(cachedResponse); return res.status(status).json(body); } // Set a lock to prevent concurrent requests const lock = await client.set(`lock:${idempotencyKey}`, 'true', 'NX', 'EX', 30); if (!lock) { return res.status(409).json({ error: 'Request is currently being processed' }); } try { // Actual email sending logic const result = await emailProvider.send(req.body); const responsePayload = { status: 200, body: result }; // Cache the result for 24 hours await client.set(`idempotency:${idempotencyKey}`, JSON.stringify(responsePayload), 'EX', 86400); return res.status(200).json(result); } catch (error) { return res.status(500).json({ error: 'Internal Server Error' }); } finally { await client.del(`lock:${idempotencyKey}`); } }

Tot slot

Idempotentie is geen "nice-to-have" voor transactionele e-mail; het is een vereiste voor elk systeem dat waarde hecht aan gebruikerservaring en kostenbeheersing. Door de verantwoordelijkheid voor uniciteit bij de client te leggen en op de server een mechanisme te bieden om die uniciteit bij te houden, elimineer je het risico op dubbele verzendingen bij een instabiel netwerk.

Of je nu een traditioneel SaaS-product bouwt of een autonome AI-agent: als je e-mail behandelt als een kritiek neveneffect, blijft je systeem betrouwbaar en blijven je gebruikers tevreden. Zoek je een e-mail-API die voor developers is gemaakt en deze complexiteit afhandelt? Bekijk dan https://sendhq.cc.