JavaMail API for Sending Emails in Java

Introduction

JavaMail API (now part of Jakarta Mail) provides a platform-independent and protocol-independent framework for building mail and messaging applications. It supports SMTP, POP3, and IMAP protocols for sending and receiving emails.

Maven Dependencies

<!-- Jakarta Mail API -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
<!-- For Spring Boot projects -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- For testing -->
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>1.6.11</version>
<scope>test</scope>
</dependency>

Basic Email Setup

Simple Email Sending

import jakarta.mail.*;
import jakarta.mail.internet.*;
import java.util.Properties;
public class BasicEmailSender {
private final String host;
private final int port;
private final String username;
private final String password;
private final boolean auth;
private final boolean starttls;
public BasicEmailSender(String host, int port, String username, 
String password, boolean auth, boolean starttls) {
this.host = host;
this.port = port;
this.username = username;
this.password = password;
this.auth = auth;
this.starttls = starttls;
}
public void sendSimpleEmail(String to, String subject, String content) 
throws MessagingException {
// Setup mail server properties
Properties props = new Properties();
props.put("mail.smtp.host", host);
props.put("mail.smtp.port", port);
props.put("mail.smtp.auth", String.valueOf(auth));
props.put("mail.smtp.starttls.enable", String.valueOf(starttls));
// Create session with authentication
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
try {
// Create message
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(username));
message.setRecipients(Message.RecipientType.TO, 
InternetAddress.parse(to));
message.setSubject(subject);
message.setText(content);
// Send message
Transport.send(message);
System.out.println("Email sent successfully to: " + to);
} catch (MessagingException e) {
System.err.println("Failed to send email: " + e.getMessage());
throw e;
}
}
// Usage example
public static void main(String[] args) {
BasicEmailSender sender = new BasicEmailSender(
"smtp.gmail.com", 587, "[email protected]", 
"your-app-password", true, true);
try {
sender.sendSimpleEmail(
"[email protected]",
"Test Email Subject",
"This is a test email content."
);
} catch (MessagingException e) {
e.printStackTrace();
}
}
}

Advanced Email Features

HTML Email with Attachments

public class AdvancedEmailSender {
private Session session;
public AdvancedEmailSender(String host, int port, String username, 
String password, boolean useTLS) {
Properties props = new Properties();
props.put("mail.smtp.host", host);
props.put("mail.smtp.port", port);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", String.valueOf(useTLS));
this.session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
// Enable debug mode for troubleshooting
session.setDebug(true);
}
public void sendHtmlEmail(String to, String subject, String htmlContent) 
throws MessagingException {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(session.getProperty("mail.smtp.user")));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
message.setSubject(subject);
// Set HTML content
message.setContent(htmlContent, "text/html; charset=utf-8");
Transport.send(message);
}
public void sendEmailWithAttachment(String to, String subject, String content,
File attachment) throws MessagingException {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(session.getProperty("mail.smtp.user")));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
message.setSubject(subject);
// Create multipart message
Multipart multipart = new MimeMultipart();
// Text part
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText(content);
multipart.addBodyPart(messageBodyPart);
// Attachment part
messageBodyPart = new MimeBodyPart();
DataSource source = new FileDataSource(attachment);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(attachment.getName());
multipart.addBodyPart(messageBodyPart);
message.setContent(multipart);
Transport.send(message);
}
public void sendEmailWithMultipleAttachments(String to, String subject, 
String content, List<File> attachments) 
throws MessagingException {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(session.getProperty("mail.smtp.user")));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
message.setSubject(subject);
Multipart multipart = new MimeMultipart();
// Text part
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(content);
multipart.addBodyPart(textPart);
// Add all attachments
for (File file : attachments) {
MimeBodyPart attachmentPart = new MimeBodyPart();
attachmentPart.attachFile(file);
multipart.addBodyPart(attachmentPart);
}
message.setContent(multipart);
Transport.send(message);
}
public void sendEmailWithInlineImages(String to, String subject, String htmlContent,
Map<String, File> inlineImages) 
throws MessagingException {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(session.getProperty("mail.smtp.user")));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
message.setSubject(subject);
Multipart multipart = new MimeMultipart("related");
// HTML part
BodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(htmlContent, "text/html; charset=utf-8");
multipart.addBodyPart(htmlPart);
// Inline images
for (Map.Entry<String, File> entry : inlineImages.entrySet()) {
BodyPart imagePart = new MimeBodyPart();
imagePart.setDataHandler(new DataHandler(new FileDataSource(entry.getValue())));
imagePart.setHeader("Content-ID", "<" + entry.getKey() + ">");
imagePart.setDisposition(MimeBodyPart.INLINE);
multipart.addBodyPart(imagePart);
}
message.setContent(multipart);
Transport.send(message);
}
}

