Helidon and Jakarta Mail API

Lukas Jungmann
Helidon
Published in
4 min readJan 27, 2023

--

One of the most recent new feature of Angus Mail, the successor of JavaMail and later Jakarta Mail, is its built-in support for the GraalVM native image. In this article, we’ll demonstrate how you can easily leverage this functionality in a Helidon application.

Set up a project

For this tutorial, we’ll use the Helidon SE quick-start archetype:

mvn -U archetype:generate -DinteractiveMode=false \
-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-quickstart-se \
-DarchetypeVersion=3.1.0 \
-DgroupId=io.helidon.samples \
-DartifactId=angus-mail-demo \
-Dpackage=io.helidon.samples.mail

Add Dependencies

Next, add a dependency on Jakarta Mail API. We also need an implementation for the runtime. For that, we use Eclipse Angus. Eclipse Angus project provides multiple jar files suitable for various usages. Since we aim for working with emails in a native microservice and we want our binary to be as small as possible, we pick up support only for protocols we really need. To be able to send emails, we need org.eclipse.angus:smtp protocol handler. To be able to read emails, we needorg.eclipse.angus:imap. In order to use Gmail, we also need org.eclipse.angus:gimap. To use these dependencies, add them all as dependencies in the pom file of the project we’ve created in the previous step:

<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>smtp</artifactId>
<version>2.0.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>imap</artifactId>
<version>2.0.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>gimap</artifactId>
<version>2.0.0</version>
<scope>runtime</scope>
</dependency>

This also brings in all other required dependencies we need, so we do not need to define them explicitly, yet we need to use the right version of the Jakarta Activation API required by Angus Mail. To use it, add following property definition to the pom file of the project to override the version of Helidon:

<properties>
...
<version.lib.jakarta.activation-api>2.1.1</version.lib.jakarta.activation-api>
</properties>

Create a Service

The service — MailService — we want to create will provide two operations. First will send simple email to the sender and the second will allow one to search given string in the email INBOX. Let’s create a skeleton with a helper method getMailSession method returning jakarta.mail.Session, an entry point to Jakarta Mail API, both our operations will need as follows:

package io.helidon.samples.mail;

import io.helidon.common.http.Http;
import io.helidon.webserver.*;
import jakarta.json.*;
import jakarta.mail.*;
import jakarta.mail.internet.*;
import jakarta.mail.search.*;
import java.util.*;

public class MailService implements Service {

private final Properties mailProperties;

MailService(Properties mailProperties) {
this.mailProperties = new Properties(mailProperties);
}

@Override
public void update(Routing.Rules rules) {
rules
.get("/search", this::search)
.get("/send", this::send);
}

private Session getMailSession(Properties properties) {
return Session.getDefaultInstance(mailProperties, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
mailProperties.getProperty("mail.username"),
mailProperties.getProperty("mail.password"));
}
});
}
}

and also put username and password for the mail server in use to application.yaml:

mail:
username: "...@gmail.com"
password: "..."
# debug: "true" #show mail related debug info

Send an Email

Sending an email requires setting up the outgoing SMTP server, so let’s put following into application.yaml as well:

mail:
...
smtp:
host: "smtp.gmail.com"
port: "465"
auth: "true"
ssl:
enable: "true"
socketFactory:
port: "465"
class: "javax.net.ssl.SSLSocketFactory"

Don’t forget to put in your own username for GMail. If your account is configured to use 2-step-verification, you also have to use App Password — see Sign in with App Passwords in the documentation for GMail to set it up.

Time to write the code which actually sends the email. First, we need to get the Session object needed for creating a MimeMessage. After setting necessary options on it, we can use a Transport to send the message:

private void send(ServerRequest request, ServerResponse response) {
String user = mailProperties.getProperty("mail.username");
Session session = getMailSession(mailProperties);
try {
InternetAddress userEmail = new InternetAddress(user);
MimeMessage msg = new MimeMessage(session);
msg.setFrom(user);
msg.setSender(userEmail);
msg.setReplyTo(new InternetAddress[] {userEmail});
msg.setRecipients(Message.RecipientType.TO,
new InternetAddress[] {userEmail});
msg.setSubject("Greetings from Helidon!");
msg.setText("Sent by Angus Mail/Helidon.");
Transport.send(msg);
} catch (MessagingException ex) {
throw new RuntimeException(ex);
}
JsonObject returnObject = Json.createBuilderFactory(
Collections.emptyMap()).createObjectBuilder()
.add("message", "email has been sent!")
.build();
response.status(Http.Status.OK_200).send(returnObject);
}

Now we can make the MailService responding to requests on Helidon WebServer by updating createRouting method we have in Main class:

private static Routing createRouting(Config config) {
...
return Routing.builder()
...
.register(new MailService(
config.get("mail").as(Properties.class).get()))
.build();
}

Build and run the project

> mvn package -Pnative-image
> ./target/angus-mail-demo

To verify the service is up, running and actually sending emails, let’s ping it:

> curl -X GET http://localhost:8080/send
{"message":"email has been sent!"}

Searching INBOX

Next, we want to search the INBOX for an arbitrary string. Let’s add a method handling the search operation. This method connects to the INBOX folder on our mail server, looks for all messages having a word helidon in its subject and returns a Json Object containing found subjects together with corresponding senders to the caller . The code for this looks as follows:

private void search(ServerRequest request, ServerResponse response) {
Session session = getMailSession(mailProperties);
Optional<String> term = request.queryParams().first("term");
Store store;
Folder inbox = null;
try {
store = session.getStore("gimap");
if (!store.isConnected()) {
store.connect();
}

inbox = store.getFolder("INBOX");
SearchTerm searchTerm = new SubjectTerm(term.orElse("helidon"));
inbox.open(Folder.READ_ONLY);
Message[] msgs = inbox.search(searchTerm);
JsonArrayBuilder ab = Json.createBuilderFactory(
Collections.emptyMap()).createArrayBuilder();
for (Message m : msgs) {
JsonObjectBuilder ob = Json.createBuilderFactory(
Collections.emptyMap()).createObjectBuilder();
ob.add("from", m.getFrom()[0].toString())
.add("subject", m.getSubject());
ab.add(ob.build());
}
JsonObject returnObject = Json.createBuilderFactory(
Collections.emptyMap()).createObjectBuilder()
.add("message", "OK")
.add("count", msgs.length)
.add("result", ab)
.build();
response.status(Http.Status.OK_200).send(returnObject);
} catch (MessagingException e) {
response.send(e);
} finally {
if (inbox != null && inbox.isOpen()) {
try {
inbox.close();
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
}
}

And build and run our app:

> mvn package -Pnative-image
> ./target/angus-mail-demo

Now its time for the search test:

> curl -X GET http://localhost:8080/search?term=helidon
{"message":"OK","count":1,"result":[{"from":"...","subject":"Greetings from Helidon!"}]}

Conclusion

In this article we’ve shown how to use Jakarta Mail APIs within a Helidon service on a GraalVM native image. Full code used in this article is available on Github.

To stay informed about the latest Helidon news — follow our official Twitter, Mastodon and LinkedIn for updates! If you have further questions, please reach out to us at helidon.slack.com or https://stackoverflow.com/tags/helidon.

--

--