API

The compose() Function

eletter.compose(*, subject: str, to: Iterable[Union[str, email.headerregistry.Address, email.headerregistry.Group]], from_: Optional[Union[str, email.headerregistry.Address, email.headerregistry.Group, Iterable[Union[str, email.headerregistry.Address, email.headerregistry.Group]]]] = None, text: Optional[str] = None, html: Optional[str] = None, cc: Optional[Iterable[Union[str, email.headerregistry.Address, email.headerregistry.Group]]] = None, bcc: Optional[Iterable[Union[str, email.headerregistry.Address, email.headerregistry.Group]]] = None, reply_to: Optional[Union[str, email.headerregistry.Address, email.headerregistry.Group, Iterable[Union[str, email.headerregistry.Address, email.headerregistry.Group]]]] = None, sender: Optional[Union[str, email.headerregistry.Address]] = None, date: Optional[datetime.datetime] = None, headers: Optional[Mapping[str, Union[str, Iterable[str]]]] = None, attachments: Optional[Iterable[eletter.classes.Attachment]] = None)email.message.EmailMessage[source]

Construct an EmailMessage instance from a subject, From address, To addresses, and a plain text and/or HTML body, optionally accompanied by attachments and other headers.

All parameters other than subject, to, and at least one of text and html are optional.

Parameters
  • subject (str) – The e-mail’s Subject line

  • to (iterable of addresses) – The e-mail’s To line

  • from (address or iterable of addresses) – The e-mail’s From line. Note that this argument is spelled with an underscore, as “from” is a keyword in Python.

  • text (str) – The contents of a text/plain body for the e-mail. At least one of text and html must be specified.

  • html (str) – The contents of a text/html body for the e-mail. At least one of text and html must be specified.

  • cc (iterable of addresses) – The e-mail’s CC line

  • bcc (iterable of addresses) – The e-mail’s BCC line

  • reply_to (address or iterable of addresses) – The e-mail’s Reply-To line

  • sender (address) – The e-mail’s Sender line. The address must be a string or Address, not a Group.

  • date (datetime) – The e-mail’s Date line

  • attachments (iterable of attachments) – A collection of attachments to append to the e-mail

  • headers (mapping) – A collection of additional headers to add to the e-mail. A header value may be either a single string or an iterable of strings to add multiple headers with the same name. If you wish to set an otherwise-unsupported address header like Resent-From to a list of addresses, use the format_addresses() function to first convert the addresses to a string.

Return type

email.message.EmailMessage

Raises

ValueError – if neither text nor html is set

Addresses

Addresses in eletter can be specified in three ways:

  • As an "address@domain.com" string giving just a bare e-mail address

  • As an eletter.Address("Display Name", "address@domain.com") instance pairing a person’s name with an e-mail address

  • As an eletter.Group("Group Name", iterable_of_addresses) instance specifying a group of addresses (strings or Address instances)

Note

eletter.Address and eletter.Group are actually just subclasses of Address and Group from email.headerregistry with slightly more convenient constructors. You can also use the standard library types directly, if you want to.

class eletter.Address(display_name: str, address: str)[source]

A combination of a person’s name and their e-mail address

class eletter.Group(display_name: str, addresses: Iterable[Union[str, email.headerregistry.Address]])[source]

An e-mail address group

MailItem Classes

class eletter.MailItem[source]

Base class for all eletter message components

compose(*, subject: str, to: Iterable[Union[str, email.headerregistry.Address, email.headerregistry.Group]], from_: Optional[Union[str, email.headerregistry.Address, email.headerregistry.Group, Iterable[Union[str, email.headerregistry.Address, email.headerregistry.Group]]]] = None, cc: Optional[Iterable[Union[str, email.headerregistry.Address, email.headerregistry.Group]]] = None, bcc: Optional[Iterable[Union[str, email.headerregistry.Address, email.headerregistry.Group]]] = None, reply_to: Optional[Union[str, email.headerregistry.Address, email.headerregistry.Group, Iterable[Union[str, email.headerregistry.Address, email.headerregistry.Group]]]] = None, sender: Optional[Union[str, email.headerregistry.Address]] = None, date: Optional[datetime.datetime] = None, headers: Optional[Mapping[str, Union[str, Iterable[str]]]] = None)email.message.EmailMessage[source]

