Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

mail.message

import mail

Everything here is re-exported by mail, so import mail is enough and the names are called as mail.*. Importing mail.message on its own works too and reaches the same definitions.

A mail message: its headers, its body, and the tree of parts a body turns into once there is more than one thing in it.

Message is both what comes off the wire and what goes onto it. Parsing one gives the whole MIME tree with every part decoded on demand; building one from text, HTML and files gives back a message any mail server will accept.

import mail

var note = mail.message()
  .set_from('Richard Ore <richard@example.com>')
  .add_to('ann@example.com')
  .set_subject('Quarterly report')
  .set_text('The numbers are attached.')

echo note.subject()
echo note.content_type().mime_type()
Quarterly report
text/plain

A message with both text and HTML in it becomes a multipart/alternative; add a file and the whole thing is wrapped in a multipart/mixed. None of that has to be spelled out: the shape follows from what was put in.

Functions

text_part()

mail.text_part(text: string, charset: ?string, subtype: ?string) -> Message

Builds one text part.

Parameters

  • text (string)
  • charset (?string) — utf-8, or us-ascii when the text needs nothing more. No other set can be written.
  • subtype (?string) — plain when not given.

Returns Message

Raises MessageError if charset names a set this cannot write.

attachment()

mail.attachment(data) -> Attachment

Starts an attachment from what is in it.

import mail

echo mail.attachment('some bytes').set_filename('notes.txt').to_string()
<Attachment notes.txt, 10 bytes>

Parameters

  • data (string|bytes)

Returns Attachment

multipart()

mail.multipart(subtype: string, parts: list)

new_message_id()

mail.new_message_id(domain: ?string) -> string

An identifier for a message, unique enough that no two ever collide.

Parameters

  • domain (?string) — The right hand side. The machine’s own name when not given.

Returns string — including the angle brackets a header needs.

parse()

mail.parse(data) -> Message

Reads a message.

Parameters

  • data (string|bytes)

Returns Message

message()

mail.message() -> Message

Starts a message.

What goes in it is said one call at a time, and every call hands the message back so they read as one:

import mail

var note = mail.message()
  .set_from('Richard Ore <richard@example.com>')
  .add_to('ann@example.com')
  .set_subject('Quarterly report')
  .set_text('The numbers are in.')

echo note.subject()
echo note.content_type().mime_type()
Quarterly report
text/plain

The shape follows from what is said. Text alone is a text/plain message; adding HTML makes it a multipart/alternative; attaching a file wraps the whole thing in a multipart/mixed. None of that has to be arranged by hand.

A message starts dated, since the time it was written is the time it was written. It gets its identifier when it is sent, because that is when the domain it belongs to is settled.

Returns Message

Classes

Message

class mail.Message

One message, or one part of one.

A part is a message too: it has headers and a body, and its body may be more parts. The same class covers both, so walking a message and reading a standalone one are the same code.

  • printable — has a @to_string(), so echo and print() show something useful

Constructor

mail.Message(headers, source)

Builds a message.

Most callers want mail.message(), which fills one in, or mail.parse(), which reads one off the wire.

Parameters

  • headers (?Headers) — The headers to start with.
  • source (?any) — Bytes or text to read the message from, in which case headers is ignored.

Message.parse()

mail.Message.parse(data) -> Message

Reads a message.

Nothing is decoded up front. The tree is built, and each part decodes its own body the first time something asks for it, so reading the subject of a message with a large attachment in it costs nothing more than reading the headers.

import mail

var note = mail.parse(
  'From: a@example.com\r\n'
  + 'Subject: Hi\r\n'
  + '\r\n'
  + 'Hello there.\r\n'
)

echo note.subject()
echo note.text()
Hi
Hello there.

Parameters

  • data (string|bytes)

Returns Message

Raises MessageError if the headers cannot be read.

Message.content_type()

mail.Message.content_type() -> ContentType

The Content-Type of this part.

Returns ContentType

Message.is_multipart()

mail.Message.is_multipart() -> bool

Whether this part holds other parts.

Returns bool

Message.parts()

mail.Message.parts() -> list

The parts directly inside this one, which is empty for a part that holds content rather than other parts.

Returns list — of Message

Message.walk()

mail.Message.walk() -> list

Every part of the message including this one, outermost first.

for part in note.walk() {
  echo part.content_type().mime_type()
}

Returns list — of Message

Message.body_bytes()

mail.Message.body_bytes() -> bytes

This part’s body, decoded from whatever transfer encoding it arrived in.

