All guides

LEARN \u00b7 DEBUGGING GUIDE

Duplicate emails sent by retry bug: how to debug it

A user signs up. They get the welcome email. Then another. Then another. Your email sending logic retried because it did not know the first attempt succeeded.

IntermediateObservability/performance debugging

What this usually means

Email sending has the same at-least-once problem as any distributed operation. The SMTP server or email API might accept the email and queue it, but the response to your application times out. Your retry logic sends it again. The email provider delivers both. Unlike database writes, email providers rarely have native idempotency, so deduplication must happen in your application.

( 01 )Fast diagnosis

The first ten minutes \u2014 establish facts before touching code.

  • 1Check if the email sending code has retry logic. Is it retrying on timeout? On any error?
  • 2Check if you track email send status. Do you record that an email was sent before or after actually sending?
  • 3Check the email provider's dashboard. Are there multiple deliveries of the same email to the same recipient?
  • 4Check if the email send is wrapped in a database transaction. If the transaction fails after sending, the retry re-sends.
  • 5Check if a message queue is involved. Is the email job being processed more than once?
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchEmail sending service code — retry configuration, error handling, idempotency
  • searchEmail provider dashboard — delivery logs, bounce logs, duplicate detection
  • searchDatabase — email send log table with unique constraint on email ID
  • searchMessage queue — if emails are sent via a queue worker, check for duplicate jobs
  • searchApplication logs — correlation ID linking the user action to email sends
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningRetry logic sends the email again on timeout without checking if the first one succeeded
  • warningEmail send is tracked in the database after sending — if the tracking fails, the email is sent twice
  • warningQueue worker processes the same message twice due to lack of idempotency
  • warningEmail provider API returns a success response slowly, triggering a client-side timeout
  • warningNo idempotency key sent to the email provider (some providers like SendGrid support this)
  • warningA batch email job is restarted and re-sends to everyone who already received it
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildRecord the intent to send in a database with a unique constraint before calling the email API
  • buildUse an idempotency key with the email provider's API if supported
  • buildSend emails through a queue with deduplication based on a unique key per email
  • buildSet retry to only retry on explicit failure responses, not on timeout
  • buildAdd a send log that prevents re-sending to the same user for the same event within a time window
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedTrigger an email send. Simulate a slow response from the email provider. Confirm only one email is delivered.
  • verifiedCheck the email provider dashboard after a test run. Only one delivery per recipient.
  • verifiedCheck the database. The email send log should have exactly one entry per email.
  • verifiedRun a load test with many email triggers and verify zero duplicates.
  • verifiedSet up monitoring that alerts if duplicate emails are detected in the send log.
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningTracking the email send after the API call instead of before
  • warningNot using a database unique constraint on email ID or recipient-event pair
  • warningRetrying on timeout without idempotency
  • warningNot monitoring for duplicate deliveries — users will notice before you do
  • warningAssuming the email provider handles deduplication for you