Concept

Mail Model

Emil uses a simplified model of an e-mail. With MIME, an e-mail can appear in various shapes. Emil uses a flat structure, being:

  1. Headers
  2. Body
  3. Attachments

The Headers are the standard headers in an e-mail. The Body defines the content of the mail. It can be specified as plain text, HTML or both. In case both is specified it defines the same content, just a different format. It is translated into a multipart/alternative message.

Then a list of attachments follows. The major difference to MIME is that it is not recursive. Emil can only create Mixed or Alternative MIME messages.

MailOp

The other concept is the MailOp, which is an alias for the cats Kleisli class, where the input type is a Connection. Every code that does something with an e-mail runs inside such a function.

type MailOp[F[_], C, A] = Kleisli[F, C, A]

The C is the type representing the connection to some mail server. It is not a concrete type, because this depends on the implementation and the operations should not depend on it.

There are pre-defined primitive operations in the Access and Send trait, respectively. These are implemented by some “implementation module”. These primitive operations can be composed into custom ones.

For example, this is an operation that moves the first mail in INBOX into the Trash folder:

import cats.implicits._, cats.effect._, emil._

def moveToTrash[F[_]: Sync, C](a: Access[F, C]): MailOp[F, C, Unit] = {
  val trash = a.getOrCreateFolder(None, "Trash")
  val findFirstMail = a.getInbox.
    flatMap(in => a.search(in, 1)(SearchQuery.All)).
    map(_.mails.headOption.toRight(new Exception("No mail found."))).
    mapF(_.rethrow)

  for {
    target <- trash
    mail   <- findFirstMail
    _      <- a.moveMail(mail, target)
  } yield ()
}

This uses only imports from emil-common.

Implementation

The module emil-common lets you define mails and operations among them. To actually execute these operations, an implementation module is necessary. Currently there is emil-javamail, that is based on the JavaMail library.

This module provides a factory for creating concrete Connection instances. The emil-javamail module has a ConnectionResource that creates a Resource[F, JavaMailConnection] given some MailConfig. This can be used to run the MailOp operations.

The Emil trait exists to make this more convenient. It simply combines the ConnectionResource and the implementations for the primitive MailOps (defined in Access and Send trait). The emil-javamail module provides this as JavaMailEmil.

For example, to execute the “moveToTrash” operation from above, one needs a corresponding connection to some imap server and the JavaMailEmil.

import emil.javamail._
import scala.concurrent.ExecutionContext

val myemil = JavaMailEmil[IO]()
// myemil: Emil[IO] = emil.javamail.JavaMailEmil@1621264e
val imapConf = MailConfig("imap://devmail:143", "dev", "dev", SSLType.NoEncryption)
// imapConf: MailConfig = MailConfig(
//   url = "imap://devmail:143",
//   user = "dev",
//   password = "dev",
//   sslType = NoEncryption,
//   enableXOAuth2 = false,
//   disableCertificateCheck = false,
//   timeout = 10 seconds
// )

val moveIO = myemil(imapConf).run(moveToTrash(myemil.access))
// moveIO: IO[Unit] = Uncancelable(
//   body = cats.effect.IO$$$Lambda$1486/0x00000008017023c0@5392c0a7,
//   event = cats.effect.tracing.TracingEvent$StackTrace
// )

Note: The emil-javamail depends on the JavaMail library, which has a dual license: EPL and GPLv2 with Classpath exception.