Prompt
How should an email service integrate with a backend?
Latest observation
A good email service integration with a backend usually follows one of these patterns, depending on whether you’re using a third-party provider like SendGrid, SES, Mailgun, Postmark, or your own SMTP server.
Recommended approach
1. Backend owns email logic
Your backend should decide:
- when to send an email
- which template to use
- what data goes into it
The backend should not expose direct email-sending logic to the frontend.
2. Use a dedicated email provider or service layer
Create an internal email module/service in your backend, for example:
EmailService.sendWelcomeEmail(user)EmailService.sendPasswordReset(user, token)EmailService.sendReceipt(order)
This service handles:
- template rendering
- provider API calls
- retries/failover
- logging and metrics
3. Send emails asynchronously
Do not send emails inline in the request-response cycle if you can avoid it.
Instead:
- backend receives event/action
- backend writes a job/message to a queue
- worker process sends the email
This improves:
- latency
- reliability
- scalability
Common tools:
- RabbitMQ
- SQS
- Kafka
- Redis queues / BullMQ
- Celery
4. Use templates
Emails should be template-driven:
- HTML template
- plain text fallback
- dynamic variables
Example:
welcome_email.htmlpassword_reset.html
Keep templates in:
- the backend repository, or
- the provider’s template system, or
- a separate shared template service
5. Handle provider callbacks/webhooks
Integrate provider webhooks for:
- delivery
- bounce
- spam complaints
- unsubscribe events
- open/click tracking if needed
Your backend should store these events and update user/email status accordingly.
6. Keep credentials secure
Store API keys and SMTP creds in:
- environment variables
- secret manager
- vault
Never expose them to the client.
Typical architecture
Frontend -> Backend API -> Queue -> Email Worker -> Email Provider -> Recipient
↘
Webhooks back to Backend
Example flow: password reset
- User submits email to request reset.
- Backend verifies account exists.
- Backend creates reset token.
- Backend enqueues
password_reset_emailjob. - Worker renders template and sends email.
- Provider webhook reports delivery/bounce.
- Backend logs status.
Best practices
- Idempotency: avoid sending duplicate emails on retries.
- Retry with backoff: transient provider failures should retry.
- Rate limiting: prevent abuse of resend endpoints.
- Audit logging: record when/why an email was sent.
- Unsubscribe management: for marketing emails, honor preferences and legal rules.
- Separate transactional vs marketing email: different sending infrastructure if possible.
- Monitor deliverability: bounce rate, spam complaints, SPF/DKIM/DMARC setup.
What not to do
- Don’t let the frontend send email directly.
- Don’t block API requests waiting for email delivery.
- Don’t hardcode SMTP credentials.
- Don’t mix business logic and email provider code everywhere—centralize it.
Simple implementation pattern
If you want a practical setup:
- Controller/API triggers an event
- Email service creates a job
- Worker sends via provider SDK
- Webhook endpoint receives delivery events
- Database stores email status and logs
If you want, I can also show:
- a Node.js example,
- a Python/Django example, or
- a microservices architecture diagram for email integration.