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.
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_("from@from.com")
.to_("first@to.com", "second@to.com")
.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
.
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.
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.
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.
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 {
...
}
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.
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.
You can log the session using SmtpLoggingFilter
described in SmtpLoggingFilter.scala
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
This extension is described in the auth package. It lets you send AuthRequest
to
perform authentication with some AuthMechanism
and receive AuthReply
s, 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("sender@example.org")
val authorizedSender = MailingAddress("authorized@example.org")
service(ExtendedMailingSession(sender).authorize(authorizedSender))
Note that for now there is no API for using more sophisticated SASL mechanisms.
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...
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("sender@example.org")
service(ExtendedMailingSession(sender).bodyEncoding(BodyEncoding.Binary))
...
service(BeginDataChunk(120))
// Sending binary data...
Without this extension any binary data will be rejected.
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("sender@example.org")
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).
This extension allows you to send request to expand a mailing list. For example:
val list = MailingAddress("mailing-list@example.org")
service(Request.ExpandMailingList(list)) onSuccess {
case ok@OK(_) => ok.lines foreach log.info
...
}
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("sender@example.org")
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.
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("sender@example.org")
service(ExtendedMailingSession(sender).messageSize(25000)) handle {
case InsufficientStorageError(_) => log.info("Can't place these damn bytes anywhere!")
...
}
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.