Ir a la documentación
SDK

Reintentos e idempotencia

Qué se reintenta, qué no se reintenta a propósito y por qué un envío reintentado no puede duplicarse.

Envíos

El cliente adjunta un Idempotency-Key a cada envío (emails.send, emails.sendBatch y templates.send), generado una vez por **llamada** y reutilizado por los reintentos de esa llamada. La API reserva esa clave antes de despachar nada, así que un reintento reproduce el mensaje original en lugar de enviar uno segundo, mientras que dos llamadas deliberadas a send() siguen enviando dos veces. Son intenciones distintas y siguen siéndolo.

Pasa tu propio idempotencyKey para extender esa garantía entre procesos, de modo que un trabajo que se cayó y volvió a ejecutarse reproduzca sus envíos en lugar de repetirlos.

idempotency.ts
await openemail.emails.send(message, { idempotencyKey: `invoice:${invoice.id}` })

Dedúcelo de aquello que hizo necesario el envío. Nunca de un reloj. Reutilizar una clave con un cuerpo distinto se rechaza con idempotency_key_reuse en lugar de reproducirse en silencio.

Todo lo demás

Toda lectura se reintenta. Una escritura solo se reintenta cuando una segunda petición idéntica no puede significar nada distinto de la primera, y un envío cumple esa condición porque su clave de idempotencia convierte una repetición en una reproducción.

LlamadaSe reintentaPor qué
Toda lecturaNo cambia nada.
`emails.send`, `emails.sendBatch`, `templates.send`Una clave de idempotencia convierte una repetición en una reproducción.
`emails.cancel`, `emails.reschedule`Una asignación pura de un estado con nombre.
`threads.update`, `threads.trash`Una asignación de etiquetas. Aplicarla dos veces equivale a aplicarla una vez.
`threads.snooze`, `threads.unsnooze`El instante de reactivación viaja en el cuerpo; no se deriva de la hora de llegada.
`labels.update`, `webhooks.update`, `settings.update`, `roles.update`, `members.update`Una asignación pura de campos con nombre.
`members.grantAddress`, `rules.reorder`La concesión es un upsert, y el orden se indica por completo.
`templates.publish`Publicar un head que ya está publicado lo devuelve sin cambios.
`templates.preview`, `rules.test`Renderizan o evalúan, y no escriben nada.
`drafts.create`, `labels.create`, `webhooks.create`, `templates.create`, `rules.create`, `roles.create`, `tempMail.create`NoUn reintento deja dos objetos.
`drafts.update`NoLee el id del resultado de cada escritura en lugar de reutilizar el que enviaste.
`drafts.delete`, `labels.delete`, `webhooks.delete`, `templates.delete`, `rules.delete`, `roles.delete`, `members.remove`, `members.revokeAddress`, `tempMail.delete`, `tempMail.deleteMessage`NoUn reintento tras una respuesta perdida informa de un fallo en un trabajo que sí se completó.
`webhooks.rotateSecret`NoUna segunda rotación invalida el secreto que devolvió el primer intento.
`webhooks.test`NoEnviaría una segunda entrega sintética.
`emails.translate`NoConsume llamadas al modelo, así que un reintento tras una solicitud sin respuesta paga dos veces la misma respuesta.
Cualquier otra escrituraNoSe envía una sola vez, y un fallo se informa en lugar de repetirse.

El backoff

  • Limitado por maxRetries en el cliente, con dos intentos adicionales por defecto.
  • Solo tras un fallo de red o un 408, 500, 502, 503 o 504. Un 429 se reintenta únicamente cuando lleva un Retry-After, y esta API no lo envía, así que un límite de tasa lanza un error de inmediato. Cualquier otro estado lanza al instante.
  • Exponencial, desde medio segundo hasta ocho, con jitter, para que una flota no se resincronice al recuperarse el servicio.
  • Regulado por Retry-After en cualquiera de sus formas, delay-seconds y HTTP-date. Cuando el servidor indica una espera, el cliente espera exactamente ese tiempo en lugar de aplicar backoff.
  • Si el servidor pide más de un minuto, se interpreta como una orden de detenerse y no de esperar, así que el error se lanza con retryAfterSeconds incluido. Volver antes de lo que pidió no es respetarlo.
  • El AbortSignal de quien llama nunca se reintenta. Abortar lanza un OpenEmailNetworkError de inmediato, ya sea desde la solicitud o desde la espera previa al siguiente intento.