Skip to main content

Email Results of a Flow

Question

How can I make an email alert from a step in my Metaflow flow?

Solution

You can format a human-readable report as a Metaflow Card. You can then send the card, which is a simple HTML file, as an email attachment.

1Get Postmark API Token

note

There is nothing specific to Postmark in this guide. Sendgrid, or any other provider, can readily be swapped as an alternative.

Before using the code in this example to send emails from your Python code, you need to set up a Postmark server. You can set this as an environment variable:

export PM_SERVER_API_TOKEN=<YOUR TOKEN>

2Use Postmark to Send Email

The following code snippet will be called from the flow. It uses Postmark to send an email with HTML attached. The HTML will contain the contents of a Metaflow card, a quick way to visualize flow artifacts in HTML.

You can replace this send_email function with another one that uses an email provider of your choice.

emailer.py
import os
import requests
import base64

def send_email(from_email, to_email, subject, html):
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"X-Postmark-Server-Token": os.environ.get('PM_SERVER_API_TOKEN')
}
payload = {
"From": from_email,
"To": to_email,
"Subject": subject,
"HtmlBody": html,
"Attachments": [
{
"Name": "card.html",
"Content": base64.b64encode(html.encode('utf-8')).decode('utf-8'),
"ContentType": "text/html"
}
]
}
response = requests.post("https://api.postmarkapp.com/email", json=payload, headers=headers)
if response.status_code == 200:
print("Email sent successfully!")
else:
print("Failed to send email:", response.json())

3Run Flow

This flow shows how to:

  • Pull an image with an internet request.
  • Store the image in a Metaflow card.
  • Access the card and attach it to an email.
  • Pull an image with an HTTP request to show how you can include images in the card. The @card decorator packages them in the HTML file, so the recipient of the email will be able to see them too.
send_email_from_flow.py
import os
from metaflow import FlowSpec, step, current, card, Flow
from metaflow.cards import Markdown, Image, get_cards

CAT = 'https://upload.wikimedia.org' + \
'/wikipedia/commons/b/b9/CyprusShorthair.jpg'

class EmailCardFlow(FlowSpec):

@card(type='blank')
@step
def start(self):
import requests
resp = requests.get(CAT, headers = {'user-agent': 'metaflow-example'})
current.card.append(Markdown("# Meow mail 🐈"))
current.card.append(Image(resp.content))
self.next(self.end)

@step
def end(self):
from emailer import send_email
send_email(
'eddie@outerbounds.co', # put your email
'eddie@outerbounds.co', # put receiver's email
f'Card from {current.flow_name}/{current.run_id}', # message body
get_cards(
Flow(current.flow_name)[current.run_id]['start'].task
)[0].get()
)

if __name__ == '__main__':
EmailCardFlow()
python send_email_from_flow.py run
     Workflow starting (run-id 6752), see it in the UI at https://ui.dev-content.outerbounds.xyz/p/default/EmailCardFlow/6752
[6752/start/26219 (pid 97152)] Task is starting.
[6752/start/26219 (pid 97152)] Task finished successfully.
[6752/end/26220 (pid 97421)] Task is starting.
[6752/end/26220 (pid 97421)] Email sent successfully!
[6752/end/26220 (pid 97421)] Task finished successfully.
Done! See the run in the UI at https://ui.dev-content.outerbounds.xyz/p/default/EmailCardFlow/6752

2Visualize Artifact

You can use the following command to look at your card and verify the email sent the same in an HTML attachment.

python send_email_from_flow.py card view start

Further Reading