Java Mail Best Practices
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):
- Enable 2FA on your Google account
- Generate an App Password at myaccount.google.com
- 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
- Never hardcode SMTP credentials — use env vars or secrets managers.
- Set up SPF/DKIM/DMARC or your emails will land in spam.
- Use Mailtrap in development — never accidentally email real users.
- Rate limit sends to protect your sender reputation.
- Test HTML across email clients — they all render differently.
- 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
JavaMailSenderwith 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.