This section documents breaking changes between Micronaut Email versions:
Micronaut Email 3.0.0
The previously deprecated constructor io.micronaut.email.Attachment(String, String, byte[], String) has been removed. Use Attachment(String, String, byte[], String, String) instead.
The previously deprecated class io.micronaut.email.validation.AnyRecipientConstraintValidationFactory has been removed. The @Factory annotation was intentionally removed. Thus, this class does nothing. AnyRecipientValidator is used instead.
Micronaut Email 2.0.0
Micronaut Email 2.0.0 migrates to Jakarta Mail package namespaces, from javax.mail to jakarta.mail. Moreover, it uses transitive dependency jakarta.mail:jakarta.mail-api instead of com.sun.mail:javax.mail. Jakarta Mail also separates API and Implementation. Previous implementation sources are now part of the Eclipse Angus project, the direct successor to JavaMail/JakartaMail. In addition to jakarta-mail-api an additional dependency on org.eclipse.angus:angus-mail is required. Note that for Eclipse Angus, module and package prefixes changed from com.sun.mail to org.eclipse.angus.mail.
This release also updates the ActiveCampaign Postmark library from 1.8.x to 1.9.0. Although it’s a minor revision otherwise it refactors package names and the dependency groupId from com.wildbit.java to com.postmarkapp.
3 Release History
For this project, you can find a list of releases (with release notes) here:
If you send emails always from the same email address you can specify it via configuration and skip populating the from field when you build the Email.
By setting micronaut.email.from.email, Micronaut Email registers a bean of type FromDecorator which populates the from field if not specified in the construction of the Email.
Moreover, if you have a custom need (e.g. always bcc an email address, adding a prefix to the email subject in a particular environment), you can register a bean of type EmailDecorator.
7 Customizing Emails
If you have a requirement to customize the email being sent — for example adding headers to the email — then you can use the Consumer variant of EmailSender.
Here we show an example for JavaMail using the jakarta.mail.Message class, but this can be altered to the Mail platform request class you are using.
In addition, you will need to provide a runtime dependency on an implementation of the Jakarta Mail api. Eclipse Angus is the direct successor to prior versions of JavaMail/JakartaMail.
Micronaut Email can create beans of type MailPropertiesProvider and SessionProvider from javamail.properties.
Configure the Jakarta Mail properties required by your transport, such as SMTP host, port, and TLS settings:
Mailpit is a small, fast, low-memory, zero-dependency, multi-platform email testing tool and API for developers.
It acts as an SMTP server, provides a modern web interface to view and test captured emails, and provides an API for automated integration testing.
9.7.1 Mailpit for Development
Given a class like this, which is transactional email provider-agnostic:
@Requires(property = "spec.name", value = "OrderServiceTest")@Singletonpublic class OrderService { private final EmailSender<?, ?> emailSender; public OrderService(EmailSender<?, ?> emailSender) { this.emailSender = emailSender; } public void sendOrderEmail(@jakarta.validation.constraints.Email String recipient, @NotBlank String orderNumber) { String text = "We have received your order " + orderNumber + ". You will receive your product soon."; String html = "<html><body><p>" + text + "</p></body></html>"; emailSender.send(Email.builder() .to(recipient) .subject("Order Number: " + orderNumber) .body(html, text)); }}
imports
from typing import Annotatedfrom jakarta.inject import Singletonfrom jakarta.validation.constraints import Email as EmailAddress, NotBlankfrom micronaut.context.annotation import Requiresfrom micronaut.email import Email, EmailSender
@Requires(property="spec.name", value="OrderServiceTest")@Singletonclass OrderService: def __init__(self, email_sender: EmailSender): self.email_sender = email_sender def send_order_email(self, recipient: Annotated[str, EmailAddress], order_number: Annotated[str, NotBlank]) -> None: text = "We have received your order " + order_number + ". You will receive your product soon." html = "<html><body><p>" + text + "</p></body></html>" self.email_sender.send(Email.builder() .to(recipient) .subject("Order Number: " + order_number) .body(html, text))
imports
package io.micronaut.email.docs.mailpitimport io.micronaut.context.annotation.Requiresimport io.micronaut.email.Emailimport io.micronaut.email.EmailSenderimport jakarta.inject.Singletonimport jakarta.validation.constraints.NotBlankimport jakarta.validation.constraints.Email as EmailConstraint
@Requires(property = "spec.name", value = "OrderServiceTest")@Singletonclass OrderService(private val emailSender: EmailSender<*, *>) { fun sendOrderEmail( @EmailConstraint recipient: String, @NotBlank orderNumber: String ) { val text = "We have received your order $orderNumber. You will receive your product soon." val html = "<html><body><p>$text</p></body></html>" emailSender.send( Email.builder() .to(recipient) .subject("Order Number: $orderNumber") .body(html, text) ) }}
imports
package io.micronaut.email.docs.mailpitimport io.micronaut.context.annotation.Requiresimport io.micronaut.email.Emailimport io.micronaut.email.EmailSenderimport jakarta.inject.Singletonimport jakarta.validation.constraints.Email as EmailConstraintimport jakarta.validation.constraints.NotBlank
@Requires(property = "spec.name", value = "OrderServiceTest")@Singletonclass OrderService { private final EmailSender<?, ?> emailSender OrderService(EmailSender<?, ?> emailSender) { this.emailSender = emailSender } void sendOrderEmail(@EmailConstraint String recipient, @NotBlank String orderNumber) { String text = "We have received your order ${orderNumber}. You will receive your product soon." String html = "<html><body><p>${text}</p></body></html>" emailSender.send(Email.builder() .to(recipient) .subject("Order Number: ${orderNumber}") .body(html, text)) }}
You could be sending emails in production with Amazon SES, Postmark, SendGrid, or Mailjet,
and you may wish to send emails to Mailpit while developing the application using Jakarta Mail.
First, run Mailpit. For example, you can run it with Docker:
docker run --rm --name mailpit -p 1025:1025 -p 8025:8025 axllent/mailpit
Then, if you run with the dev environment as the default environment,
add a development-only configuration file (e.g., application-dev.properties) that configures Jakarta Mail to send emails through Mailpit:
The Mailpit HTTP client module provides a declarative HTTP client for Mailpit API v1.
It is useful for test assertions and development tooling that need to inspect messages captured by Mailpit.
The module exposes MailpitClient, a Micronaut HTTP Client declared with the service ID mailpit.
Configure the named HTTP service to point at the Mailpit web UI and API port:
# The Mailpit Testcontainers container properties (SMTP host/port and the Mailpit HTTP client URL) are supplied# by the Java io.micronaut.email.docs.MailpitTestConfigurer application context configurer of the "mailpit"# environment: Micronaut Test calls TestPropertyProvider before the context, and with it the GraalPy runtime,# exists, so a Python test class cannot provide them.@Property(name="spec.name", value="OrderServiceTest")@Property(name="micronaut.email.from.email", value="info@micronaut.io")@MicronautTest(startApplication=False, environments=["mailpit"])class OrderServiceTest: order_service: Annotated[OrderService, Inject] client: Annotated[MailpitClient, Inject] from_configuration: Annotated[FromConfiguration, Inject] @AfterAll @staticmethod def cleanup_spec() -> None: Mailpit.close() @Test def order_service(self): Assumptions.assumeTrue(DockerClientFactory.instance().isDockerAvailable(), "Docker is not available") recipient = "example@micronaut.io" order_number = str(UUID.randomUUID().toString()) text = "We have received your order " + order_number + ". You will receive your product soon." html = "<html><body><p>" + text + "</p></body></html>" self.order_service.send_order_email(recipient, order_number) message = self.client.getMessage("latest") assert message is not None assert message.from_() is not None assert message.to() is not None sender = message.from_() to = message.to() assert self.from_configuration.getFrom().getEmail() == sender.address() assert [recipient] == [address.address() for address in to] assert "Order Number: " + order_number == message.subject() assert text == message.text() assert html == message.html()
@Property(name = "spec.name", value = "OrderServiceTest")@Property(name = "micronaut.email.from.email", value = "info@micronaut.io")@spock.lang.Requires({ org.testcontainers.DockerClientFactory.instance().isDockerAvailable() })@MicronautTest(startApplication = false)class OrderServiceTest extends Specification implements TestPropertyProvider { @Inject OrderService orderService @Inject MailpitClient client @Inject FromConfiguration fromConfiguration @Override Map<String, String> getProperties() { Mailpit.getProperties() } void cleanupSpec() { Mailpit.close() } void "order service"() { given: String recipient = "example@micronaut.io" String orderNumber = UUID.randomUUID().toString() String text = "We have received your order ${orderNumber}. You will receive your product soon." String html = "<html><body><p>${text}</p></body></html>" when: orderService.sendOrderEmail(recipient, orderNumber) MailpitMessage message = client.getMessage("latest") then: message message.from() message.to() when: MailpitAddress from = message.from() List<MailpitAddress> to = message.to() then: fromConfiguration.from.email == from.address() [recipient] == to*.address() "Order Number: ${orderNumber}" == message.subject() text == message.text() html == message.html() } static class Mailpit { private static GenericContainer<?> container = new GenericContainer(DockerImageName.parse("axllent/mailpit")) .withExposedPorts(1025, 8025) .waitingFor(Wait.forHttp("/").forPort(8025)) static Map<String, String> getProperties() { getProperties(getRunningContainer()) } private static GenericContainer<?> getRunningContainer() { if (!container.isRunning()) { container.start() } container } static Map<String, String> getProperties(GenericContainer<?> container) { [ "javamail.properties.mail.smtp.host": container.getHost(), "javamail.properties.mail.smtp.port": "${container.getMappedPort(1025)}", "micronaut.http.services.mailpit.url": "http://${container.getHost()}:${container.getMappedPort(8025)}" ] } static void close() { container.close() } }}
Micronaut Test calls TestPropertyProvider before the application context (and with it the GraalPy runtime) exists, so the Python test selects a mailpit environment and a small Java @ContextConfigurer class implementing ApplicationContextConfigurer supplies the SMTP host/port and the Mailpit HTTP client URL of the Testcontainers container for that environment.
10 Guides
See the following list of guides to learn more about working with email in the Micronaut Framework: