CDI & EJB: Sending asynchronous mail on transaction success

Hello again! 🙂

This time I’ve chosen a common task that most of the time, in my opinion, is done the wrong way: sending e-mails. Not that people can’t figure out how e-mail APIs work, such as JavaMail or Apache’s commons-email. What I usually see as a problem is that they underestimate the need to make the sending mail routine asynchronous, and that it also should only run when the underlying transaction commits successfully (most of the time).

Think of the common use case where a user is shopping online. When he’s done, he will probably want to receive an order confirmation e-mail. The process of making an order is kinda complex: We would usually insert records in a lot of different tables, also possibly delete records to remove items from stock and so forth. All of this, of course, has to be accomplished in a single atomic transaction:

//A sample EJB method
//(using CMT for transaction management)
public void saveOrder() {
    //saving some products

    //removing them from stock

    //and at last, we have to send that email
    sendOrderConfirmationMail(); //the transaction has not yet been commited by this point

Much like the pseudocode above, we usually strive to keep transaction logic out of our code. That is, we use CMT (container managed transactions) to have the container do everything for us and keep our code cleaner. So RIGHT AFTER our method call completes, the EJB container commits our transaction. This is problem number 1: When the sendOrderConfirmationMail() method gets called, we have no way of knowing if the transaction will succeed. The user might receive a confirmation for an order that doesn’t exist.

If this is something you have no yet realized, just run a test in any of your codes. Those calls to entityManager.persist() don’t trigger any database commands until our enclosing method call is over. Just put a breakpoint and see for yourself. I’ve seen confusions like these many times.

So in case of a rollback, we do not need to send any emails. Things can go wrong for a number of reasons: system failure, some business rule could deny the purchase, credit card validation, etc.

So we already know that when using CMT we can have a hard time knowing when the transaction is successful or not. The next problem is making the mailing routine asynchronous, completely independent of our order routine. Picture this, what if everything goes fine with the ordering process but some exception occurs when trying to send the email? Should we rollback everything just because our confirmation mail could not be sent? Should we really prevent the user from buying at our store, just because our mail server is having a bad day?

I know that business requirements like this can go either way, but also keep in mind that it’s usually desirable to make the inherent latency of sending mails not interfere with the order processing. Most of the time, processing the order is our main goal. Low priority tasks like sending emails can even be postponed to times when the server load is low.

Here We Go

To tackle this problem I’ve chosen a pure Java EE approach. No third-party APIs need be used. Our environment comprises:

  • JDK 7 or above.
  • Java EE 7 (JBoss Wildfly 8.1.0)
  • CDI 1.1
  • EJB 3.2
  • JavaMail 1.5

I’ve set up a small web project so you can see everything working, download it here if you want.

Before diving into code, just a brief observation: The solution shown below consists mainly on CDI events mixed with EJB async calls. This is because the CDI 1.1 spec doesn’t provide async event processing. It seems that is something being discussed for the CDI 2.0 spec, still on the works. For this reason, a pure CDI approach might be tricky. I’m not saying it’s impossible, I just haven’t even tried.

The code example is just a make believe for a “Register Customer” use case. Where we would send an email to confirm user registration. The overall architecture looks something like this:


The code sample also presents a “fail test case”, so you can actually see that when there’s a rollback no email is sent. I’m only showing you here the “happy path”, starting with the Managed Bean invoking our CustomerService EJB. Nothing interesting, just boilerplate:


Inside our CustomerService EJB things start to get interesting. By using the CDI API we fire an MailEvent event right at the end of the saveSuccess() method:

public class CustomerService {
    private EntityManager em;
    private Event<MailEvent> eventProducer;
    public void saveSuccess() {
        Customer c1 = new Customer();
        c1.setName("John Doe");

    private void sendEmail() {
        MailEvent event = new MailEvent();
        event.setSubject("Async email testing");
        event.setMessage("Testing email");; //firing event!

The MailEvent class is just a regular POJO that represents our event. It encapsulates information about the email message: the recipient, subject, text message, etc:

public class MailEvent {
    private String to; //recipient address
    private String message;
    private String subject;

    //getters and setters

If you’re new to CDI and still a bit confused about this event stuff, just read the docs. It should give you an idea.

Next it’s time for the event observer, the MailService EJB. It’s a simple EJB with some JavaMail magic and a couple of annotations you should pay attention to:

public class MailService {
    private Session mailSession; //more on this later
    public void sendMail(@Observes(during = TransactionPhase.AFTER_SUCCESS) MailEvent event) {
        try {
            MimeMessage m = new MimeMessage(mailSession);
            Address[] to = new InternetAddress[] {new InternetAddress(event.getTo())};

            m.setRecipients(Message.RecipientType.TO, to);
            m.setSentDate(new java.util.Date());
        } catch (MessagingException e) {
            throw new RuntimeException(e);

Like I said this is just a regular EJB. What makes this class an event observer, more precisely the sendMail() method, is the @Observes annotation in line 9. This annotation alone would make this method run after the event is fired.

But, we need this event to be fired only when the transaction is commited!. A rollback should not trigger email. That’s where the “during” attribute comes in. By specifying the value TransactionPhase.AFTER_SUCCESS we make sure the event is triggered only if the transaction commits successfully.

Last but not least, we also need to make this logic run in a separate thread from our main logic. It has to run asynchronously. And to achieve this we simply used two EJB annotations, @Asynchronous and @Lock(LockType.READ). The latter, @Lock(LockType.READ) is not required but highly recommended. It guarantees that no locks are used and multiple threads can use the method at the same time.

Configuring the mail session in JBoss Wildfly 8.1.0

As a bonus I’m gonna show how we can correctly configure a mail “source” in JBoss WildFly. Mail sources are pretty much like datasources, except that they’re for sending email, not for database stuff :). It’s a way to keep the code decoupled from how the connection to the mail server is made. I used a connection to my Gmail account, but you could switch to anything you want without having to touch any of the code inside the MailService class.

The javax.mail.Session object can be retrieved by its JNDI name using the @Resource annotation:

@Resource(mappedName = "java:jboss/mail/Gmail")
private Session mailSession;

You probably noticed that in my previous code snippets I did not use the @Resource annotation, I used just CDI’s @Inject. Well, if you’re curious how I did that just download the source code and take a look. (hint: I used a producer helper class.)

Moving on, just open up the standalone.xml (or domain.xml if you’re in domain mode) and first look for the “mail subsystem”. It should look like this:

<subsystem xmlns="urn:jboss:domain:mail:2.0">
    <mail-session name="default" jndi-name="java:jboss/mail/Default">
        <smtp-server outbound-socket-binding-ref="mail-smtp"/>

There’s a mail session already provided by default running on localhost. Since we probably do not have any mail servers running in your development machines, we’re gonna add a new one pointing to gmail:

<subsystem xmlns="urn:jboss:domain:mail:2.0">
    <mail-session name="default" jndi-name="java:jboss/mail/Default">
        <smtp-server outbound-socket-binding-ref="mail-smtp"/>
    <mail-session name="gmail" jndi-name="java:jboss/mail/Gmail" from="">
        <smtp-server outbound-socket-binding-ref="mail-gmail" ssl="true" username="" password="your-password"/>

See how lines 5, 6 and 7 are highlighted. That’s our new mail session. But that’s not all. We still need to create a socket binding to our new mail session. So inside standalone.xml look for an element called socket-binding-group:

<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">

    <!-- a bunch of stuff here -->

    <outbound-socket-binding name="mail-smtp">
        <remote-destination host="localhost" port="25"/>

Now we add our gmail port to the existing ones, by creating a new outbound-socket-binding element:

<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">

    <!-- a bunch of stuff here -->

    <outbound-socket-binding name="mail-smtp">
        <remote-destination host="localhost" port="25"/>

    <!-- "mail-gmail" is the same name we used in the mail-session config -->
    <outbound-socket-binding name="mail-gmail">
        <remote-destination host="" port="465"/>

This is it. Please leave a comment if you have any questions :). Later!

11 thoughts on “CDI & EJB: Sending asynchronous mail on transaction success

  1. Hi Rodrigo, as far as I can see, this solution doesn’t tackle your “Should we really prevent the user from buying at our store, just because our mail server is having a bad day?”-statement.

    An easy approach to be even more decoupled from any Mail-Stuff is to write a record into a Mail-Table in your database, inside the same order processing transaction. You can then have a completely separated process (deployed separately, can be any application) that does just poll the mail-table every minute or even gets triggered by a database trigger on the mail table, and send out all mails which have the state “Not Sent Yet”. After sending correctly, the state can change to “Success”. In the case of failure (mail server has a bad day) you can inform an administrator and leave the state as “not Sent”. We even implemented a Retry-Counter, so the admin is informed after the 3rd attempt to send the mail.

    You can have any application just inserting records in your mail table, to have mails being sent out, async and failsafe. And, as bonus, you have your mails archived in the very same database table 🙂

    • Hi Hans 🙂

      I imagined a scenario where we would not need to guarantee delivery. It would be acceptable if a confirmation order mail never got sent. In that case, since the order routine and mailing routine would be running in different threads, having a exception in the mailing routine would not cause the order routine to rollback. In that statement I was simply making a point to not run both routines in the same thread. 🙂

      Your alternative is a far more robust solution, no questions asked. 🙂 Next step I believe would be to use a JMS Queue.

      Really appreciate you sharing your thoughts. Thank you 🙂

  2. Pingback: CDI & EJB: Sending asynchronous mail on transaction success | Dinesh Ram Kali.

  3. Excelent solution, but I have a problem, could you help me please, when I start the server and not exists internet conecction, the server not start, have an error (ERROR [] (MSC service thread 1-1) MSC000001: Failed to start service org.jboss.msc.service.StartException in service Failed to start service). Thanks

    • Hi Ronald!

      I’m afraid I might not have a definitive answer.. The thing is:

      Much like datasources, Wildfly will “validate” them on startup. And if you’re out of internet it will mean that the mail server is not reachable, and wildfly will think it does not exist. What I would try is to look for any configuration property that make the initialization “lazy”, that is, to only try to validate on first use and not on startup. Try a question in the Wildfly user forums, someone there might know if such a property exist for mail session configuration.

      And while at it, just a heads up: Gmail made much harder recently to use its SMTP server with mail clients (like Wildfly) for security reasons. You will need to turn off a security option in the google security preferences. The exact procedure will be written on the exception message though 🙂

  4. Hi Rodrigo,

    I written on Wildfly forum and the answer for this problem is change to wildfly 9. On Wildfly 9.0.2 starts correctly the server without internet connection with configuration at gmail mail session. Thanks

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s