Skip to content

Commit ec754a9

Browse files
committed
Merge branch 'main' into sending-quarto-emails
2 parents 5b574ad + a4c665f commit ec754a9

File tree

9 files changed

+204
-63
lines changed

9 files changed

+204
-63
lines changed

.github/workflows/ci.yml

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,51 @@
11
name: Build and Deploy
22
on: [push, pull_request]
33
permissions:
4-
contents: write
4+
contents: write
55
jobs:
6-
build-and-deploy:
7-
concurrency: ci-${{ github.ref }}
8-
runs-on: ubuntu-latest
9-
permissions:
10-
pull-requests: write
11-
actions: write
12-
contents: write
13-
steps:
14-
- name: Checkout 🛎️
15-
uses: actions/checkout@v4
6+
build-and-deploy:
7+
concurrency: ci-${{ github.ref }}
8+
runs-on: ubuntu-latest
9+
permissions:
10+
pull-requests: write
11+
actions: write
12+
contents: write
13+
steps:
14+
- name: Checkout 🛎️
15+
uses: actions/checkout@v4
1616

17-
# python ----
18-
- uses: actions/checkout@v2
19-
- name: Install uv
20-
uses: astral-sh/setup-uv@v5
21-
- name: "Set up Python"
22-
uses: actions/setup-python@v5
23-
with:
24-
python-version-file: ".python-version"
17+
# python ----
18+
- uses: actions/checkout@v2
19+
- name: Install uv
20+
uses: astral-sh/setup-uv@v5
21+
- name: "Set up Python"
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version-file: ".python-version"
2525

26-
- name: Install the project
27-
run: uv sync --all-extras --dev
26+
- name: Install the project
27+
run: uv sync --all-extras --dev
2828

29-
# quarto docs build ----
30-
- uses: quarto-dev/quarto-actions/setup@v2
31-
with:
32-
tinytex: true
29+
# quarto docs build ----
30+
- uses: quarto-dev/quarto-actions/setup@v2
31+
with:
32+
tinytex: true
3333

34-
- name: Build docs
35-
run: |
36-
uv run quarto render
34+
- name: Build docs
35+
run: |
36+
uv run quarto render
3737
38-
- name: Deploy
39-
uses: JamesIves/github-pages-deploy-action@v4
40-
if: ${{ github.ref == 'refs/heads/main' }}
41-
with:
42-
force: false
43-
folder: _book
44-
clean-exclude: pr-preview
38+
- name: Deploy
39+
uses: JamesIves/github-pages-deploy-action@v4
40+
if: ${{ github.ref == 'refs/heads/main' }}
41+
with:
42+
force: false
43+
folder: _site
44+
clean-exclude: pr-preview
4545

46-
- name: Deploy (preview)
47-
uses: rossjrw/pr-preview-action@v1
48-
# if in a PR
49-
if: ${{ github.event_name == 'pull_request' }}
50-
with:
51-
source-dir: _book
46+
- name: Deploy (preview)
47+
uses: rossjrw/pr-preview-action@v1
48+
# if in a PR
49+
if: ${{ github.event_name == 'pull_request' }}
50+
with:
51+
source-dir: _site

_quarto.yml

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,38 @@
11
project:
2-
type: book
2+
type: website
33

4-
book:
4+
website:
55
title: "Email for Data Science"
6-
author: "Norah Jones"
7-
date: "10/6/2025"
8-
chapters:
9-
- index.qmd
10-
- summary.qmd
11-
- part: Content
12-
chapters:
13-
- content-embedding.qmd
14-
- content-layout.qmd
15-
- part: Configuring
16-
chapters:
17-
- configuring-subject.qmd
18-
- configuring-attachments.qmd
19-
- part: Orchestrating
20-
chapters:
21-
- orchestrating-auth.qmd
22-
- orchestrating-tests.qmd
6+
site-url: https://posit-dev.github.io/email-for-data-science/
7+
navbar:
8+
left:
9+
- text: User Guide
10+
file: index.qmd
11+
- text: Reference
12+
file: reference/index.qmd
13+
sidebar:
14+
- title: User Guide
15+
contents:
16+
- index.qmd
17+
- summary.qmd
18+
- section: Content
19+
contents:
20+
- content-embedding.qmd
21+
- content-layout.qmd
22+
- section: Configuring
23+
contents:
24+
- configuring-subject.qmd
25+
- configuring-attachments.qmd
26+
- section: Orchestrating
27+
contents:
28+
- orchestrating-auth.qmd
29+
- orchestrating-tests.qmd
2330

2431

2532
format:
2633
html:
2734
theme:
2835
- cosmo
2936
- brand
30-
pdf:
31-
documentclass: scrreprt
32-
3337

3438

