finagle-smtp icon indicating copy to clipboard operation
finagle-smtp copied to clipboard

Finagle SMTP client

finagle-smtp

Build status Coverage status

This is an SMTP client implementation for finagle based on RFC5321. The simplest guide to SMTP can be found, for example, here. Please see the API documentation for information that isn't covered in the introduction below.

Usage

Sending an email

The object for instantiating a client capable of sending a simple email can be obtained by calling Smtp.client.simple. For services created with it the request type is EmailMessage, described in EmailMessage.scala.

You can create an email using DefaultEmail class described in DefaultEmail.scala:

    val email = DefaultEmail()
      .from_("[email protected]")
      .to_("[email protected]", "[email protected]")
      .subject_("test")
      .text("text")

You can either create a plain text message with DefaultEmail.text() or a MIME message using DefaultEmail.addBodyPart() and DefaultEmail.setBody(). The Mime trait is described in Mime.scala.

Applying the service on the email returns Future.Done in case of a successful operation. In case of failure it returns the first encountered error wrapped in a Future.

Greeting and session

Upon the connection the client receives server greeting. In the beginning of the session an greeting request is sent automatically to identify the client. The session state is reset before every subsequent try.

Extensions

If the server supports 8BITMIME, BINARYMIME or SIZE extensions, they will be used. Other extensions are ignored. Note that it means that for now this client can send emails only to servers that don't require authentication.

Logging

To make a simple client log the session using the Logger passed to the function, you should call Smtp.client.withLogging().simple. Only the dialog is logged, with no additional information.

Sending independent SMTP commands

The object for instantiating an SMTP client capable of sending any command defined in RFC5321 can be obtained by calling Smtp.client.

For services created with it the request type is Request. Command classes are described in Request.scala.

Replies are differentiated by groups, which are described in ReplyGroups.scala. The concrete reply types are case classes described in SmtpReplies.scala.

This allows flexible error handling:

val res = service(command) onFailure {
  // An error group
  case ex: SyntaxErrorReply => log.error("Syntax error: %s", ex.info)
  ...
  // A concrete reply
  case ProcessingError(info) => log,error("Error processing request: %s", info)
  ...
  // Default
  case _ => log.error("Error!")
}
    // Or, another way:
    res handle {
    ...
    }

Greeting and session

Default SMTP client connects to the server, receives its greeting and replies to it. In case of malformed greeting the service is closed when trying to send a request. Upon service.close() a quit command is sent automatically, if not sent earlier.

Extensions

You can make the client support SMTP extensions by passing their names to Smtp.client.withSupportFor() and instantiating the service from the derived Client. Default client does not support any extensions. If the extension is supported by both client and server (and also implemented - see Using extensions), it will be able to be used.

Logging

You can log the session using SmtpLoggingFilter described in SmtpLoggingFilter.scala

Using extensions

Each implemented extension has its own package in extension package. Currently supported extensions are: AUTH, BINARYMIME, CHUNKING, 8BITMIME, EXPN (which is actually an extension according to RFC5321), PIPELINING and SIZE. Some of these extensions require adding the parameters to MAIL FROM command, which you can achieve by sending ExtendedMailingSession described in ExtendedMailingSession.scala instead of Request.NewMailingSession

AUTH

This extension is described in the auth package. It lets you send AuthRequest to perform authentication with some AuthMechanism and receive AuthReplys, and also to authorize the email message using ExtendedMailingSession.authorize(). For example:

    // Sending authentication request
    val mechanism = AuthMechanism.plain("login", "password")
    service(AuthRequest(mechanism)) flatMap {
      case ch@ServerChallenge(_) => service(mechanism.reply(ch))
      ...
    } handle {
      case AuthRejected(reason) => log.info("You cannot pass!")
    }
    // Authorizing email message
    val sender = MailingAddress("[email protected]")
    val authorizedSender = MailingAddress("[email protected]")
    service(ExtendedMailingSession(sender).authorize(authorizedSender))

Note that for now there is no API for using more sophisticated SASL mechanisms.

CHUNKING

This extension is described in the chunking package. It lets you send BeginDataChunk and BeginLastDataChunk commands instead of Request.BeginData. For example:

    service(BeginDataChunk(120))
    // Sending 120 bytes...
    ...
    service(BeginLastDataChunk(50))
    // Sending last 50 bytes...

BINARYMIME

This extension is described in the binarymime package. It lets you specify binary body encoding using ExtendedMailingSession.bodyEncoding() with BodyEncoding.Binary. Note that this extension only works with CHUNKING extension support. For example:

    val sender = MailingAddress("[email protected]")
    service(ExtendedMailingSession(sender).bodyEncoding(BodyEncoding.Binary))
    ...
    service(BeginDataChunk(120))
    // Sending binary data...

Without this extension any binary data will be rejected.

8BITMIME

This extension is described in the eightbitmime package. It lets you specify 8bit body encoding using ExtendedMailingSession.bodyEncoding() with BodyEncoding.EightBit. For example:

    val sender = MailingAddress("[email protected]")
    service(ExtendedMailingSession(sender).bodyEncoding(BodyEncoding.EightBit))
    ...
    service(Request.BeginData)
    // Sending 8bit data...

Without this extension all 8-bit data will be either rejected (if it is MIME data) or converted to 7-bit (if it is plain text).

EXPN

This extension allows you to send request to expand a mailing list. For example:

    val list = MailingAddress("[email protected]")
    service(Request.ExpandMailingList(list)) onSuccess {
      case ok@OK(_) => ok.lines foreach log.info
      ...
    }

PIPELINING

This extension is described in the pipelining package. It lets you send groups of requests using RequestGroup without waiting individually for each of them, but receiving replies batched in GroupedReply. For example:

    val sender = MailingAddress("[email protected]")
    val group = RequestGroup(Request.NewMailingSession(sender), Request.Quit)
    service(group) onSuccess {
      case GroupedReply(reps) => reps foreach log.info(_.info) 
      ...
    }

Note that, according to RFC4954, the following commands can appear only in the end of the group:

  • Greeting commands
  • Commands indicating that data is going to be sent
  • Address debugging commands (verifying mailboxes and expanding mailing lists)
  • Quit request
  • Noop request
  • Authentication request
  • the commands coming from other extensions, for which it is specified.

SIZE

This extension is described in the size package. It lets you specify message size using ExtendedMailingSession.messageSize() to make sure that the server can or can not accept the message of such size. For example:

    val sender = MailingAddress("[email protected]")
    service(ExtendedMailingSession(sender).messageSize(25000)) handle {
      case InsufficientStorageError(_) => log.info("Can't place these damn bytes anywhere!")
      ...
    }

License

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.