Email Templates and Builder Pattern

Email Builder for Complex Emails

public class EmailBuilder {
private final Session session;
private String from;
private String[] to;
private String[] cc;
private String[] bcc;
private String subject;
private String textContent;
private String htmlContent;
private List<File> attachments;
private Map<String, File> inlineImages;
private Priority priority = Priority.NORMAL;
public enum Priority {
HIGH("1"), NORMAL("3"), LOW("5");
private final String value;
Priority(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public EmailBuilder(Session session) {
this.session = session;
this.attachments = new ArrayList<>();
this.inlineImages = new HashMap<>();
}
public EmailBuilder from(String from) {
this.from = from;
return this;
}
public EmailBuilder to(String... to) {
this.to = to;
return this;
}
public EmailBuilder cc(String... cc) {
this.cc = cc;
return this;
}
public EmailBuilder bcc(String... bcc) {
this.bcc = bcc;
return this;
}
public EmailBuilder subject(String subject) {
this.subject = subject;
return this;
}
public EmailBuilder textContent(String textContent) {
this.textContent = textContent;
return this;
}
public EmailBuilder htmlContent(String htmlContent) {
this.htmlContent = htmlContent;
return this;
}
public EmailBuilder addAttachment(File file) {
this.attachments.add(file);
return this;
}
public EmailBuilder addInlineImage(String contentId, File image) {
this.inlineImages.put(contentId, image);
return this;
}
public EmailBuilder priority(Priority priority) {
this.priority = priority;
return this;
}
public void send() throws MessagingException {
Message message = new MimeMessage(session);
// Set basic headers
if (from != null) {
message.setFrom(new InternetAddress(from));
}
message.setRecipients(Message.RecipientType.TO, parseAddresses(to));
if (cc != null && cc.length > 0) {
message.setRecipients(Message.RecipientType.CC, parseAddresses(cc));
}
if (bcc != null && bcc.length > 0) {
message.setRecipients(Message.RecipientType.BCC, parseAddresses(bcc));
}
message.setSubject(subject);
message.setHeader("X-Priority", priority.getValue());
message.setSentDate(new Date());
// Build message content
message.setContent(buildContent());
Transport.send(message);
}
private Address[] parseAddresses(String[] addresses) throws AddressException {
if (addresses == null) return new Address[0];
List<Address> addressList = new ArrayList<>();
for (String address : addresses) {
addressList.add(new InternetAddress(address));
}
return addressList.toArray(new Address[0]);
}
private Multipart buildContent() throws MessagingException {
Multipart multipart = new MimeMultipart();
// Handle text and HTML content
if (textContent != null || htmlContent != null) {
MimeBodyPart contentPart = new MimeBodyPart();
if (htmlContent != null && textContent != null) {
// Both text and HTML - create alternative content
Multipart alternative = new MimeMultipart("alternative");
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(textContent);
alternative.addBodyPart(textPart);
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(htmlContent, "text/html; charset=utf-8");
alternative.addBodyPart(htmlPart);
contentPart.setContent(alternative);
} else if (htmlContent != null) {
contentPart.setContent(htmlContent, "text/html; charset=utf-8");
} else {
contentPart.setText(textContent);
}
multipart.addBodyPart(contentPart);
}
// Add attachments
for (File attachment : attachments) {
MimeBodyPart attachmentPart = new MimeBodyPart();
attachmentPart.attachFile(attachment);
multipart.addBodyPart(attachmentPart);
}
// Add inline images
if (!inlineImages.isEmpty()) {
for (Map.Entry<String, File> entry : inlineImages.entrySet()) {
MimeBodyPart imagePart = new MimeBodyPart();
imagePart.setDataHandler(new DataHandler(new FileDataSource(entry.getValue())));
imagePart.setHeader("Content-ID", "<" + entry.getKey() + ">");
imagePart.setDisposition(MimeBodyPart.INLINE);
multipart.addBodyPart(imagePart);
}
}
return multipart;
}
}

Spring Boot Integration

Spring Boot Mail Configuration

@Configuration
@ConfigurationProperties(prefix = "spring.mail")
@Data
public class MailConfig {
private String host;
private int port;
private String username;
private String password;
private String protocol = "smtp";
private Properties properties = new Properties();
@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(host);
mailSender.setPort(port);
mailSender.setUsername(username);
mailSender.setPassword(password);
mailSender.setProtocol(protocol);
mailSender.setJavaMailProperties(properties);
return mailSender;
}
}
// application.yml
spring:
mail:
host: smtp.gmail.com
port: 587
username: [email protected]
password: your-app-password
protocol: smtp
properties:
mail:
smtp:
auth: true
starttls:
enable: true
connectiontimeout: 5000
timeout: 5000
writetimeout: 5000

Spring Service for Email Operations

@Service
@Slf4j
public class EmailService {
private final JavaMailSender mailSender;
private final TemplateEngine templateEngine; // Thymeleaf or FreeMarker
public EmailService(JavaMailSender mailSender, TemplateEngine templateEngine) {
this.mailSender = mailSender;
this.templateEngine = templateEngine;
}
public void sendSimpleMessage(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
message.setFrom(mailSender.getUsername());
try {
mailSender.send(message);
log.info("Email sent successfully to: {}", to);
} catch (MailException e) {
log.error("Failed to send email to: {}", to, e);
throw new EmailException("Failed to send email", e);
}
}
public void sendMimeMessage(String to, String subject, String htmlContent) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo(to);
helper.setSubject(subject);
helper.setText(htmlContent, true);
helper.setFrom(mailSender.getUsername());
mailSender.send(message);
log.info("HTML email sent successfully to: {}", to);
} catch (MessagingException e) {
log.error("Failed to send MIME email to: {}", to, e);
throw new EmailException("Failed to send MIME email", e);
}
}
public void sendTemplatedEmail(String to, String subject, String templateName, 
Map<String, Object> variables) {
try {
// Process template
String htmlContent = templateEngine.process(templateName, 
new Context(Locale.getDefault(), variables));
sendMimeMessage(to, subject, htmlContent);
} catch (Exception e) {
log.error("Failed to send templated email to: {}", to, e);
throw new EmailException("Failed to send templated email", e);
}
}
public void sendMessageWithAttachment(String to, String subject, String text,
String attachmentPath) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(text);
helper.setFrom(mailSender.getUsername());
// Add attachment
FileSystemResource file = new FileSystemResource(new File(attachmentPath));
helper.addAttachment(Objects.requireNonNull(file.getFilename()), file);
mailSender.send(message);
log.info("Email with attachment sent to: {}", to);
} catch (MessagingException e) {
log.error("Failed to send email with attachment to: {}", to, e);
throw new EmailException("Failed to send email with attachment", e);
}
}
public void sendBulkEmails(List<String> recipients, String subject, 
String templateName, Map<String, Object> variables) {
for (String recipient : recipients) {
try {
sendTemplatedEmail(recipient, subject, templateName, variables);
// Small delay to avoid being flagged as spam
Thread.sleep(100);
} catch (Exception e) {
log.error("Failed to send bulk email to: {}", recipient, e);
// Continue with other emails even if one fails
}
}
}
}
// Custom exception
class EmailException extends RuntimeException {
public EmailException(String message) {
super(message);
}
public EmailException(String message, Throwable cause) {
super(message, cause);
}
}