Convert the MailItem into an EmailMessage with the item’s contents as the payload and with the given subject, From address, To addresses, and optional other headers.

All parameters other than subject and to are optional.

Parameters
  • subject (str) – The e-mail’s Subject line

  • to (iterable of addresses) – The e-mail’s To line

  • from (address or iterable of addresses) – The e-mail’s From line. Note that this argument is spelled with an underscore, as “from” is a keyword in Python.

  • cc (iterable of addresses) – The e-mail’s CC line

  • bcc (iterable of addresses) – The e-mail’s BCC line

  • reply_to (address or iterable of addresses) – The e-mail’s Reply-To line

  • sender (address) – The e-mail’s Sender line. The address must be a string or Address, not a Group.

  • date (datetime) – The e-mail’s Date line

  • headers (mapping) – A collection of additional headers to add to the e-mail. A header value may be either a single string or an iterable of strings to add multiple headers with the same name. If you wish to set an otherwise-unsupported address header like Resent-From to a list of addresses, use the format_addresses() function to first convert the addresses to a string.

Return type

email.message.EmailMessage

Attachments

class eletter.Attachment[source]

Base class for the attachment classes

class eletter.BytesAttachment(content: bytes, filename: str, *, content_id: Optional[str] = None, content_type: str = NOTHING, inline: bool = False)[source]

A binary e-mail attachment. content_type defaults to "application/octet-stream".

content: bytes

The body of the attachment

content_id: Optional[str]

Content-ID header value for the item

content_type: str

The Content-Type of the attachment

filename: str

The filename of the attachment

classmethod from_file(path: Union[bytes, str, os.PathLike[bytes], os.PathLike[str]], content_type: Optional[str] = None, inline: bool = False, content_id: Optional[str] = None)BytesAttachment[source]

Construct a BytesAttachment from the contents of the file at path. The filename of the attachment will be set to the basename of path. If content_type is None, the Content-Type is guessed based on path’s file extension.

inline: bool

Whether the attachment should be displayed inline in clients

class eletter.EmailAttachment(content: email.message.EmailMessage, filename: str, *, content_id: Optional[str] = None, inline: bool = False)[source]

A message/rfc822 e-mail attachment

content: email.message.EmailMessage

The body of the attachment

content_id: Optional[str]

Content-ID header value for the item

filename: str

The filename of the attachment

classmethod from_file(path: Union[bytes, str, os.PathLike[bytes], os.PathLike[str]], inline: bool = False, content_id: Optional[str] = None)EmailAttachment[source]

Construct an EmailAttachment from the contents of the file at path. The filename of the attachment will be set to the basename of path.

inline: bool

Whether the attachment should be displayed inline in clients

class eletter.TextAttachment(content: str, filename: str, *, content_id: Optional[str] = None, content_type: str = NOTHING, inline: bool = False)[source]

A textual e-mail attachment. content_type defaults to "text/plain" and must have a maintype of text.

content: str

The body of the attachment

content_id: Optional[str]

Content-ID header value for the item

content_type: str

The Content-Type of the attachment

filename: str

The filename of the attachment

classmethod from_file(path: Union[bytes, str, os.PathLike[bytes], os.PathLike[str]], content_type: Optional[str] = None, encoding: Optional[str] = None, errors: Optional[str] = None, inline: bool = False, content_id: Optional[str] = None)TextAttachment[source]

Construct a TextAttachment from the contents of the file at path. The filename of the attachment will be set to the basename of path. If content_type is None, the Content-Type is guessed based on path’s file extension. encoding and errors are used when opening the file and have no relation to the Content-Type.

inline: bool

Whether the attachment should be displayed inline in clients

Body Classes

class eletter.HTMLBody(content: str, *, content_id: Optional[str] = None)[source]

A text/html e-mail body

content: str

The HTML source of the body

