Java Mail Best Practices

4 minute read

Sending email from code is easy. Sending email reliably, securely, and without landing in spam takes discipline. This chapter covers production-ready best practices.


1. Never Hardcode Credentials

// ❌ NEVER do this
props.put("mail.smtp.user", "myemail@gmail.com");
props.put("mail.smtp.password", "mypassword123");

// ✅ Use environment variables
String username = System.getenv("MAIL_USERNAME");
String password = System.getenv("MAIL_PASSWORD");

Spring Boot — Use Properties + Env Variables

spring:
  mail:
    username: ${MAIL_USERNAME}
    password: ${MAIL_PASSWORD}

Production: Use a secrets manager (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault).


2. Use App Passwords or OAuth2 for Gmail

App Passwords (simple):

  1. Enable 2FA on your Google account
  2. Generate an App Password at myaccount.google.com
  3. Use the 16-character App Password, not your real password

OAuth2 (production):

props.put("mail.smtp.auth.mechanisms", "XOAUTH2");
props.put("mail.smtp.sasl.enable", "true");
props.put("mail.smtp.sasl.mechanisms", "XOAUTH2");

For production, prefer AWS SES, SendGrid, or Mailgun over Gmail — they’re designed for application email.


3. SPF, DKIM, and DMARC — Email Authentication

Without these, your emails land in spam.

Record Purpose DNS Entry
SPF Lists authorized sending servers v=spf1 include:_spf.google.com ~all
DKIM Cryptographic signature proving email isn’t forged Public key in DNS
DMARC Tells receiving servers what to do with unauthenticated email v=DMARC1; p=reject; rua=mailto:dmarc@myapp.com

Verify Setup

# Check SPF
dig TXT myapp.com | grep spf

# Check DKIM
dig TXT selector._domainkey.myapp.com

# Check DMARC
dig TXT _dmarc.myapp.com

If you use SendGrid or AWS SES, they guide you through DNS setup.


4. Rate Limiting

SMTP providers have sending limits. Exceeding them gets you flagged as spam:

Provider Limit
Gmail 500/day (free), 2,000/day (Workspace)
Amazon SES Starts at 200/day, increases with reputation
SendGrid Varies by plan

Implement Rate Limiting

private final RateLimiter rateLimiter = RateLimiter.create(10.0); // 10 emails/sec

public void sendEmail(String to, String subject, String html) {
    rateLimiter.acquire();  // blocks if over limit
    // ... send email
}

5. HTML Email Testing

Email clients render HTML differently. Test before sending:

Tool Purpose
Mailtrap Catch all emails in dev/staging
Litmus Preview in 90+ email clients
Email on Acid Cross-client rendering tests
mail-tester.com Spam score checker

Mailtrap in Development

# application-dev.yml — emails captured, never delivered
spring:
  mail:
    host: sandbox.smtp.mailtrap.io
    port: 2525

6. Error Handling and Retry

@Retryable(
    retryFor = {MailSendException.class, MailAuthenticationException.class},
    maxAttempts = 3,
    backoff = @Backoff(delay = 2000, multiplier = 2)
)
public void sendEmail(String to, String subject, String html) {
    // ... send
}

@Recover
public void handleFailure(Exception e, String to, String subject, String html) {
    log.error("Email permanently failed for {}: {}", to, e.getMessage());
    alertService.notify("Email delivery failure: " + to);
    deadLetterRepository.save(new FailedEmail(to, subject, html, e.getMessage()));
}

7. Monitoring and Alerting

Track these metrics:

Metric Alert Threshold
Send success rate < 95%
Average send latency > 5 seconds
Queue depth (if queued) > 1000
Bounce rate > 5%
Spam complaint rate > 0.1%

Spring Boot Actuator + Micrometer

private final MeterRegistry meterRegistry;

public void sendEmail(String to, String subject, String html) {
    Timer.Sample sample = Timer.start(meterRegistry);
    try {
        // ... send email
        meterRegistry.counter("email.sent", "status", "success").increment();
    } catch (Exception e) {
        meterRegistry.counter("email.sent", "status", "failure").increment();
        throw e;
    } finally {
        sample.stop(meterRegistry.timer("email.send.duration"));
    }
}

8. Content Best Practices

Do Don’t
Include unsubscribe link (CAN-SPAM) Send without opt-out
Set Reply-To to a monitored address Use noreply@ without alternative
Use inline CSS in HTML Rely on external CSS
Include plain text fallback Send HTML-only
Test on multiple email clients Assume Gmail rendering is universal
Include physical address (CAN-SPAM) Omit required legal info

9. Security Checklist

  • Credentials in environment variables or secrets manager
  • App Passwords or OAuth2 (never real passwords)
  • TLS/SSL enabled for SMTP connections
  • SPF, DKIM, DMARC configured in DNS
  • No user input directly in email headers (injection prevention)
  • Rate limiting to avoid spam flagging
  • Mailtrap in dev/staging (no accidental production emails)
  • Input validation on email addresses
  • Logging (no PII — log delivery status, not content)

Email Header Injection Prevention

// ❌ Vulnerable — user input in headers
message.setSubject(userInput);  // could contain \r\n to inject headers

// ✅ Sanitize
String safeSubject = userInput.replaceAll("[\\r\\n]", " ");
message.setSubject(safeSubject);

Quick Reference

Topic Recommendation
Credentials Environment variables or secrets manager
SMTP provider (dev) Mailtrap
SMTP provider (prod) AWS SES, SendGrid, Mailgun
Authentication SPF + DKIM + DMARC
HTML emails Inline CSS, plain text fallback, test across clients
Sending pattern Async + retry + queue
Monitoring Success rate, latency, bounces, spam complaints

Key Takeaways

  1. Never hardcode SMTP credentials — use env vars or secrets managers.
  2. Set up SPF/DKIM/DMARC or your emails will land in spam.
  3. Use Mailtrap in development — never accidentally email real users.
  4. Rate limit sends to protect your sender reputation.
  5. Test HTML across email clients — they all render differently.
  6. Monitor delivery rates and alert on failures.

Module Complete!

Congratulations! You’ve completed the Java Mail module. You now know how to:

  • ✅ Send plain text, HTML, and attachment emails with JavaMail
  • ✅ Build dynamic templates with Thymeleaf
  • ✅ Use Spring Boot’s JavaMailSender with profiles
  • ✅ Read emails via IMAP
  • ✅ Queue emails with async, retry, and message brokers
  • ✅ Secure and monitor email delivery

Next Module: Java Testing — JUnit 5, Mockito, integration testing, and TDD.

Updated: