Emailing reports is a critical but challenging task for data science. Mainly because you have to figure out generating the email content, configuring pieces like attachments, and orchestrating it (e.g. testing, or sending on a schedule). Moreover, content can range from simple layouts to more complex ones.
In this tutorial, we’ll walk through the whole game of sending email. We’ll start with this simple example:
Code
import osfrom dotenv import load_dotenvfrom data_polars import sp500import redmailload_dotenv()gmail_address = os.environ["GMAIL_ADDRESS"]gmail_app_password = os.environ["GMAIL_APP_PASSWORD"]email_subject ="Report on Cars"email_body = sp500.head(10).style.as_raw_html(inline_css=True)# This is here to emphasize the sender does not have to be the same as the receiveremail_receiver = gmail_addressredmail.gmail.username = gmail_addressredmail.gmail.password = gmail_app_passwordredmail.gmail.send( subject=email_subject, receivers=[email_receiver], html=email_body,)
Content: writing the text of the email, including plots and tables.
Composing: setting up the subject, sender, and receivers.
Orchestrating: previewing, testing, and scheduling the email.
We’ll also quickly review writing more advanced content layouts, and authoring email reports that involve running code with Quarto.
A simple email
Generate and preview
Authenticate (may need to refer to its own authentication page in guide)
Send
Code
import osfrom dotenv import load_dotenvfrom data_polars import sp500import redmailload_dotenv()gmail_address = os.environ["GMAIL_ADDRESS"]gmail_app_password = os.environ["GMAIL_APP_PASSWORD"]email_subject ="Report on Cars"email_body = sp500.head(10).style.as_raw_html(inline_css=True)# This is here to emphasize the sender does not have to be the same as the receiveremail_receiver = gmail_addressredmail.gmail.username = gmail_addressredmail.gmail.password = gmail_app_passwordredmail.gmail.send( subject=email_subject, receivers=[email_receiver], html=email_body,)
Configure: subject, recipients, attachments
you could attach the data as a CSV attachment
Orchestrate: save and preview
previewing email
intermediate json, easy for sending email later
embedding images makes previewing hard
can always email to yourself (or use a test service like Litmus)
Content: Quarto authoring
Here’s our same simple email generated using quarto.
Focused on basic configuring, and content
Sending happens via our tool
Generate using quarto render
Can preview email
Content: advanced layouts
We’ll highlight the key pieces (discussed later in this guide) to go from that simple email, to a more advanced on like below:
This is just one of many options: it is also possible to send emails in Python from other email providers (Outlook, ProtonMail, etc.), or even from a custom domain. To skip ahead to a discussion of alternative sending methods, see Authentication
Once you’ve created your App Password, that is used as your Gmail password for sending with Python.
There are many ways to store the password seperate from your email-sending code, so as to not expose any sensitive information. One such approach uses a .env file, and the `dotenv and os packages.
Subject: An Example Email From: YourGmail@gmail.com To: Recipient@gmail.com Date: Tue, 14 Oct 2025 20:25:38 -0000
For a number of decades in the middle of the 20th century, the nature of the soccer ball changed quite drastically in each iteration of the World Cup. The accepted number of panels constantly changed (and is still changing to this day). Presented below are the years during the transition from local manufacturers to multi-national corporations.