A part that holds other parts has no body of its own and gives back nothing.

Returns bytes

Message.text()

mail.Message.text() -> string

This part’s body as text, decoded from its transfer encoding and then from its character set.

Returns string

Message.raw_body()

mail.Message.raw_body() -> bytes

The body still encoded, exactly as it sits in the message.

Returns bytes

Message.set_raw_body()

mail.Message.set_raw_body(data) -> Message

Replaces the body with bytes that are already in the transfer encoding the headers name, and drops any parts this held.

The encoding is not applied here and not checked. set_text() and attach() are the calls that do both.

Parameters

  • data (string|bytes)

Returns Message — itself.

Message.set_parts()

mail.Message.set_parts(parts: list) -> Message

Replaces the parts this one holds.

Parameters

  • parts (list) — Message values.

Returns Message — itself.

Message.is_empty()

mail.Message.is_empty() -> bool

Whether this part holds nothing at all: no body and no parts.

Returns bool

Message.source_headers()

mail.Message.source_headers() -> string|nil

The bytes this part’s headers were read from, or nil for a part that was built rather than parsed.

A signature covers the message as it was written, not as it would be written again, which is what this is for.

Returns string|nil

Message.source_body()

mail.Message.source_body() -> bytes|nil

The bytes this part’s body was read from, or nil for a part that was built rather than parsed.

Returns bytes|nil

Message.subject()

mail.Message.subject() -> string

The subject, with any encoded words in it decoded.

Returns string — empty when the message has no subject.

Message.set_subject()

mail.Message.set_subject(text: string) -> Message

Sets the subject, encoding it if it holds characters a header cannot carry.

Parameters

  • text (string)

Returns Message — itself.

Message.sender()

mail.Message.sender() -> Address|nil

The sender, or nil when the message has no From.

Returns Address|nil

Message.to()

mail.Message.to() -> list

The addresses in To.

Returns list — of Address

Message.cc()

mail.Message.cc() -> list

The addresses in Cc.

Returns list — of Address

Message.bcc()

mail.Message.bcc() -> list

The addresses in Bcc.

A message that has been sent should not have this header at all, since the whole point of a blind copy is that the other recipients cannot see it. mail.send() strips it before handing the message over, after taking the recipients from it.

Returns list — of Address

Message.reply_to()

mail.Message.reply_to() -> list

The addresses in Reply-To, falling back to the sender when there is no such header, which is what a mail client does.

Returns list — of Address

Message.recipients()

mail.Message.recipients() -> list

Everyone the message is addressed to: To, Cc and Bcc together, with duplicates removed.

Returns list — of Address

Message.sent_at()

mail.Message.sent_at() -> Date|nil

The Date header as a date, or nil when it is absent or unreadable.

Returns Date|nil

Message.message_id()

mail.Message.message_id() -> string|nil

The Message-ID, with the angle brackets left on, or nil.

Returns string|nil

Message.references()

mail.Message.references() -> list

The message identifiers in References, oldest first, which is the chain of replies this message belongs to.

Returns list — of string

Message.filename()

mail.Message.filename() -> string|nil

The name this part suggests being saved under, from its disposition or, failing that, its type. nil when it suggests none.

Returns string|nil

Message.is_attachment()

mail.Message.is_attachment() -> bool

Whether this part is meant to be offered as a file rather than shown in place.

A part is an attachment when it says so, and also when it says nothing but carries a filename, which is what older mailers do.

Returns bool

Message.find_part()

mail.Message.find_part(mime_type: string) -> Message|nil

The first part of a given type anywhere in the message, or nil.

Parameters

  • mime_type (string) — As text/html.

Returns Message|nil

Message.text_body()

mail.Message.text_body() -> string|nil

The plain text of the message, wherever in the tree it is, or nil when there is none.

Returns string|nil

Message.html_body()

mail.Message.html_body() -> string|nil

The HTML of the message, wherever in the tree it is, or nil when there is none.

Returns string|nil

Message.attachments()

mail.Message.attachments() -> list

Every part of the message that is an attachment.

Returns list — of Message

Message.take_content()

mail.Message.take_content() -> Message

Moves this part’s content into a part of its own and hands it back, leaving this one holding the message-level headers and nothing else.

This is what turns a message into the first child of the multipart that is about to replace it, which is why attach() can wrap a message without the caller rebuilding it.

Returns Message

Message.become_multipart()

mail.Message.become_multipart(subtype: string, parts: list) -> Message