content_id: Optional[str]

Content-ID header value for the item

class eletter.TextBody(content: str, *, content_id: Optional[str] = None)[source]

A text/plain e-mail body

content: str

The plain text body

content_id: Optional[str]

Content-ID header value for the item

Multipart Classes

class eletter.Multipart[source]

Base class for all multipart classes. All such classes are mutable sequences of MailItems supporting the usual methods (construction from an iterable, subscription, append(), pop(), etc.).

class eletter.Alternative(content=NOTHING, *, content_id: Optional[str] = None)[source]

A multipart/alternative e-mail payload. E-mails clients will display the resulting payload by choosing whichever part they support best.

An Alternative instance can be created by combining two or more MailItems with the | operator:

text = TextBody("This is displayed on plain text clients.\n")
html = HTMLBody("<p>This is displayed on graphical clients.<p>\n")

alternative = text | html

Likewise, additional MailItems can be added to an Alternative instance with the |= operator:

# Same as above:
alternative = Alternative()
alternative |= TextBody("This is displayed on plain text clients.\n")
alternative |= HTMLBody("<p>This is displayed on graphical clients.<p>\n")

Using | to combine a MailItem with a str automatically converts the str to a TextBody:

# Same as above:

text = "This is displayed on plain text clients.\n"
html = HTMLBody("<p>This is displayed on graphical clients.<p>\n")

alternative = text | html

assert alternative.contents == [
    TextBody("This is displayed on plain text clients.\n"),
    HTMLBody("<p>This is displayed on graphical clients.<p>\n"),
]

When combining two Alternative instances with | or |=, the contents are “flattened”:

# Same as above:
txtalt = Alternative([
    TextBody("This is displayed on plain text clients.\n")
])
htmlalt = Alternative([
    HTMLBody("<p>This is displayed on graphical clients.<p>\n")
])
alternative = txtalt | htmlalt
assert alternative.contents == [
    TextBody("This is displayed on plain text clients.\n"),
    HTMLBody("<p>This is displayed on graphical clients.<p>\n"),
]
content: List[eletter.classes.MailItem]

The MailItems contained within the instance

content_id: Optional[str]

Content-ID header value for the item

class eletter.Mixed(content=NOTHING, *, content_id: Optional[str] = None)[source]

A multipart/mixed e-mail payload. E-mails clients will display the resulting payload one part after another, with attachments displayed inline if their inline attribute is set.

A Mixed instance can be created by combining two or more MailItems with the & operator:

text = TextBody("Look at the pretty kitty!\n")
image = BytesAttachment.from_file("snuffles.jpeg", inline=True)
sig = TextBody("Sincerely, Me\n")

mixed = text & image & sig

Likewise, additional MailItems can be added to a Mixed instance with the &= operator:

# Same as above:
mixed = Mixed()
mixed &= TextBody("Look at the pretty kitty!\n")
mixed &= BytesAttachment.from_file("snuffles.jpeg", inline=True)
mixed &= TextBody("Sincerely, Me\n")

Using & to combine a MailItem with a str automatically converts the str to a TextBody:

# Same as above:
image = BytesAttachment.from_file("snuffles.jpeg", inline=True)

mixed = "Look at the pretty kitty!\n" & image & "Sincerely, Me\n"

assert mixed.contents == [
    TextBody("Look at the pretty kitty!\n"),
    BytesAttachment.from_file("snuffles.jpeg", inline=True),
    TextBody("Sincerely, Me\n"),
]

When combining two Mixed instances with & or &=, the contents are “flattened”:

part1 = Mixed()
part1 &= TextBody("Look at the pretty kitty!\n")
part1 &= BytesAttachment.from_file("snuffles.jpeg", inline=True)

part2 = Mixed()
part2 &= TextBody("Now look at this dog.\n")
part2 &= BytesAttachment.from_file("rags.jpeg", inline=True)
part2 &= TextBody("Which one is cuter?\n")

mixed = part1 & part2

