Creating Mails

The Mail class in emil-common is used to represent an e-mail. Using the MailBuilder makes creating values more convenient. The MailBuilder works by collecting a list of transformations to an initial Mail object and applying them when invoking the build method. There exists a set of predefined transformations to cover the most common things. But you can create your own easily, too.

Simple Mails

Creating mails without attachments:

import cats.effect._, emil._, emil.builder._

val mail: Mail[IO] = MailBuilder.build(
  From("me@test.com"),
  To("test@test.com"),
  Subject("Hello!"),
  CustomHeader(Header("User-Agent", "my-email-client")),
  TextBody("Hello!\n\nThis is a mail."),
  HtmlBody("<h1>Hello!</h1>\n<p>This <b>is</b> a mail.</p>")
)
// mail: Mail[IO] = Mail(
//   header = MailHeader(
//     id = "",
//     messageId = None,
//     folder = None,
//     recipients = Recipients(
//       to = List(MailAddress(name = None, address = "test@test.com")),
//       cc = List(),
//       bcc = List()
//     ),
//     sender = None,
//     from = Some(value = MailAddress(name = None, address = "me@test.com")),
//     replyTo = None,
//     originationDate = None,
//     subject = "Hello!",
//     received = List(),
//     flags = Set()
//   ),
//   additionalHeaders = Headers(
//     all = List(
//       Header(
//         name = "User-Agent",
//         value = NonEmptyList(head = "my-email-client", tail = List())
//       )
//     )
//   ),
//   body = HtmlAndText(
//     text = Pure(
//       value = StringContent(
//         asString = """Hello!
// 
// This is a mail."""
//       )
//     ),
//     html = Pure(
//       value = StringContent(
//         asString = """<h1>Hello!</h1>
// <p>This <b>is</b> a mail.</p>"""
//       )
//     )
//   ),
//   attachments = Attachments(all = Vector())
// )

The Mail defines an effect type, because the attachments and the mail body does. Using MailBuilder.build already applies all transformations and yields the final Mail instance. Using the MailBuilder.apply instead, would return the MailBuilder instance which can be further modified. It is also possible to go from an existing Mail to a MailBuilder to change certain parts:

val builder = mail.asBuilder
// builder: MailBuilder[IO] = emil.builder.MailBuilder@1791e231
val mail2 = builder.
  clearRecipients.
  add(To("me2@test.com")).
  set(Subject("Hello 2")).
  build
// mail2: Mail[IO] = Mail(
//   header = MailHeader(
//     id = "",
//     messageId = None,
//     folder = None,
//     recipients = Recipients(
//       to = List(MailAddress(name = None, address = "me2@test.com")),
//       cc = List(),
//       bcc = List()
//     ),
//     sender = None,
//     from = Some(value = MailAddress(name = None, address = "me@test.com")),
//     replyTo = None,
//     originationDate = None,
//     subject = "Hello 2",
//     received = List(),
//     flags = Set()
//   ),
//   additionalHeaders = Headers(
//     all = List(
//       Header(
//         name = "User-Agent",
//         value = NonEmptyList(head = "my-email-client", tail = List())
//       )
//     )
//   ),
//   body = HtmlAndText(
//     text = Pure(
//       value = StringContent(
//         asString = """Hello!
// 
// This is a mail."""
//       )
//     ),
//     html = Pure(
//       value = StringContent(
//         asString = """<h1>Hello!</h1>
// <p>This <b>is</b> a mail.</p>"""
//       )
//     )
//   ),
//   attachments = Attachments(all = Vector())
// )

The add and set methods are both doing the same thing: appending transformations. Both names exists for better reading; i.e. a recipient is by default appended, but the subject is not. The methods accept a list of transformations, too.

val mail3 = mail2.asBuilder.
  clearRecipients.
  add(
    To("me3@test.com"),
    Cc("other@test.com")
  ).
  build
// mail3: Mail[IO] = Mail(
//   header = MailHeader(
//     id = "",
//     messageId = None,
//     folder = None,
//     recipients = Recipients(
//       to = List(MailAddress(name = None, address = "me3@test.com")),
//       cc = List(MailAddress(name = None, address = "other@test.com")),
//       bcc = List()
//     ),
//     sender = None,
//     from = Some(value = MailAddress(name = None, address = "me@test.com")),
//     replyTo = None,
//     originationDate = None,
//     subject = "Hello 2",
//     received = List(),
//     flags = Set()
//   ),
//   additionalHeaders = Headers(
//     all = List(
//       Header(
//         name = "User-Agent",
//         value = NonEmptyList(head = "my-email-client", tail = List())
//       )
//     )
//   ),
//   body = HtmlAndText(
//     text = Pure(
//       value = StringContent(
//         asString = """Hello!
// 
// This is a mail."""
//       )
//     ),
//     html = Pure(
//       value = StringContent(
//         asString = """<h1>Hello!</h1>
// <p>This <b>is</b> a mail.</p>"""
//       )
//     )
//   ),
//   attachments = Attachments(all = Vector())
// )

