This is the e-mail Akka Extra, which allows you to easily add simple javax.mail support to your application. My motivatiosn were quite simple:

  • Actor and non-actor API
  • Flexible configuration
  • No classes for Email, Address and such like
  • Flexible error handling

The first item, actor and non-actor API was easy. I have a trait that you can directly mix in, or you can instantiate an actor that does the dirty work. The second one is yet another trait and appropriate implementations. The third and fourth points get more interesting. Let's now take a look at the code and discuss the main points.

No classes for Email, Address and such like

The challenge was to construct a system that would have sufficiently extensible system that does not drown in complex class hierarchies. As usual, traits and self-type annotations (aka the cake pattern) is the answer. When sending e-mails, we have several distinct entities:

  • Subject
  • Sender and recipient addresses
  • Body

Translating this to a function, we arrive at a function

type Address = String
type Subject = String
type Body    = String
type Result  = ...
def email(sender: Address, subject: String, body: Body, 
          to: List[Address], cc: List[Address], bcc: List[Address]): Result

Let's leave out the type of the Result for now. If we want to build flexible system, we would like to be able to specify the types ourselves. Traits and self-type annotations to the rescue. I will show you how to do that with the Address types.

trait InternetAddressBuilder {
  type AddressIn

  def buildInternetAddress: 
  	AddressIn => M[InternetAddress] = ???

  def buildInternetAddresses: 
  	List[AddressIn] => M[Array[InternetAddress]] = 
  	??? // but using buildInternetAddress

}

This trait defines buildInternetAddress that returns a function that takes the AddressIn and returns a context M containing the InternetAddress. Let's take a look at one of the implementations of this trait. It defines the abstract type AddressIn and implements the buildInternetAddress function.

trait SimpleInternetAddressBuilder extends InternetAddressBuilder {
  type AddressIn = String

  def buildInternetAddress: AddressIn => M[InternetAddress] = { 
    address: AddressIn =>
    m(InternetAddress.parse(address, false)(0))
  }
}

Excellent. The javamail module defines the InternetAddressBuilder and its SimpleInternetAddressBuilder implementation. However, in your code, you may want to extract email addresses from User objects, not Strings. In that case, you will need to provide the appropriate implementation of the InternetAddressBuilder.

Context

Let's now tackle the context in which the InternetAddressBuilder returns the InternetAddresses. We want to use the context to indicate successes and failures, and we would like to be able to chain these contexts together. Scalaz 7 has just the right thing. We can have a context that contains some errors or successes; left or right. Scalaz calls it EitherT[F[+_], L, R]; F[+_] is the container into which we pack the Rs. In our case, the L is Throwable to indicate errors, R is InternetAddress and F is Id. So, let's go back to InternetAddressBuilder and plug in the proper return type.

trait InternetAddressBuilder {
  type AddressIn

  def buildInternetAddress: 
  	AddressIn => EitherT[Id, Throwable, InternetAddress] = ???

  def buildInternetAddresses: 
  	List[AddressIn] => EitherT[Id, Throwable, Array[InternetAddress]] = 
  	??? // but using buildInternetAddress

}

To save us typing, we shall define EitherFailures[A] as wrapper around EitherT: type EitherFailures[A] = EitherT[Id, Throwable, A].

Now, this leaves us with the last task. Examine the buildInternetAddress and buildInternetAddresses. The first one takes one AddressIn and returns EitherFailures[InternetAddress]. The second one takes List[AddressIn] and reutrn a Array[InternetAddress], by folding the successes and stopping on failures. We begin by mapping the input addresses. So, we go from List[AddressIn] into List[EitherT[Id, Throwable, InternetAddress]]. However, we want EitherT[Id, Throwable, List[InternetAddress]]. We seem to be migrating the List from the front into the value on the right. Let's fold.

def buildInternetAddresses: 
  List[AddressIn] => EitherFailures[Array[InternetAddress]] = { 
  addresses =>
  import scalaz.syntax.monad._

  val z = List.empty[InternetAddress].point[EitherFailures]
  addresses.map(buildInternetAddress).foldLeft(z) { (b, a) =>
    for {
      address   <- a
      addresses <- b
    } yield address :: addresses
  }.map(_.toArray)
}