assert mixed.contents == [
    TextBody("Look at the pretty kitty!\n"),
    BytesAttachment.from_file("snuffles.jpeg", inline=True),
    TextBody("Now look at this dog.\n"),
    BytesAttachment.from_file("rags.jpeg", inline=True),
    TextBody("Which one is cuter?\n"),
]
content: List[eletter.classes.MailItem]

The MailItems contained within the instance

content_id: Optional[str]

Content-ID header value for the item

class eletter.Related(content=NOTHING, start: Optional[str] = None, *, content_id: Optional[str] = None)[source]

A multipart/related e-mail payload. E-mail clients will display the part indicated by the start parameter, or the first part if start is not set. This part may refer to other parts (e.g., images or CSS stylesheets) by their Content-ID headers, which can be generated using email.utils.make_msgid().

Note

Content-ID headers begin & end with angle brackets (<...>), which need to be stripped off before including the ID in the starting part.

A Related instance can be created by combining two or more MailItems with the ^ operator:

from email.utils import make_msgid

img_cid = make_msgid()

html = HTMLBody(
    "<p>Look at the pretty kitty!</p>"
    f'<img src="cid:{img_cid[1:-1]}"/>"
    "<p>Isn't he <em>darling</em>?</p>"
)

image = BytesAttachment.from_file("snuffles.jpeg", content_id=img_cid)

related = html ^ image

Likewise, additional MailItems can be added to a Related instance with the ^= operator:

# Same as above:

img_cid = make_msgid()

related = Related()

related ^= HTMLBody(
    "<p>Look at the pretty kitty!</p>"
    f'<img src="cid:{img_cid[1:-1]}"/>"
    "<p>Isn't he <em>darling</em>?</p>"
)

related ^= BytesAttachment.from_file("snuffles.jpeg", content_id=img_cid)

Using ^ to combine a MailItem with a str automatically converts the str to a TextBody, though this is generally not all that useful, as you’ll usually want to create Related instances from HTMLBodys instead.

When combining two Related instances with ^ or ^=, the contents are “flattened”:

# Same as above:

img_cid = make_msgid()

htmlrel = Related([
    HTMLBody(
        "<p>Look at the pretty kitty!</p>"
        f'<img src="cid:{img_cid[1:-1]}"/>"
        "<p>Isn't he <em>darling</em>?</p>"
    )
])

imgrel = Related([
    BytesAttachment.from_file("snuffles.jpeg", content_id=img_cid)
])

related = htmlrel ^ imgrel

assert related.contents == [
    HTMLBody(
        "<p>Look at the pretty kitty!</p>"
        f'<img src="cid:{img_cid[1:-1]}"/>"
        "<p>Isn't he <em>darling</em>?</p>"
    ),
    BytesAttachment.from_file("snuffles.jpeg", content_id=img_cid),
]
content: List[eletter.classes.MailItem]

The MailItems contained within the instance

content_id: Optional[str]

Content-ID header value for the item

start: Optional[str]

The Content-ID of the part to display (defaults to the first part)

Utility Functions

eletter.assemble_content_type(maintype: str, subtype: str, **params: str)str[source]

Construct a Content-Type string from a maintype, subtype, and some number of parameters

Raises

ValueError – if f"{maintype}/{subtype}" is an invalid Content-Type

eletter.format_addresses(addresses: Iterable[Union[str, email.headerregistry.Address, email.headerregistry.Group]], encode: bool = False)str[source]

Convert an iterable of e-mail address strings (of the form “foo@example.com”, without angle brackets or a display name), Address objects, and/or Group objects into a formatted string. If encode is False (the default), non-ASCII characters are left as-is. If it is True, non-ASCII display names are converted into RFC 2047 encoded words, and non-ASCII domain names are encoded using Punycode.

eletter.reply_quote(s: str, prefix: str = '> ')str[source]

Quote a text following the de facto standard for replying to an e-mail; that is, prefix each line of the text with "> " (or a custom prefix), and if a line already starts with the prefix, omit any trailing whitespace from the newly-added prefix (so "> already quoted" becomes ">> already quoted").

If the resulting string does not end with a newline, one is added. The empty string is treated as a single line.