Turns this part into a multipart holding the given parts, under a boundary nothing inside it can contain.

Parameters

  • subtype (string) — mixed, alternative, related or digest.
  • parts (list) — The Message values to put in it.

Returns Message — itself.

Message.set_from()

mail.Message.set_from(person) -> Message

Sets who the message is from, replacing whoever was there.

note.set_from('Richard Ore <richard@example.com>')

Parameters

  • person (string|Address)

Returns Message — itself.

Message.set_sender()

mail.Message.set_sender(person) -> Message

Sets the Sender header, which says who actually put the message on the wire when that is not who it is from.

Parameters

  • person (string|Address)

Returns Message — itself.

Message.add_to()

mail.Message.add_to(people) -> Message

Adds recipients, keeping any already there.

note.add_to('ann@example.com').add_to('bob@example.com')

Parameters

  • people (string|Address|list)

Returns Message — itself.

Message.add_cc()

mail.Message.add_cc(people) -> Message

Adds copied recipients, keeping any already there.

Parameters

  • people (string|Address|list)

Returns Message — itself.

Message.add_bcc()

mail.Message.add_bcc(people) -> Message

Adds blind copied recipients.

The header is removed before the message is handed to a server, after the recipients have been taken from it, which is the whole point of a blind copy.

Parameters

  • people (string|Address|list)

Returns Message — itself.

Message.set_to()

mail.Message.set_to(people) -> Message

Sets the recipients, replacing any already there.

Parameters

  • people (string|Address|list)

Returns Message — itself.

Message.set_cc()

mail.Message.set_cc(people) -> Message

Sets the copied recipients, replacing any already there.

Parameters

  • people (string|Address|list)

Returns Message — itself.

Message.set_bcc()

mail.Message.set_bcc(people) -> Message

Sets the blind copied recipients, replacing any already there.

Parameters

  • people (string|Address|list)

Returns Message — itself.

Message.set_reply_to()

mail.Message.set_reply_to(people) -> Message

Sets where replies should go, when that is not the sender.

Parameters

  • people (string|Address|list)

Returns Message — itself.

Message.set_header()

mail.Message.set_header(name: string, value) -> Message

Sets any header at all, replacing one already there.

The value is written exactly as given, so a header that needs encoding needs it applied first. set_subject() and the address setters do that for the headers where it matters.

Parameters

  • name (string)
  • value (any)

Returns Message — itself.

Message.add_header()

mail.Message.add_header(name: string, value) -> Message

Adds a header, keeping any already there. Received is the header this is for.

Parameters

  • name (string)
  • value (any)

Returns Message — itself.

Message.set_date()

mail.Message.set_date(moment) -> Message

Sets when the message was written.

A message built by mail.message() is dated for you, so this is for saying otherwise.

Parameters

  • moment (?Date) — The current time when not given.

Returns Message — itself.

Message.set_message_id()

mail.Message.set_message_id(id: ?string) -> Message

Sets the message’s own identifier.

Parameters

  • id (?string) — A fresh one, built from the sender’s domain, when not given.

Returns Message — itself.

Message.set_in_reply_to()

mail.Message.set_in_reply_to(id: string) -> Message

Marks the message as a reply to another, which is what puts the two in the same conversation in a mail client.

reply.set_in_reply_to(original.message_id())
     .set_references(original.references() + [original.message_id()])

Parameters

  • id (string) — The identifier of the message being answered.

Returns Message — itself.

Message.set_references()

mail.Message.set_references(ids) -> Message

Sets the chain of identifiers this message belongs to, oldest first.

Parameters

  • ids (list|string)

Returns Message — itself.

Message.set_text()

mail.Message.set_text(text: string, charset: ?string) -> Message

Sets the plain text of the message.

Replaces the text that is already there, wherever in the tree it is. A message that has HTML but no text becomes a multipart/alternative holding both.

Parameters

  • text (string)
  • charset (?string) — utf-8 when not given.

Returns Message — itself.

Message.set_html()

mail.Message.set_html(html: string, charset: ?string) -> Message

Sets the HTML of the message.

Parameters

  • html (string)
  • charset (?string) — utf-8 when not given.

Returns Message — itself.

Message.attach()

mail.Message.attach(part) -> Message

Adds an attachment to the message.

The message becomes a multipart/mixed if it is not one already, with whatever it held before as the first part. That happens once, however many attachments are added.

note.attach(mail.attachment(figures).set_filename('figures.csv'))
note.attach(mail.Attachment.from_file('reports/q3.pdf'))