436 KB
Loading

assets/whole-game-email.png

135 KB
Loading

assets/whole-game-fancy.png

611 KB
Loading

assets/whole-game-quarto.png

46 KB
Loading

examples/quarto-report.qmd

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
title: "Email report generation with Quarto"
3+
jupyter: python3
4+
format: email
5+
---
6+
7+
:::{.email}
8+
9+
:::{.subject}
10+
Report on cars
11+
:::
12+
13+
14+
```{python}
15+
# | code-fold: true
16+
# | eval: false
17+
from data_polars import sp500
18+
19+
sp500.head(10).style
20+
```
21+
22+
:::

reference/index.qmd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
title: This will be the reference index
3+
---

summary.qmd

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,119 @@
1010
* Orchestration: testing email
1111
-->
1212

13+
Emailing reports is a critical but challenging task for data science.
14+
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.
1315

16+
In this tutorial, we'll walk through the whole game of sending email. We'll start with this simple example:
17+
18+
```{python}
19+
# | code-fold: true
20+
# | eval: false
21+
import os
22+
from dotenv import load_dotenv
23+
from data_polars import sp500
24+
import redmail
25+
26+
load_dotenv()
27+
gmail_address = os.environ["GMAIL_ADDRESS"]
28+
gmail_app_password = os.environ["GMAIL_APP_PASSWORD"]
29+
30+
31+
email_subject = "Report on Cars"
32+
email_body = sp500.head(10).style.as_raw_html(inline_css=True)
33+
34+
# This is here to emphasize the sender does not have to be the same as the receiver
35+
email_receiver = gmail_address
36+
37+
redmail.gmail.username = gmail_address
38+
redmail.gmail.password = gmail_app_password
39+
40+
redmail.gmail.send(
41+
subject=email_subject,
42+
receivers=[email_receiver],
43+
html=email_body,
44+
)
45+
```
46+
47+
48+
![](./assets/whole-game-email-annotated.png){width=50% fig-align="center"}
49+
50+
* **Content**: writing the text of the email, including plots and tables.
51+
* **Composing**: setting up the subject, sender, and receivers.
52+
* **Orchestrating**: previewing, testing, and scheduling the email.
53+
54+
We'll also quickly review writing more advanced content layouts, and authoring email reports that involve running code with Quarto.
55+
56+
## A simple email
57+
58+
![](./assets/whole-game-email.png){width=50% fig-align="center"}
59+
60+
* Generate and preview
61+
* Authenticate (may need to refer to its own authentication page in guide)
62+
* Send
63+
64+
```{python}
65+
# | code-fold: true
66+
# | eval: false
67+
import os
68+
from dotenv import load_dotenv
69+
from data_polars import sp500
70+
import redmail
71+
72+
load_dotenv()
73+
gmail_address = os.environ["GMAIL_ADDRESS"]
74+
gmail_app_password = os.environ["GMAIL_APP_PASSWORD"]
75+
76+
77+
email_subject = "Report on Cars"
78+
email_body = sp500.head(10).style.as_raw_html(inline_css=True)
79+
80+
# This is here to emphasize the sender does not have to be the same as the receiver
81+
email_receiver = gmail_address
82+
83+
redmail.gmail.username = gmail_address
84+
redmail.gmail.password = gmail_app_password
85+
86+
redmail.gmail.send(
87+
subject=email_subject,
88+
receivers=[email_receiver],
89+
html=email_body,
90+
)
91+
```
92+
93+
## Configure: subject, recipients, attachments
94+
95+
* you could attach the data as a CSV attachment
96+
97+
98+
## Orchestrate: save and preview
99+
100+
* previewing email
101+
* intermediate json, easy for sending email later
102+
* embedding images makes previewing hard
103+
* can always email to yourself (or use a test service like Litmus)
104+
105+
## Content: Quarto authoring
106+
107+
Here's our same simple email generated using quarto.
108+
109+
110+
111+
![](./assets/whole-game-quarto.png){width=50% fig-align="center"}
112+
113+
* Focused on basic configuring, and content
114+
* Sending happens via our tool
115+
* Generate using quarto render
116+
* Can preview email
117+
118+
## Content: advanced layouts
119+
120+
We'll highlight the key pieces (discussed later in this guide) to go from that simple email, to a more advanced on like below:
121+
122+
![](./assets/whole-game-fancy.png){width=50% fig-align="center"}
123+
124+
125+
## Fridge
14126

15127
In this tutorial, we are going to send an email from a Gmail account. To do so, you will need to [create an App Password](https://support.google.com/accounts/answer/185833). Note this is only possible if you've [enabled 2-step verification](https://support.google.com/accounts/answer/185839).
16128

0 commit comments

Comments
 (0)