Enviar email en produccion no es solo llamar un SDK. El problema real aparece con timeouts, respuestas 429/5xx y workers que reintentan sin control hasta duplicar notificaciones.
Caso practico guiado
Escenario: pipeline de emails transaccionales (receipt, password-reset). Requisito:
- entrega eventual con maximo 5 intentos,
- cero duplicados por
messageId, - alertas si falla mas de 2% por 10 minutos.
Implementacion TypeScript
type Job = {
messageId: string;
to: string;
template: "receipt" | "password-reset";
payload: Record<string, string>;
attempt: number;
};
const MAX_ATTEMPTS = 5;
function retryDelayMs(attempt: number): number {
const base = Math.min(500 * 2 ** attempt, 30_000);
const jitter = Math.floor(Math.random() * 600);
return base + jitter;
}
export async function processEmail(job: Job) {
if (await sentStore.exists(job.messageId)) {
metrics.count("email_duplicate_prevented_total", 1);
return;
}
try {
await provider.send(job.to, job.template, job.payload, {
idempotencyKey: job.messageId,
});
await sentStore.mark(job.messageId);
metrics.count("email_sent_total", 1, { template: job.template });
} catch (error) {
const status = extractHttpStatus(error);
const retryable = status !== null && [408, 429, 500, 502, 503, 504].includes(status);
if (retryable && job.attempt < MAX_ATTEMPTS) {
await queue.enqueue(job, { delayMs: retryDelayMs(job.attempt + 1), attempt: job.attempt + 1 });
metrics.count("email_retry_total", 1, { status: String(status) });
return;
}
metrics.count("email_failed_total", 1, { status: String(status ?? "unknown") });
throw error;
}
}
Observabilidad minima obligatoria
- Tasa de entrega por plantilla y por proveedor.
- Latencia p95 de API de correo.
- Conteo de retries y agotamiento de reintentos.
- Cola acumulada y tiempo promedio en cola.
Checklist accionable
- Idempotencia por
messageIden app y proveedor - Retries con exponential backoff + jitter
- Dead-letter queue para fallos terminales
- Dashboard con
sent,retry,failed,queue_lag - Alertas sobre error rate y crecimiento de cola
Happy reading! ☕
Comments