Parameters

  • part (Attachment|Message) — An attachment, or a part that is already built.

Returns Message — itself.

Raises MessageError if handed anything else.

Message.embed()

mail.Message.embed(part) -> string

Adds something to be shown inside the HTML rather than offered separately, and returns the reference to point an <img> at.

The message becomes a multipart/related around the content it already held, which is what tells a reader the two belong together.

var source = note.embed(mail.attachment(logo).set_filename('logo.png'))

note.set_html('<img src="${source}">')

Parameters

  • part (Attachment|Message)

Returns string — the cid: URL that refers to it.

Raises MessageError if handed anything else.

Message.to_bytes()

mail.Message.to_bytes() -> bytes

The whole message, ready to be written to a socket or a file.

Lines end with a carriage return and a newline, which is what the protocols require whatever the local convention is.

Returns bytes

Message.to_string()

mail.Message.to_string() -> string

The whole message as text.

Returns string

Attachment

class mail.Attachment

Something a message carries alongside what it says: a file to offer, or an image the HTML shows.

Built one call at a time, the way a message is, and handed to Message.attach() or Message.embed().

import mail

var figures = mail.attachment('name,total\nengines,41')
  .set_filename('figures.csv')

echo figures.filename()
echo figures.content_type()
echo figures.size()
figures.csv
text/csv
24

The media type follows from the filename unless it is set. The transfer encoding follows from the contents, and a file is always encoded rather than sent as it is, because bytes that happen to look like text are still bytes.

  • printable — has a @to_string(), so echo and print() show something useful

Constructor

mail.Attachment(data)

Parameters

  • data (string|bytes) — The contents.

Attachment.from_file()

mail.Attachment.from_file(path: string) -> Attachment

Reads the contents from a file on disk, taking the name from the path.

note.attach(Attachment.from_file('reports/q3.pdf'))

Parameters

  • path (string)

Returns Attachment

Raises Error if the file cannot be read.

Attachment.set_filename()

mail.Attachment.set_filename(name: string) -> Attachment

Sets the name to offer the file under.

A name with characters a header cannot carry is encoded, and a long one is split across continuations, on the way out.

Parameters

  • name (string)

Returns Attachment — itself.

Attachment.set_content_type()

mail.Attachment.set_content_type(type: string) -> Attachment

Sets the media type, for when the filename does not settle it.

Parameters

  • type (string) — As application/pdf.

Returns Attachment — itself.

Attachment.set_disposition()

mail.Attachment.set_disposition(kind: string) -> Attachment

Sets whether the part is offered as a file or shown where it sits.

embed() sets this to inline itself, so it is only worth setting for a part that is neither quite.

Parameters

  • kind (string) — attachment or inline.

Returns Attachment — itself.

Attachment.set_cid()

mail.Attachment.set_cid(id: string) -> Attachment

Sets the identifier HTML refers to this part by.

embed() invents one when there is none, so this is for a reference that has to be predictable.

Parameters

  • id (string) — With or without the angle brackets.

Returns Attachment — itself.

Attachment.set_encoding()

mail.Attachment.set_encoding(name: string) -> Attachment

Sets the transfer encoding, overriding the one the contents would have chosen.

Parameters

  • name (string) — One of mail.encoding.TRANSFER_ENCODINGS.

Returns Attachment — itself.

Attachment.set_description()

mail.Attachment.set_description(text: string) -> Attachment

Sets a human-readable description, which some mail clients show beside the file.

Parameters

  • text (string)

Returns Attachment — itself.

Attachment.filename()

mail.Attachment.filename() -> string|nil

The name this will be offered under, or nil.

Returns string|nil

Attachment.content_type()

mail.Attachment.content_type() -> string

The media type this will be sent as, worked out from the filename when it was not set.

Returns string

Attachment.disposition()

mail.Attachment.disposition() -> string

Whether the part is offered as a file or shown where it sits.

Returns string

Attachment.cid()

mail.Attachment.cid() -> string|nil

The identifier HTML refers to this part by, or nil.

Returns string|nil

Attachment.size()

mail.Attachment.size() -> number

How many bytes the contents are.

Returns number

Attachment.data()

mail.Attachment.data() -> bytes

The contents.

Returns bytes

Attachment.to_part()

mail.Attachment.to_part() -> Message

Turns this into the message part that goes on the wire.

attach() and embed() call it. It is here for a program building a tree by hand.

Returns Message

Attachment.to_string()

mail.Attachment.to_string()

2026, Richard Ore and Zuri contributors