Asserting on HTML email content
Check the rendered half of a message: the HTML body is delivered exactly as it arrived, so you can assert on markup, buttons and merge fields.
How it works
body_html is the HTML part of the message, untouched. That matters when the thing under test is the template: a broken merge field, a missing button, a stale year in the footer. If a sender only ships a plain text part, this field is empty and the message still has body_text.
Assert on the markup
Substring checks catch most template regressions without a parser.
msg = inbox.latest()
assert "Reset your password" in msg.html
assert "{{" not in msg.html, "an unrendered merge field shipped"
Parse it when a substring is not enough
For structure — a specific button, an alt attribute, a table cell — hand the HTML to a parser you already have.
from bs4 import BeautifulSoup
soup = BeautifulSoup(msg.html, "html.parser")
button = soup.select_one("a.btn-primary")
assert button.text.strip() == "Verify email"
Worth knowing
Render it in the dashboard while you write the test
Every message has an HTML view in the web inbox, with trackers blocked. Looking at what actually arrived is usually faster than guessing which selector to assert on.
See also
Plain text content
The text part of a message, exactly as sent. Simpler to assert on than HTML, and the part most transactional senders get wrong.
LinksEvery clickable link in a message is pulled out for you as an ordered, de-duplicated list, so magic-link and password-reset flows are one assertion away.
Message propertiesThe envelope around the body: who sent it, exactly which address it reached, what the subject line said and when it landed.