Mails with Attachments

Adding attachments is the same as with other data. Creating attachments might be more involved, depending on where the data is coming from. Emil defines transformations to add attachments from files, urls and java’s InputStream easily. Otherwise you need to get a Stream[F, Byte] from somewhere else.

import scala.concurrent.ExecutionContext

val mail4 = mail3.asBuilder.add(
  AttachUrl[IO](getClass.getResource("/files/Test.pdf")).
    withFilename("test.pdf").
    withMimeType(MimeType.pdf)).
  build
// mail4: Mail[IO] = Mail(
//   header = MailHeader(
//     id = "",
//     messageId = None,
//     folder = None,
//     recipients = Recipients(
//       to = List(MailAddress(name = None, address = "me3@test.com")),
//       cc = List(MailAddress(name = None, address = "other@test.com")),
//       bcc = List()
//     ),
//     sender = None,
//     from = Some(value = MailAddress(name = None, address = "me@test.com")),
//     replyTo = None,
//     originationDate = None,
//     subject = "Hello 2",
//     received = List(),
//     flags = Set()
//   ),
//   additionalHeaders = Headers(
//     all = List(
//       Header(
//         name = "User-Agent",
//         value = NonEmptyList(head = "my-email-client", tail = List())
//       )
//     )
//   ),
//   body = HtmlAndText(
//     text = Pure(
//       value = StringContent(
//         asString = """Hello!
// 
// This is a mail."""
//       )
//     ),
//     html = Pure(
//       value = StringContent(
//         asString = """<h1>Hello!</h1>
// <p>This <b>is</b> a mail.</p>"""
//       )
//     )
//   ),
//   attachments = Attachments(
//     all = Vector(
//       Attachment(
//         filename = Some(value = "test.pdf"),
//         mimeType = MimeType(
//           primary = "application",
//           sub = "pdf",
//           params = Map()
// ...

Emil creates a Stream[F, Byte] from the java.net.URL using the fs2-io api. The same is available when attaching files and java.io.InputStreams. Any given Stream[F, Byte] can be attached as well:

import fs2.Stream

val mydata: Stream[IO, Byte] = Stream.empty.covary[IO]
// mydata: Stream[IO, Byte] = Stream(..)

val mail5 = mail4.asBuilder.
  clearAttachments.
  add(
    Attach(mydata).
      withFilename("empty.txt")
  ).
  build
// mail5: Mail[IO] = Mail(
//   header = MailHeader(
//     id = "",
//     messageId = None,
//     folder = None,
//     recipients = Recipients(
//       to = List(MailAddress(name = None, address = "me3@test.com")),
//       cc = List(MailAddress(name = None, address = "other@test.com")),
//       bcc = List()
//     ),
//     sender = None,
//     from = Some(value = MailAddress(name = None, address = "me@test.com")),
//     replyTo = None,
//     originationDate = None,
//     subject = "Hello 2",
//     received = List(),
//     flags = Set()
//   ),
//   additionalHeaders = Headers(
//     all = List(
//       Header(
//         name = "User-Agent",
//         value = NonEmptyList(head = "my-email-client", tail = List())
//       )
//     )
//   ),
//   body = HtmlAndText(
//     text = Pure(
//       value = StringContent(
//         asString = """Hello!
// 
// This is a mail."""
//       )
//     ),
//     html = Pure(
//       value = StringContent(
//         asString = """<h1>Hello!</h1>
// <p>This <b>is</b> a mail.</p>"""
//       )
//     )
//   ),
//   attachments = Attachments(
//     all = Vector(
//       Attachment(
//         filename = Some(value = "empty.txt"),
//         mimeType = MimeType(
//           primary = "application",
//           sub = "octet-stream",
//           params = Map()
// ...

Custom Transformations

Customs transformations can be easily created. It’s only a function Mail[F] => Mail[F]. This can be handy, if there is a better way to retrieve attachments, or just to create common headers.

object MyHeader {
  def apply[F[_]](value: String): Trans[F] =
    CustomHeader("X-My-Header", value)
}

val mymail = MailBuilder.build[IO](
  To("me@test.com"),
  MyHeader("blablabla"),
  TextBody("This is a text")
)
// mymail: Mail[IO] = Mail(
//   header = MailHeader(
//     id = "",
//     messageId = None,
//     folder = None,
//     recipients = Recipients(
//       to = List(MailAddress(name = None, address = "me@test.com")),
//       cc = List(),
//       bcc = List()
//     ),
//     sender = None,
//     from = None,
//     replyTo = None,
//     originationDate = None,
//     subject = "",
//     received = List(),
//     flags = Set()
//   ),
//   additionalHeaders = Headers(
//     all = List(
//       Header(
//         name = "X-My-Header",
//         value = NonEmptyList(head = "blablabla", tail = List())
//       )
//     )
//   ),
//   body = Text(text = Pure(value = StringContent(asString = "This is a text"))),
//   attachments = Attachments(all = Vector())
// )

The above example simply delegates to an existing Trans constructor.