Asynchronous Email Sending

Async Email Service

@Service
@Slf4j
public class AsyncEmailService {
private final JavaMailSender mailSender;
private final TaskExecutor taskExecutor;
public AsyncEmailService(JavaMailSender mailSender, 
@Qualifier("taskExecutor") TaskExecutor taskExecutor) {
this.mailSender = mailSender;
this.taskExecutor = taskExecutor;
}
@Async
public CompletableFuture<Void> sendEmailAsync(String to, String subject, String text) {
return CompletableFuture.runAsync(() -> {
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
message.setFrom(mailSender.getUsername());
mailSender.send(message);
log.info("Async email sent successfully to: {}", to);
} catch (Exception e) {
log.error("Failed to send async email to: {}", to, e);
throw new EmailException("Failed to send async email", e);
}
}, taskExecutor);
}
public void sendEmailWithCallback(String to, String subject, String text) {
taskExecutor.execute(() -> {
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
message.setFrom(mailSender.getUsername());
mailSender.send(message);
log.info("Callback email sent successfully to: {}", to);
} catch (Exception e) {
log.error("Failed to send callback email to: {}", to, e);
}
});
}
}
// Configuration for async support
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("taskExecutor")
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("EmailExecutor-");
executor.initialize();
return executor;
}
}

Email Validation and Utilities

