Email Sending Service Design (HLD & LLD)
Functional Requirements
Section titled “Functional Requirements”- Send emails to users.
- Support single email and bulk emails.
- Support HTML templates.
- Retry failed deliveries.
- Track email status (sent, delivered, failed).
- Support attachments.
- Support multiple providers (SMTP, SES, SendGrid).
Non-Functional Requirements
Section titled “Non-Functional Requirements”- High throughput (millions of emails/day)
- Reliability
- Retry & fault tolerance
- Scalability
- Rate limiting
- Low latency
High-Level Design (HLD)
Section titled “High-Level Design (HLD)”Architecture Overview
Section titled “Architecture Overview”flowchart TD
C[Client] --> G[API Gateway]
G --> E[Email Service]
E --> Q[Message Queue]
Q --> W[Email Worker]
W --> P[Email Provider]
W --> D[(Status DB)]
Components
Section titled “Components”API Gateway
Section titled “API Gateway”Responsibilities:
- Authentication
- Rate limiting
- Request validation
- Routing
Example request:
POST /email/sendPayload:
{ "to": "user@example.com", "subject": "Welcome", "templateId": "WELCOME_TEMPLATE", "variables": { "name": "John" }}Email Service
Section titled “Email Service”Responsibilities:
- Validate request
- Apply template
- Save email request
- Publish message to queue
Message Queue
Section titled “Message Queue”Examples:
- Kafka
- RabbitMQ
- AWS SQS
Benefits:
- Decoupling
- Retry support
- Backpressure handling
- High throughput
Email Worker
Section titled “Email Worker”Responsibilities:
- Consume queue
- Build email
- Connect to provider
- Send email
- Update status
- Retry on failure
Email Provider
Section titled “Email Provider”Examples:
- SMTP
- AWS SES
- SendGrid
- Mailgun
Use a provider abstraction layer to enable failover.
Database
Section titled “Database”Example schema:
EmailRequest-------------idrecipientsubjecttemplate_idstatusretry_countcreated_atupdated_atLow-Level Design (LLD)
Section titled “Low-Level Design (LLD)”Class Overview
Section titled “Class Overview”classDiagram class EmailController class EmailService class EmailQueueProducer class EmailWorker class EmailSender class EmailProvider class RetryService class TemplateService EmailController --> EmailService EmailService --> TemplateService EmailService --> EmailQueueProducer EmailWorker --> EmailSender EmailSender --> EmailProvider
EmailRequest
Section titled “EmailRequest”class EmailRequest {
String id; String to; String subject; String templateId; Map<String, String> variables; EmailStatus status; int retryCount;
}EmailController
Section titled “EmailController”@RestControllerclass EmailController {
@Autowired EmailService emailService;
@PostMapping("/send") public ResponseEntity sendEmail(@RequestBody EmailRequest request) {
emailService.queueEmail(request);
return ResponseEntity.ok("Email queued"); }}EmailService
Section titled “EmailService”class EmailService {
@Autowired EmailQueueProducer producer;
@Autowired TemplateService templateService;
public void queueEmail(EmailRequest request) {
String body = templateService.renderTemplate( request.getTemplateId(), request.getVariables());
request.setBody(body);
producer.publish(request); }}EmailQueueProducer
Section titled “EmailQueueProducer”class EmailQueueProducer {
public void publish(EmailRequest request) {
kafkaTemplate.send("email-topic", request); }}EmailWorker
Section titled “EmailWorker”class EmailWorker {
@Autowired EmailSender sender;
@KafkaListener(topics="email-topic") public void process(EmailRequest request) {
sender.send(request); }}EmailSender
Section titled “EmailSender”class EmailSender {
@Autowired EmailProvider provider;
public void send(EmailRequest request) {
provider.sendEmail( request.getTo(), request.getSubject(), request.getBody() ); }}EmailProvider Interface
Section titled “EmailProvider Interface”interface EmailProvider {
void sendEmail(String to, String subject, String body);
}SMTP Implementation
Section titled “SMTP Implementation”class SmtpEmailProvider implements EmailProvider {
public void sendEmail(String to, String subject, String body) {
// SMTP send logic
}}Retry Mechanism
Section titled “Retry Mechanism”Retry 1 -> 10 secRetry 2 -> 1 minRetry 3 -> 5 minAfter the maximum retry count, move the message to a Dead Letter Queue (DLQ).
Rate Limiting
Section titled “Rate Limiting”Respect provider limits (e.g., SES 50 emails/sec).
Approaches:
- Token bucket
- Queue throttling
Bulk Email Flow
Section titled “Bulk Email Flow”flowchart TD
C[Campaign Service] --> G[Generate Email Jobs]
G --> Q[Queue]
Q --> W[Worker Pool]
Email Template Service
Section titled “Email Template Service”Example:
WELCOME_TEMPLATE
Hello {{name}},
Welcome to our platform!Variables are substituted at runtime.
Production Features
Section titled “Production Features”Email Tracking
Section titled “Email Tracking”- Open tracking
- Click tracking
Implementation:
- Tracking pixel
- Redirect URLs
Monitoring
Section titled “Monitoring”Track:
- Email sent rate
- Failure rate
- Retry rate
- Queue lag
Tools:
- Prometheus
- Grafana
Idempotency
Section titled “Idempotency”Use a unique request identifier:
request_idPrevent duplicate sends by checking previously processed IDs.
Scalability
Section titled “Scalability”- Horizontal worker scaling
- Kafka partitioning
- Multi-provider failover
Example:
Primary -> SESFallback -> SendGridInterview Discussion Topics
Section titled “Interview Discussion Topics”- Exactly-once vs at-least-once delivery
- Idempotency
- Retry strategy and DLQ
- Provider failover
- Rate limiting
- Bulk campaign handling
- Template management
- Observability
Summary
Section titled “Summary”- Use asynchronous processing with a message queue for scalability.
- Abstract email providers to support failover and vendor independence.
- Implement retries with exponential backoff and a DLQ.
- Enforce idempotency to avoid duplicate emails.
- Monitor queue lag, delivery success, and retry metrics for operational health.