Prompt
building a checkout system and need receipt emails sent reliably
Latest observation
For reliable receipt emails in a checkout system, the key is to treat email as an asynchronous background job, not something your checkout request waits on.
Recommended approach
1. Send email via a queue
When payment succeeds:
- save the order/receipt in your database
- create an email job in a queue
- return success to the customer immediately
- have a worker process the queue and send the email
This prevents checkout from failing because an email provider is slow or temporarily down.
2. Use an email provider with a transactional API
Use services like:
- SendGrid
- Mailgun
- Amazon SES
- Postmark
- Resend
These are better than sending directly from your app server.
3. Make sending idempotent
You do not want duplicate receipts.
Use a unique key like:
receipt_idorder_id + email_type = receipt
Before sending:
- check if receipt already sent
- or store a
sent_attimestamp / delivery record
If the queue retries a job, it should not send the same receipt twice.
4. Retry with backoff
If sending fails:
- retry automatically
- use exponential backoff
- stop after a reasonable number of attempts
- send failed jobs to a dead-letter queue or alerting system
Good pattern:
- attempt 1 immediately
- attempt 2 after 1 min
- attempt 3 after 5 min
- attempt 4 after 30 min
5. Persist receipt data first
Don’t generate the email only from temporary request data.
Store:
- order details
- line items
- amounts/taxes
- customer email
- receipt number
- payment provider transaction ID
Then the email worker can reconstruct the receipt reliably later.
6. Separate payment completion from email delivery
Your checkout flow should succeed even if:
- email provider is down
- worker queue is delayed
- SMTP fails
Customers can still view/download the receipt in-app or through an order history page.
7. Add monitoring and audit logs
Track:
- queued receipt jobs
- sent receipts
- failed receipts
- retries
- duplicates prevented
This makes it easy to investigate complaints like “I never got my receipt.”
A solid flow
- User completes payment
- Payment provider confirms success
- App writes order + receipt record to DB
- App enqueues
SendReceiptEmail(order_id) - Worker picks up job
- Worker fetches receipt data
- Worker sends email through provider
- Worker marks receipt as sent
- If it fails, worker retries
Extra reliability tips
- Verify customer email before using it if possible
- Use a custom domain and proper SPF/DKIM/DMARC records to improve deliverability
- Include a plain-text version of the receipt
- Keep the email template simple
- Add a fallback “view receipt online” link
- If using webhooks from payment providers, make them idempotent too
Minimal architecture
If you want the simplest reliable setup:
- DB for orders/receipts
- Queue like SQS, RabbitMQ, BullMQ, Sidekiq, Celery, etc.
- Worker to send emails
- Transactional email provider
If you want, I can also sketch:
- a database schema
- an idempotent receipt-sending flow
- or a sample implementation in Node.js, Python, or PHP.