We map the input addresses.map(buildInternetAddress), giving us List[EitherT[...]]. We then fold this list, starting from the empty value on the right (z) by flat mapping the container with each value. We finally turn the List[InternetAddress] into Array[InternetAddress] (stupid JavaMail).

More contexts

And this is how the rest of the javamail module operates internally. It makes the most of the cake pattern and uses the EitherT monad transformer to build a context that, when executed, will send e-mail or report errors. Let's fast-forward to SimpleMimeMessageBuilder, which takes some input and makes a MimeMessage that can be transported.

trait SimpleMimeMessageBuilder extends MimeMessageBuilder {
  this: EmailConfiguration with InternetAddressBuilder with MimeMessageBodyBuilder =>

  type MessageIn = 
    (AddressIn,         // from
     String,            // subject
     MimeMessageBodyIn, // body
     List[AddressIn],   // to
     List[AddressIn],   // cc
     List[AddressIn]    // bcc
    )   

  private def mimeMessage(session: Session, 
    from: InternetAddress, subject: String, body: MimeMultipart,
    to: Array[InternetAddress], cc: Array[InternetAddress], 
    bcc: Array[InternetAddress]): MimeMessage = { ... }

  def buildMimeMessage: MessageIn => EitherFailures[MimeMessage] = { 
    in: MessageIn =>

    val (from, subject, body, to, cc, bcc) = in
    for {
      session     <- getMailSession
      body        <- buildMimeMessageBody(body)
      from        <- buildInternetAddress(from)
      to          <- buildInternetAddresses(to)
      cc          <- buildInternetAddresses(cc)
      bcc         <- buildInternetAddresses(bcc)
    } yield mimeMessage(session, from, subject, body, to, cc, bcc)
  }
}

Notice how nicely we were able to combine all EitherT contexts to produce one final context that, given MessageIn produces either Throwable on the left or MimeMessage on the right. All that is needed is to transport it.

Actors

To close, let's examine how we use the low-level trait in an actor. The actor needs to be able to send MimeMessage as well as some MessageIn types (the conctete type of MessageIn is driven by the mixed-in dependencies at the point of actor construction).

trait SimpleConfgiruedActorEmail extends 
  SimpleUnconfiguredEmail with ConfigEmailConfiguration {
  this: Actor =>

  def config = context.system.settings.config
}

class SimpleEmailActor extends Actor with Emailer {
  this: EmailTransport with EmailConfiguration with MimeMessageBuilder with MimeMessageBodyBuilder with InternetAddressBuilder =>

  def receive = {
    case m: MimeMessage => transportEmailMessage(m).run
    case m: MessageIn => email(m).run
  }

}

That's actually all there is to it. By carefully combining the small components of the underlying transport mechanism, we are able to create a very compact actor that sends e-mails.

Tests

No code would be complete without tests. However, we would like to make sure that we can actually talk to the SMTP server. This is the job for Dumbster, which we setup to listen on port 10025. Your e-mail testing code can then be as simple as

class JavamailEmailMessageDeliverySpec extends Specification with EmailFragments {

  class Simple extends Emailer with JavamailEmailTransport with TestingEmailConfiguration with SimpleInternetAddressBuilder with SimpleMimeMessageBodyBuilder with SimpleMimeMessageBuilder

  "SMTP email transport" should {
    sequential

    "construct and send simple emails" in {
      val subject = "Subject"
      val body = "Body"
      val email = receiveEmails {
        val from = "Jan Machacek <janm@cakesolutions.net>"

        val simple = new Simple
        val x = simple.email(from, subject, body, List(from), Nil, Nil)
        x.run.isRight must beTrue
      }.head

      email.getHeaderValue("Subject") mustEqual subject
    }
  }
}

Notice how we assemple the layers of the cake in the Simple class and how we then use it in the receiveEmails block. Inside this block, we have the SMTP server running and, when the block completes, we get a List[SmtpMessage] that we can use in our assertions.

Code

The code is available at https://github.com/eigengo/akka-extras, the binary artefacts are available on Sonatype.