Prompt
How do I retry failed email sends without losing messages?
Latest observation
To retry failed email sends without losing messages, the key is to make your send process durable and idempotent.
Best practice pattern
1) Save the email before sending
Store each outgoing email in a database or queue first, with a status like:
pendingsendingsentfailedretrying
That way, if the send process crashes, the message still exists and can be retried later.
2) Use a queue or job worker
Instead of sending directly from your app request, enqueue the email and let a worker process it asynchronously.
Good options:
- RabbitMQ
- AWS SQS
- Redis queue
- Celery / Sidekiq / BullMQ / Hangfire, etc.
This lets you retry safely without blocking users.
3) Retry only transient failures
Retry for temporary problems like:
- timeouts
- 4xx SMTP errors
- rate limits
- network failures
Do not blindly retry permanent failures like:
- invalid recipient address
- mailbox does not exist
- rejected content
4) Add exponential backoff
Use increasing delays between retries, for example:
- 1 minute
- 5 minutes
- 15 minutes
- 1 hour
This avoids hammering the mail server and improves success rates.
5) Make sending idempotent
Ensure the same email is not sent twice if a retry happens after uncertainty.
Ways to do this:
- assign a unique
message_id - store a send record with a unique constraint
- check status before sending
- mark success only after confirmed provider response
If your provider supports it, use a deduplication key or idempotency key.
6) Use a dead-letter queue or failure state
After N failed attempts, move the email to:
dead_lettermanual_reviewpermanent_failed
This prevents infinite retry loops.
7) Log and monitor
Keep:
- attempt count
- last error
- timestamps
- provider response
That helps you debug and safely replay failures.
Simple reliable workflow
- User submits email
- App writes email to DB as
pending - Worker picks it up and marks
sending - Worker sends via SMTP/API
- On success: mark
sent - On transient failure: increment attempts, schedule retry
- On permanent failure or max retries: mark
failed
Extra safety tips
- Use an SMTP/API provider with delivery logs
- Avoid sending synchronously from web requests
- Reconcile retries with a unique email record to avoid duplicates
- If using SMTP, be careful: a timeout may mean the provider actually accepted the message, so don’t resend blindly without tracking
If you want the simplest answer
Store outgoing emails in a durable queue/database, retry only transient failures with backoff, and make each send idempotent so repeated attempts don’t duplicate messages.
If you want, I can show you:
- a database schema for this,
- a sample implementation in Python/Node/Java,
- or a production-ready retry algorithm.