Email Validation Service

@Service
public class EmailValidationService {
private static final Pattern EMAIL_PATTERN = 
Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
public boolean isValidEmail(String email) {
if (email == null || email.trim().isEmpty()) {
return false;
}
Matcher matcher = EMAIL_PATTERN.matcher(email);
return matcher.matches();
}
public List<String> validateEmailList(List<String> emails) {
return emails.stream()
.filter(this::isValidEmail)
.collect(Collectors.toList());
}
public void validateEmailOrThrow(String email) {
if (!isValidEmail(email)) {
throw new IllegalArgumentException("Invalid email address: " + email);
}
}
}
// Email utility class
@Component
public class EmailUtils {
public InternetAddress[] parseAddressList(String addresses) throws AddressException {
return InternetAddress.parse(addresses);
}
public String generateMessageId(String domain) {
return "<" + UUID.randomUUID() + "@" + domain + ">";
}
public Map<String, String> parseEmailHeaders(Message message) throws MessagingException {
Map<String, String> headers = new HashMap<>();
Enumeration<Header> allHeaders = message.getAllHeaders();
while (allHeaders.hasMoreElements()) {
Header header = allHeaders.nextElement();
headers.put(header.getName(), header.getValue());
}
return headers;
}
public void addCustomHeaders(Message message, Map<String, String> headers) 
throws MessagingException {
for (Map.Entry<String, String> entry : headers.entrySet()) {
message.addHeader(entry.getKey(), entry.getValue());
}
}
}

Testing Email Functionality

Test Configuration with GreenMail

@SpringBootTest
@TestPropertySource(properties = {
"spring.mail.host=127.0.0.1",
"spring.mail.port=3025",
"spring.mail.username=test@localhost",
"spring.mail.password=test"
})
public class EmailServiceTest {
@Autowired
private EmailService emailService;
@RegisterExtension
static GreenMailExtension greenMail = new GreenMailExtension(ServerSetupTest.SMTP)
.withConfiguration(GreenMailConfiguration.aConfig().withUser("test", "test"))
.withPerMethodLifecycle(false);
@Test
public void testSendSimpleEmail() throws Exception {
// Given
String to = "[email protected]";
String subject = "Test Subject";
String text = "Test Content";
// When
emailService.sendSimpleMessage(to, subject, text);
// Then
await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> {
MimeMessage[] receivedMessages = greenMail.getReceivedMessages();
assertThat(receivedMessages).hasSize(1);
MimeMessage receivedMessage = receivedMessages[0];
assertThat(receivedMessage.getSubject()).isEqualTo(subject);
assertThat(receivedMessage.getAllRecipients()[0].toString()).isEqualTo(to);
});
}
@Test
public void testSendEmailWithAttachment() throws Exception {
// Given
String to = "[email protected]";
String subject = "Test Attachment";
String text = "Test Content with Attachment";
String attachmentPath = "test-file.txt";
// Create test file
Files.write(Paths.get(attachmentPath), "Test attachment content".getBytes());
// When
emailService.sendMessageWithAttachment(to, subject, text, attachmentPath);
// Then
await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> {
MimeMessage[] receivedMessages = greenMail.getReceivedMessages();
assertThat(receivedMessages).hasSize(1);
MimeMessage receivedMessage = receivedMessages[0];
assertThat(receivedMessage.getSubject()).isEqualTo(subject);
// Verify attachment
assertThat(receivedMessage.getContent()).isInstanceOf(MimeMultipart.class);
MimeMultipart multipart = (MimeMultipart) receivedMessage.getContent();
assertThat(multipart.getCount()).isGreaterThan(1);
});
// Cleanup
Files.deleteIfExists(Paths.get(attachmentPath));
}
}
// Unit test for validation
@Test
public void testEmailValidation() {
EmailValidationService validator = new EmailValidationService();
assertTrue(validator.isValidEmail("[email protected]"));
assertTrue(validator.isValidEmail("[email protected]"));
assertFalse(validator.isValidEmail("invalid-email"));
assertFalse(validator.isValidEmail(""));
assertFalse(validator.isValidEmail(null));
}

