spinoco / fs2-mail

asynchronous library for sending and receiving mail via fs2._

GitHub

fs2-mail

Asynchronous, non-blocking IMAP/SMTP client for receiving and sending e-mails for scala.

Build Status Gitter Chat

Overview

fs2-mail is simple, minimalistic, streaming, asynchronous and non-blocking client for receiving and sending emails for scala implemented in fs2 library. Traditionally sending and receiving emails in JVM world is done with Java's JavaMail API, that allows rich set of functionality. Albeit rich and powerful, JavaMail is build on top of fully blocking API. That may present limitations when working with asynchronous and streaming libraries, and that is exactly where fs2-mail fits in.

Unlike JavaMail, fs2-mail is fully non-blocking, streaming allowing to process emails with minimal memory constrains with full utilization of modern multi-core CPUs. All processing (encoding, decoding) of emails is fully asynchronous and non-blocking. So for example receiving or sending emails over slower network connections will work perfectly fine and will automatically adjust to speed of the target/source server. That way, you can write clients that may send and receive hundreds or thousands emails concurrently fully saturating server and internet connection capacity.

Features

fs-mail currently implements:

  • IMAP to receive emails
  • SMTP to send emails

Both variants may use SSL depending whether clients are created with plaintext or SSL socket.

SBT

Add this to your sbt build file :

for fs2 0.10.x series:

libraryDependencies += "com.spinoco" %% "fs2-mail" % "0.2.0"

for fs2 0.9.x series:

libraryDependencies += "com.spinoco" %% "fs2-mail" % "0.1.1"

Dependencies

version scala fs2 scodec shapeless
0.4.0-M2 2.11, 2.12 1.0.0-M2 1.10.3 2.3.2
0.2.0 2.11, 2.12 0.10.5 1.10.3 2.3.2
0.1.1 2.11, 2.12 0.9.7 1.10.3 2.3.2

Usage

fs2-mail has simple, minimalistic API, that allows straightforward usage.

Sending emails

To send emails, fs2-mail uses SMTP protocol. Following is the simple example of minimalistic client that uses single SMTP connection to send single message:

import java.net.InetSocketAddress
import java.time.ZonedDateTime

import cats.effect.IO
import fs2._ 
import fs2.crypto.TLSEngine
import fs2.crypto.io.tcp.TLSSocket

import shapeless.tag

import spinoco.fs2.mail.smtp
import spinoco.fs2.mail.interop.StringChunk
import spinoco.protocol.mail.EmailAddress
import spinoco.protocol.mail.EMailHeader




io.tcp.client[IO](new InetSocketAddress("imap.gmail.com", 993)).flatMap { tcpSocket =>
Stream.eval(TLSEngine[IO](clientTLS)).flatMap { tlsEngine =>
Stream.eval(TLSSocket[IO](tcpSocket, tlsEngine)).flatMap { tlsSocket =>
smtp.client(tlsSocket).evalMap { smtpClient => 

    val header: EMailHeader = EMailHeader(
      subject = "Hello from fs2-mail"
      , date = ZonedDateTime.now
      , from = EmailAddress("alice", "mail.com", None)
      , to = EmailAddress("bob", "mail.com", None)
    )
    val body: Stream[IO, Char] = Stream.chunk(StringChunk("A simple text-only email from fs2-mail.")).covary[IO]

    smtpClient.connect("gmail.com") >>
    smtpClient.login("userName", "password") >>
    smtpClient.sendText(tag[EmailAddress]("alice@mail.com"), Seq(tag[EmailAddress]("bob@mail.com")), header, body)
    

    
}}}}.run.unsafeRun()

Apart from sending simple emails, fs2-mail also allows to send MIME encoded (inclusive multipart) data. To send such emails, that perhaps fetch their content not only from the strictly defined data but perhaps from data stored on filesystem or central data storage following construct (replacing the smtp.sendText in example above).

import spinoco.protocol.mime._
import java.nio.file.Paths

val fileGifSource: Stream[F, Byte] = ??? // i.e. load from the 
val body = MIMEPart.file("part-id", "file.gif", MediaType.`image/gif`, fs2.io.file.readAll(Paths.get("/some/file/location/file.gif")))  

smtpClient.send(tag[EmailAddress]("alice@mail.com"), Seq(tag[EmailAddress]("bob@mail.com")), header, body)

Receiving emails

To receive emails, fs2 supports IMAP v 4.1 protocol. User may search emails, perform various email actions, download attachment (with automatical decoding according to their mime type).

Minimalistic example of fetching ...

(Note this to run requires Spinoco fs2-crypto in project dependencies)

import java.net.InetSocketAddress
import java.time.ZonedDateTime

import fs2._
import fs2.util.syntax._ 
import spinoco.fs2.crypto.io.tcp.TLSSocket

import shapeless.tag

import spinoco.fs2.mail.imap 
import spinoco.fs2.mail.imap.MailboxName
import spinoco.fs2.mail.imap.IMAPSearchTerm


 io.tcp.client[IO](new InetSocketAddress("imap.gmail.com", 993)).flatMap { tcpSocket => 
  Stream.eval(TLSSocket[IO](tcpSocket, clientTLS)) flatMap { tlsSocket =>
  imap.client[IO](tlsSocket) evalMap { imapClient =>
    // login with supplied credentials
    imapClient.login("EMAIL", "PASSWORD") >>
    // check for imap server capabilities 
    imapClient.capability.flatMap { imapServerCapabilities =>
    // list mailboxes given specific criteria
    imapClient.list("", "*").flatMap { imapServerMailboxes => 
    // select email mailbox
    imapClient.select(tag[MailboxName]("INBOX")).flatMap { inboxStatus =>
    // search mailbox for mail with specified id
    imapClient.search(IMAPSearchTerm.Header("Message-ID", "your-message-id")).flatMap { searchResult =>   
    // print email headers from 42000 to 4238
    imapClient.emailHeaders(4200l to 4248l).map { hdr => println(s"HEADER: $hdr") }.run  
      

    }}}}

  }}}
  .run.unsafeRun