Tutorial¶
Basic Composition¶
eletter
can be used to construct a basic text e-mail using the compose()
function like so:
from eletter import compose
msg = compose(
subject="The subject of the e-mail",
from_="sender@domain.com",
to=["recipient@domain.com", "another.recipient@example.nil"],
text="This is the body of the e-mail. Write what you want here!\n",
)
Note
Observe that the from_
argument is spelled with an underscore. It has
to be this way, because plain old from
is a keyword in Python.
If you want to construct an HTML e-mail, use the html
keyword instead of
text
:
from eletter import compose
msg = compose(
subject="The subject of the e-mail",
from_="sender@domain.com",
to=["recipient@domain.com", "another.recipient@example.nil"],
html=(
"<p>This is the <strong>body</strong> of the <em>e</em>-mail."
" <span style='color: red;'>Write what you want here!</span></p>\n"
),
)
By specifying both text
and html
, you’ll get an e-mail whose HTML part
is displayed if the e-mail reader supports it and whose text part is displayed
instead on lower-tech clients.
from eletter import compose
msg = compose(
subject="The subject of the e-mail",
from_="sender@domain.com",
to=["recipient@domain.com", "another.recipient@example.nil"],
text="This is displayed on plain text clients.\n",
html="<p>This is displayed on graphical clients.<p>\n",
)
Addresses¶
In the examples so far, e-mail addresses have just been specified as, well,
addresses. However, addresses usually belong to people or organizations with
names; we can include these names alongside the addresses by constructing
Address
objects from pairs of “display names” and e-mail addresses:
from eletter import Address, compose
msg = compose(
subject="The subject of the e-mail",
from_=Address("Sender's name goes here", "sender@domain.com"),
to=[
Address("Joe Q. Recipient", "recipient@domain.com"),
Address("Jane Z. Another-Recipient", "another.recipient@example.nil"),
],
text="This is the body of the e-mail. Write what you want here!\n",
)
Sometimes addresses come in named groups. We can represent these with the
Group
class, which takes a name for the group and an iterable of address
strings and/or Address
objects:
from eletter import Address, Group, compose
msg = compose(
subject="The subject of the e-mail",
from_="sender@domain.com",
to=[
Group(
"friends",
[
Address("Joe Q. Recipient", "recipient@domain.com"),
Address("Jane Z. Another-Recipient", "another.recipient@example.nil"),
"anonymous@nowhere.nil",
],
),
Address("Mr. Not-in-a-Group", "ungrouped@unkno.wn"),
Group(
"enemies",
[
"that.guy@over.there",
"knows.what.they.did@ancient.history",
Address("Anemones", "sea.flora@ocean.net"),
],
),
],
text="This is the body of the e-mail. Write what you want here!\n",
)
CC, BCC, etc.¶
Besides From and To addresses, compose()
also
accepts optional arguments for CC, BCC,
Reply-To, and Sender addresses:
from eletter import compose
msg = compose(
from_=Address("Mme E.", "me@here.com"),
to=["you@there.net", Address("Thaddeus Hem", "them@hither.yon")],
cc=[Address("Cee Cee Cecil", "ccc@seesaws.cc"), "coco@nu.tz"],
bcc=[
"eletter@depository.nil",
Address("Secret Cabal", "illuminati@new.world.order"),
"mom@house.home",
],
reply_to="replyee@some.where",
sender="steven.ender@big.senders",
subject="To: Everyone",
text="Meeting tonight! You know the place. Bring pizza.\n",
)
Note
The to
, cc
, and bcc
arguments always take lists or iterables of
addresses. from_
and reply_to
, on the other hand, can be set to
either a single address or an iterable of addresses. sender
must
always be a single address.
Attachments¶
Attachments come in two common types: text and binary. eletter
provides a
class for each, TextAttachment
and BytesAttachment
.
We can construct a BytesAttachment
as follows:
from eletter import BytesAttachment
attachment = BytesAttachment(
b'... binary data goes here ...',
filename="name-of-attachment.dat"
)
This will create an application/octet-stream attachment with an
“attachment” disposition (meaning that most clients will just display it as a
clickable icon). To set the content type to something more informative, set
the content_type
parameter to the relevant MIME type. To have the
attachment displayed inline (generally only an option for images & videos), set
the inline
parameter to true. Hence:
from eletter import BytesAttachment
attachment = BytesAttachment(
b'... binary data goes here ...',
filename="name-of-attachment.png"
content_type="image/png",
inline=True,
)
If your desired attachment exists as a file on your system, you can construct a BytesAttachment
from the file directly with the from_file()
classmethod:
from eletter import BytesAttachment
attachment = BytesAttachment.from_file(
"path/to/file.dat",
content_type="application/x-custom",
inline=True,
)
The basename of the given file will be used as the filename of the attachment.
(If you want to use a different name, set the filename
attribute on the attachment after creating it.) If content_type
is not
given, the MIME type of the file will be guessed based on its file extension.
The TextAttachment
class is analogous to BytesAttachment
, except that it is
constructed from a str
instead of bytes
, and the content_type
(which
defaults to "text/plain"
) must be a text type.
Once you’ve created some attachment objects, they can be attached to an e-mail
by passing them in a list as the attachments
argument:
from eletter import BytesAttachment, TextAttachment, compose
spreadsheet = TextAttachment.from_file("income.csv")
image = BytesAttachment.from_file("cat.jpg")
msg = compose(
subject="That data you wanted",
from_="sender@domain.com",
to=["recipient@domain.com"],
text="Here's that data you wanted, sir. And also the ... other thing.\n",
attachments=[spreadsheet, image],
)
Attaching E-mails to E-mails¶
On rare occasions, you may have an e-mail that you want to completely embed in
a new e-mail as an attachment. With eletter
, you can do this with the
EmailAttachment
class. It works the same as BytesAttachment
and
TextAttachment
, except that the content must be an
email.message.EmailMessage
instance, and you can’t set the
Content-Type (which is always message/rfc822). Like
the other attachment classes, EmailAttachment
also has a
from_file()
classmethod for constructing an instance from an
e-mail in a file.
Date and Extra Headers¶
compose()
takes two more parameters that we haven’t mentioned yet. First is
date
, which lets you set the Date header in an e-mail to a
given datetime.datetime
instance. Second is headers
, which lets you set
arbitrary extra headers on an e-mail by passing in a dict
. Each value in the
dict
must be either a string or (if you want to set multiple headers with the
same name) an iterable of strings.
from datetime import datetime
from eletter import compose
msg = compose(
subject="The subject of the e-mail",
from_="sender@domain.com",
to=["recipient@domain.com", "another.recipient@example.nil"],
text="This is the body of the e-mail. Write what you want here!\n",
date=datetime(2021, 3, 10, 17, 56, 36).astimezone(),
headers={
"User-Agent": "My Mail Application v.1",
"Priority": "urgent",
"Comments": [
"I like e-mail.",
"But no one ever looks at e-mails' sources, so no one will ever know.",
]
},
)
multipart/mixed Messages¶
All the e-mails constructed so far, when viewed in an e-mail client, have their
attachments listed at the bottom. What if we want to mix & match attachments
and text, switching from text to an attachment and then back to text?
eletter
lets you do this by providing TextBody
and HTMLBody
classes
that can be &
-ed with attachments to produce multipart/mixed
messages, like so:
from eletter import BytesAttachment, TextBody
part1 = TextBody("Look at the pretty kitty!\n")
snuffles = BytesAttachment.from_file("snuffles.jpeg", inline=True)
part2 = TextBody("Now look at this dog.\n")
rags = BytesAttachment.from_file("rags.jpeg", inline=True)
part3 = TextBody("Which one is cuter?\n")
mixed = part1 & snuffles & part2 & rags & part3
We can then convert mixed
into an EmailMessage
by calling
its compose()
method, which takes the same arguments as the
compose()
function, minus text
, html
, and attachments
.
msg = mixed.compose(
subject="The subject of the e-mail",
from_="sender@domain.com",
to=["recipient@domain.com", "another.recipient@example.nil"],
)
When the resulting e-mail is viewed in a client, you’ll see three lines of text with images between them.
Tip
As a shortcut, you can combine a bare str
with an eletter
object
using |
or the other overloaded operators described below (&
and
^
), and that str
will be automatically converted to a TextBody
.
The example above could thus be rewritten:
from eletter import BytesAttachment, TextBody
snuffles = BytesAttachment.from_file("snuffles.jpeg", inline=True)
rags = BytesAttachment.from_file("rags.jpeg", inline=True)
mixed = (
"Look at the pretty kitty!\n"
& snuffles
& "Now look at this dog.\n"
& rags
& "Which one is cuter?\n"
)
multipart/alternative Messages¶
Now that we know how to construct mixed messages, how do we use them to create
messages with both mixed-HTML and mixed-text payloads where the client shows
whichever mixed payload it can support? The answer is the |
operator;
using it to combine two eletter
objects will give you a
multipart/alternative object, representing an e-mail message with
two different versions of the same content that the client will then pick
between.
from eletter import BytesAttachment, HTMLBody, TextBody
text1 = TextBody("Look at the pretty kitty!\n")
text2 = TextBody("Now look at this dog.\n")
text3 = TextBody("Which one is cuter?\n")
html1 = HTMLBody("<p>Look at the <em>pretty kitty</em>!</p>\n")
html2 = HTMLBody("<p>Now look at this <strong>dog</strong>.</p>\n")
html2 = HTMLBody("<p>Which one is <span style='color: pink'>cuter</span>?</p>\n")
snuffles = BytesAttachment.from_file("snuffles.jpeg", inline=True)
rags = BytesAttachment.from_file("rags.jpeg", inline=True)
mixed_text = text1 & snuffles & text2 & rags & text3
mixed_html = html1 & snuffles & html2 & rags & html3
alternative = mixed_text | mixed_html
The alternative
object can then be converted to an e-mail with the same
compose()
method that mixed objects have.
Tip
The parts of a multipart/alternative message should generally
be placed in increasing order of preference, which means that the text part
should be on the left of the |
and the HTML part should be on the
right.
Sending E-mails¶
Once you’ve constructed your e-mail and turned it into an
EmailMessage
object, you can send it using Python’s smtplib
,
imaplib
, or mailbox
modules or using a compatible third-party library.
Actually doing this is beyond the scope of this tutorial & library, but may I
suggest outgoing, by yours truly?