LuaSocket
Network support for the Lua language

home · introduction · reference


SMTP

The smtp namespace provides functionality to send e-mail messages. The high-level API consists of two functions: one to define an e-mail message, and another to actually send the message. Although almost all users will find that these functions provide more than enough functionality, the underlying implementation allows for even more control (if you bother to read the code).

The implementation conforms to the Simple Mail Transfer Protocol, RFC 2821. Another RFC of interest is RFC 2822, which governs the Internet Message Format. Multipart messages (those that contain attachments) are part of the MIME standard, but described mainly in RFC 2046

In the description below, good understanding of LTN012, Filters sources and sinks and the MIME module is assumed. In fact, the SMTP module was the main reason for their creation.

To obtain the smtp namespace, run:

-- loads the SMTP module and everything it requires
local smtp = require("socket.smtp")

MIME headers are represented as a Lua table in the form:

headers = {
  field-1-name = field-1-value,
  field-2-name = field-2-value,
  field-3-name = field-3-value,
  ...
  field-n-name = field-n-value
}

Field names are case insensitive (as specified by the standard) and all functions work with lowercase field names. Field values are left unmodified.

Note: MIME headers are independent of order. Therefore, there is no problem in representing them in a Lua table.

The following constants can be set to control the default behavior of the SMTP module:

smtp.send{
  from = string,
  rcpt = string or string-table,
  source = LTN12 source,
  [user = string,]
  [password = string,]
  [server = string,]
  [port = number,]
  [domain = string,]
  [step = LTN12 pump step,]
  [create = function]
}

Sends a message to a recipient list. Since sending messages is not as simple as downloading an URL from a FTP or HTTP server, this function doesn't have a simple interface. However, see the message source factory for a very powerful way to define the message contents.

The sender is given by the e-mail address in the from field. Rcpt is a Lua table with one entry for each recipient e-mail address, or a string in case there is just one recipient. The contents of the message are given by a simple LTN12 source. Several arguments are optional:

If successful, the function returns 1. Otherwise, the function returns nil followed by an error message.

Note: SMTP servers can be very picky with the format of e-mail addresses. To be safe, use only addresses of the form "<fulano@example.com>" in the from and rcpt arguments to the send function. In headers, e-mail addresses can take whatever form you like.

Big note: There is a good deal of misconception with the use of the destination address field headers, i.e., the 'To', 'Cc', and, more importantly, the 'Bcc' headers. Do not add a 'Bcc' header to your messages because it will probably do the exact opposite of what you expect.

Only recipients specified in the rcpt list will receive a copy of the message. Each recipient of an SMTP mail message receives a copy of the message body along with the headers, and nothing more. The headers are part of the message and should be produced by the LTN12 source function. The rcpt list is not part of the message and will not be sent to anyone.

RFC 2822 has two important and short sections, "3.6.3. Destination address fields" and "5. Security considerations", explaining the proper use of these headers. Here is a summary of what it says:

The LuaSocket send function does not care or interpret the headers you send, but it gives you full control over what is sent and to whom it is sent:

I hope this clarifies the issue. Otherwise, please refer to RFC 2821 and RFC 2822.

-- load the smtp support
local smtp = require("socket.smtp")

-- Connects to server "localhost" and sends a message to users
-- "fulano@example.com",  "beltrano@example.com", 
-- and "sicrano@example.com".
-- Note that "fulano" is the primary recipient, "beltrano" receives a
-- carbon copy and neither of them knows that "sicrano" received a blind
-- carbon copy of the message.
from = "<luasocket@example.com>"

rcpt = {
  "<fulano@example.com>",
  "<beltrano@example.com>",
  "<sicrano@example.com>"
}

mesgt = {
  headers = {
    to = "Fulano da Silva <fulano@example.com>",
    cc = '"Beltrano F. Nunes" <beltrano@example.com>',
    subject = "My first message"
  },
  body = "I hope this works. If it does, I can send you another 1000 copies."
}

r, e = smtp.send{
  from = from,
  rcpt = rcpt, 
  source = smtp.message(mesgt)
}

smtp.message(mesgt)

Returns a simple LTN12 source that sends an SMTP message body, possibly multipart (arbitrarily deep).

The only parameter of the function is a table describing the message. Mesgt has the following form (notice the recursive structure):

mesgt = {
  headers = header-table,
  body = LTN12 source or string or multipart-mesgt
}
 
multipart-mesgt = {
  [preamble = string,]
  [1] = mesgt,
  [2] = mesgt,
  ...
  [n] = mesgt,
  [epilogue = string,]
}

For a simple message, all that is needed is a set of headers and the body. The message body can be given as a string or as a simple LTN12 source. For multipart messages, the body is a table that recursively defines each part as an independent message, plus an optional preamble and epilogue.

The function returns a simple LTN12 source that produces the message contents as defined by mesgt, chunk by chunk. Hopefully, the following example will make things clear. When in doubt, refer to the appropriate RFC as listed in the introduction.

-- load the smtp support and its friends
local smtp = require("socket.smtp")
local mime = require("mime")
local ltn12 = require("ltn12")

-- creates a source to send a message with two parts. The first part is 
-- plain text, the second part is a PNG image, encoded as base64.
source = smtp.message{
  headers = {
     -- Remember that headers are *ignored* by smtp.send. 
     from = "Sicrano de Oliveira <sicrano@example.com>",
     to = "Fulano da Silva <fulano@example.com>",
     subject = "Here is a message with attachments"
  },
  body = {
    preamble = "If your client doesn't understand attachments, \r\n" ..
               "it will still display the preamble and the epilogue.\r\n" ..
               "Preamble will probably appear even in a MIME enabled client.",
    -- first part: no headers means plain text, us-ascii.
    -- The mime.eol low-level filter normalizes end-of-line markers.
    [1] = { 
      body = mime.eol(0, [[
        Lines in a message body should always end with CRLF. 
        The smtp module will *NOT* perform translation. However, the 
        send function *DOES* perform SMTP stuffing, whereas the message
        function does *NOT*.
      ]])
    },
    -- second part: headers describe content to be a png image, 
    -- sent under the base64 transfer content encoding.
    -- notice that nothing happens until the message is actually sent. 
    -- small chunks are loaded into memory right before transmission and 
    -- translation happens on the fly.
    [2] = { 
      headers = {
        ["content-type"] = 'image/png; name="image.png"',
        ["content-disposition"] = 'attachment; filename="image.png"',
        ["content-description"] = 'a beautiful image',
        ["content-transfer-encoding"] = "BASE64"
      },
      body = ltn12.source.chain(
        ltn12.source.file(io.open("image.png", "rb")),
        ltn12.filter.chain(
          mime.encode("base64"),
          mime.wrap()
        )
      )
    },
    epilogue = "This might also show up, but after the attachments"
  }
}

-- finally send it
r, e = smtp.send{
    from = "<sicrano@example.com>",
    rcpt = "<fulano@example.com>",
    source = source,
}