Best Practices and Production Considerations

Production-Ready Email Service

@Service
@Slf4j
public class ProductionEmailService {
private final JavaMailSender mailSender;
private final EmailValidationService validationService;
private final RetryTemplate retryTemplate;
public ProductionEmailService(JavaMailSender mailSender,
EmailValidationService validationService) {
this.mailSender = mailSender;
this.validationService = validationService;
this.retryTemplate = createRetryTemplate();
}
public EmailResult sendEmailWithRetry(EmailRequest request) {
try {
validationService.validateEmailOrThrow(request.getTo());
return retryTemplate.execute(context -> {
try {
sendEmailInternal(request);
return EmailResult.success(request.getTo());
} catch (Exception e) {
log.warn("Attempt {} failed for email to: {}", 
context.getRetryCount(), request.getTo(), e);
if (context.getRetryCount() >= 2) {
return EmailResult.failure(request.getTo(), e.getMessage());
}
throw e;
}
});
} catch (Exception e) {
log.error("Failed to send email after retries to: {}", request.getTo(), e);
return EmailResult.failure(request.getTo(), e.getMessage());
}
}
private void sendEmailInternal(EmailRequest request) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo(request.getTo());
helper.setSubject(request.getSubject());
helper.setText(request.getContent(), request.isHtml());
helper.setFrom(mailSender.getUsername());
if (request.getCc() != null) {
helper.setCc(request.getCc());
}
if (request.getBcc() != null) {
helper.setBcc(request.getBcc());
}
// Add attachments
if (request.getAttachments() != null) {
for (EmailAttachment attachment : request.getAttachments()) {
helper.addAttachment(attachment.getFilename(), 
new ByteArrayResource(attachment.getContent()));
}
}
mailSender.send(message);
}
private RetryTemplate createRetryTemplate() {
RetryTemplate template = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(1000);
backOffPolicy.setMultiplier(2.0);
backOffPolicy.setMaxInterval(10000);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(3);
template.setBackOffPolicy(backOffPolicy);
template.setRetryPolicy(retryPolicy);
return template;
}
// DTO classes
@Data
public static class EmailRequest {
private String to;
private String subject;
private String content;
private boolean html = false;
private String[] cc;
private String[] bcc;
private List<EmailAttachment> attachments;
}
@Data
public static class EmailAttachment {
private String filename;
private byte[] content;
private String contentType;
}
@Data
public static class EmailResult {
private final String recipient;
private final boolean success;
private final String errorMessage;
private final Instant timestamp;
public static EmailResult success(String recipient) {
return new EmailResult(recipient, true, null, Instant.now());
}
public static EmailResult failure(String recipient, String errorMessage) {
return new EmailResult(recipient, false, errorMessage, Instant.now());
}
}
}

Conclusion

JavaMail API provides comprehensive email functionality for Java applications. Key points to remember:

  • Use Jakarta Mail for new projects (the successor to JavaMail)
  • Configure proper SMTP settings including authentication and TLS
  • Handle attachments and HTML content appropriately
  • Implement error handling and retry mechanisms for production
  • Use Spring Boot's mail support for easier configuration
  • Test email functionality with tools like GreenMail
  • Follow email best practices to avoid being flagged as spam

By following these patterns and best practices, you can build robust, production-ready email functionality in your Java applications.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper