-
Notifications
You must be signed in to change notification settings - Fork 1
Emails
In this project, we're going to be writing automated emails fairly frequently - for example, when a user requests to have their username or password changed. Below is a guide to Mailtrap, the testing platform we're using to simulate automated emails, and how to write both code to send emails and testing code to simulate receiving emails.
Mailtrap is a service that allows us to simulate the sending and receiving of emails without actually doing so. This is very useful, since it allows us to make changes to our automated emails (add more info, change styling) without these emails being released to the general public. Furthermore, because Google disabled less secure apps, we can no longer use regular Gmail accounts to send/receive emails programmatically, and thus we have to rely on a third-party platform.
I have created a Mailtrap account under [email protected]
, and you can log in to the account to see if your tests work properly. Be sure to empty the inbox frequently, as there is a limit of 50 emails in the free plan.
You can use the functionality provided by flask-mail
to send emails via Mailtrap (or any other email address that you have access to, once we switch over).
All you have to do is construct a Message
according to flask-mail
's specs for messages, and import the mail
variable from common.plugins
. An example to send an email with Mailtrap:
from flask_mail import Message
from common.plugins import mail
def register():
# OTHER CODE HERE #
# Send it over to email
message = Message(
"Account registered for Week in Wonderland",
sender="[email protected]",
recipients=[json["email"]]
)
message.body = f"Your code is: {code}"
mail.send(message)
To write a message in HTML (which is what we'll be doing), you will need to use message.html
instead of message.body
.
When testing out our backend, we will need to see if emails are actually being sent, and if the content inside those emails are what we expect. Mailtrap uses POP3, so to read emails, we will have to use poplib
(which is a pre-installed package in Python).
def test_success():
# Check that we get an email sent
mailbox = poplib.POP3("pop3.mailtrap.io", 1100)
mailbox.user(os.environ["MAILTRAP_USERNAME"])
mailbox.pass_(os.environ["MAILTRAP_PASSWORD"])
(before, _) = mailbox.stat()
# OTHER CODE HERE #
# Verify recipient
raw_email = b"\n".join(mailbox.retr(1)[1])
parsed_email = email.message_from_bytes(raw_email)
assert parsed_email["To"] == "[email protected]"
Some important functions in both poplib
and email
(another builtin Python library):
-
POP3.user
and.pass_
are used to "login" to the virtual mailbox itself, and provide credentials so that the other functions can be called. -
.stat
returns two values - the number of emails and the number of bytes in all emails. Usually, only the first return value is needed. -
.retr
takes in one argument, which is the index email we want to see more details of (1-indexed, so the first email would be index 1)..retr
returns three values, but the second one (index 1) contains the raw bytes of the email itself, separated by line.
A raw email looks something like this:
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Account registered for Week in Wonderland
From: [email protected]
To: [email protected]
Date: Sat, 11 Jun 2022 12:56:09 +0000
Message-ID: <165495216700.88.6066714241386512699@b981ac2a75b6>
Your code is: 219381
If we want to access a specific field (say, the recipient of the email as seen in the To:
field), we want to be able to parse this email into some data structure that's more intuitive in Python. Luckily, the email
module does just that with email.message_from_bytes
(bytes
is needed because mailbox.retr()[1]
is an array of bytes, not an actual string). We can then access the recipient by simply doing ["To"]
.