Skip to main content

Overview

The EMS uses an asynchronous email notification system built on the adapter pattern, allowing easy integration with different email providers while maintaining a consistent interface.

Adapter Pattern

Flexible architecture for multiple email providers

Async Processing

Non-blocking email delivery

Spring Mail Integration

Built on Spring Boot’s JavaMailSender

Error Resilience

Graceful failure handling with logging

Architecture

The Adapter Pattern

The email system uses an interface-based design for flexibility:
EmailService.java:1-6
package com.ems.backend.service.email;

public interface EmailService {
    void sendNotification(String to, String subject, String body);
}
This interface allows swapping email providers (Outlook, Gmail, SendGrid, etc.) without changing business logic.

Implementation: Outlook Adapter

The system currently uses an Outlook/Gmail adapter:
OutlookEmailAdapter.java:11-37
@Service
@RequiredArgsConstructor
@Slf4j
public class OutlookEmailAdapter implements EmailService {

    private final JavaMailSender mailSender;

    @Override
    @Async
    public void sendNotification(String to, String subject, String body) {
        try {
            SimpleMailMessage message = new SimpleMailMessage();
            
            message.setFrom("[email protected]"); 
            message.setTo(to);
            message.setSubject("[EMS] " + subject);
            message.setText(body);

            mailSender.send(message);
            log.info("MAIL SUCCESS: Email delivered to {}", to);
        } catch (Exception e) {
            log.error("MAIL FAILURE: Delivery failed for {}. Error: {}", to, e.getMessage());
        }
    }
}

Key Components

@Service
annotation
Marks this as the active email service implementation
@Async
annotation
Enables asynchronous execution (non-blocking)
JavaMailSender
dependency
Spring’s mail sending interface
SimpleMailMessage
class
Plain-text email message builder

Asynchronous Processing

Why Async?

Non-Blocking

API responses return immediately without waiting for email delivery

Better UX

Users don’t wait for slow SMTP operations

Failure Isolation

Email failures don’t cause transaction rollbacks

Scalability

Handle high-volume notifications efficiently

Configuration

Async processing requires Spring’s async configuration:
@Configuration
@EnableAsync
public class AsyncConfig {
    // Async thread pool configuration
}
The @Async annotation on sendNotification() makes the method execute in a separate thread pool.

Email Flow

1

Business Event Occurs

Example: Student successfully registers for an event
2

Service Calls EmailService

emailService.sendNotification(
    student.getEmail(), 
    "Registration Confirmed", 
    emailBody
);
3

Async Execution

Method returns immediately, email processing happens in background
4

Email Delivery

Adapter connects to SMTP server and sends email
5

Logging

Success or failure is logged for monitoring

Usage Examples

Registration Confirmation

RegistrationServiceImpl.java:211-224
private void sendConfirmationEmail(Student student, Event event, String confirmationCode) {
    String emailBody = String.format(
            "Hello %s,%n%nYour registration for '%s' is confirmed.%n" +
                    "Confirmation Number: %s%n" +
                    "Venue: %s%nDate: %s%n%nSee you there!",
            student.getFirstName(),
            event.getTitle(),
            confirmationCode,
            event.getVenue(),
            event.getEventDate().toString()
    );

    emailService.sendNotification(student.getEmail(), "Registration Confirmed", emailBody);
}

Email Format

Subject: [EMS] Registration Confirmed Body:
Hello John,

Your registration for 'React Workshop' is confirmed.
Confirmation Number: REG-A3B7C9D2
Venue: Room 301
Date: 2024-04-15

See you there!
All email subjects are automatically prefixed with [EMS] for easy filtering.

Common Use Cases

Trigger: Student registers for eventRecipients: StudentContent:
  • Event title and details
  • Confirmation number
  • Venue and date
  • Call to action

Configuration

Spring Mail Properties

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: [email protected]
    password: your-app-password
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
Security Best Practice: Never commit email credentials to version control. Use environment variables or secret management systems.

Supported Providers

Gmail

Host: smtp.gmail.com Port: 587

Outlook

Host: smtp.office365.com Port: 587

SendGrid

Host: smtp.sendgrid.net Port: 587

Gmail App Passwords

1

Enable 2FA

Gmail requires 2-factor authentication for app passwords
2

Generate App Password

Google Account → Security → 2-Step Verification → App passwords
3

Use in Configuration

Use the generated 16-character password in spring.mail.password
Never use your actual Gmail password. Always use app-specific passwords.

Error Handling

Graceful Failure

The email service never throws exceptions to calling code:
OutlookEmailAdapter.java:20-36
@Override
@Async
public void sendNotification(String to, String subject, String body) {
    try {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom("[email protected]");
        message.setTo(to);
        message.setSubject("[EMS] " + subject);
        message.setText(body);

        mailSender.send(message);
        log.info("MAIL SUCCESS: Email delivered to {}", to);
    } catch (Exception e) {
        log.error("MAIL FAILURE: Delivery failed for {}. Error: {}", to, e.getMessage());
    }
}
Email failures are logged but don’t affect the main business transaction (e.g., registration still succeeds even if email fails).

Common Errors

Cause: Invalid credentials or app passwordSolution:
  • Verify username and password
  • For Gmail, ensure app password is used
  • Check 2FA is enabled
Cause: Firewall blocking SMTP port or wrong hostSolution:
  • Verify SMTP host and port
  • Check firewall/network settings
  • Ensure TLS/SSL settings match provider requirements
Cause: Malformed email addressSolution:
  • Validate email addresses before sending
  • Check for typos in recipient address
Cause: Sending too many emails too quicklySolution:
  • Implement rate limiting
  • Use batch sending for bulk notifications
  • Consider dedicated email service (SendGrid, SES)

Monitoring

Log Patterns

Success:
INFO  - MAIL SUCCESS: Email delivered to [email protected]
Failure:
ERROR - MAIL FAILURE: Delivery failed for [email protected]. Error: Authentication failed

Monitoring Recommendations

Log Aggregation

Use ELK, Splunk, or CloudWatch to track email metrics

Alerting

Set up alerts for high failure rates

Metrics

Track delivery rate, failure rate, and latency

Dead Letter Queue

Store failed emails for manual review/retry

Extending the System

Adding a New Provider

1

Create New Adapter

@Service
@Primary  // Make this the active implementation
public class SendGridEmailAdapter implements EmailService {
    @Override
    @Async
    public void sendNotification(String to, String subject, String body) {
        // SendGrid implementation
    }
}
2

Configure Provider

Add provider-specific configuration to application.yml
3

Deactivate Old Adapter

Remove @Service from old adapter or use @Primary on new one
4

Test

Verify emails are delivered through new provider

HTML Email Support

To send HTML emails instead of plain text:
@Override
@Async
public void sendNotification(String to, String subject, String htmlBody) {
    try {
        MimeMessage message = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
        
        helper.setFrom("[email protected]");
        helper.setTo(to);
        helper.setSubject("[EMS] " + subject);
        helper.setText(htmlBody, true);  // true = HTML content
        
        mailSender.send(message);
        log.info("MAIL SUCCESS: HTML email delivered to {}", to);
    } catch (Exception e) {
        log.error("MAIL FAILURE: Delivery failed for {}. Error: {}", to, e.getMessage());
    }
}

Template Support

For production systems, consider using email templates:

Thymeleaf

Spring’s recommended template engine

FreeMarker

Powerful template language

Velocity

Lightweight templating

SendGrid Templates

Provider-managed templates

Best Practices

1

Use Environment Variables

Never hardcode email credentials. Use environment variables or secret managers.
2

Implement Idempotency

Prevent duplicate emails by tracking sent notifications.
3

Validate Email Addresses

Validate email addresses before attempting delivery.
4

Monitor Delivery Rates

Track success/failure rates to detect issues early.
5

Implement Retry Logic

For critical notifications, implement retry with exponential backoff.
6

Provide Unsubscribe

For marketing emails, include unsubscribe links (GDPR compliance).

Security Considerations

Critical Security Practices:
  • Never expose email service credentials
  • Validate all user-provided content before including in emails
  • Sanitize HTML content to prevent XSS in HTML emails
  • Rate limit to prevent abuse
  • Use TLS/SSL for SMTP connections

Performance Tips

Batch Sending

For bulk notifications, send in batches to avoid rate limits

Queue System

Use message queues (RabbitMQ, SQS) for high-volume scenarios

Dedicated Service

For production, consider dedicated services like SendGrid or AWS SES

Connection Pooling

Reuse SMTP connections to reduce overhead

Registrations

Learn about registration confirmation emails

API Architecture

Understand async processing patterns

Build docs developers (and LLMs) love