Skip to content

Commit

Permalink
Merge pull request #3 from WATonomous/benz/multipart
Browse files Browse the repository at this point in the history
Use multipart to send text and HTML emails
  • Loading branch information
ben-z authored Aug 18, 2024
2 parents 3c0adf8 + 2644746 commit a903360
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=http;AccountName=devst

SMTP_HOST=smtp
SMTP_PORT=1025
SMTP_USERNAME=testuser
SMTP_USERNAME=testuser@example.com
SMTP_PASSWORD=testpass

# Comma-separated list of Google Group keys (group's email address, group alias, or the unique group ID)
Expand Down
136 changes: 80 additions & 56 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import time
import urllib.parse
from contextlib import asynccontextmanager
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from html.parser import HTMLParser
from smtplib import SMTP, SMTPNotSupportedError
from textwrap import dedent

Expand All @@ -17,6 +20,18 @@
from utils import get_azure_table_client, random_str


class HTMLTextFilter(HTMLParser):
"""
Converts HTML to plain text.
Derived from https://stackoverflow.com/a/55825140/4527337
"""

text = ""

def handle_data(self, data):
self.text += data


@asynccontextmanager
async def lifespan(app: FastAPI):
scheduler.add_job(cleanup, trigger=CronTrigger.from_crontab("* * * * *"))
Expand Down Expand Up @@ -85,67 +100,76 @@ def sign_up(req: SignUpRequest, request: Request):
)
confirmation_url = f"{app_url}/confirm/{req.mailing_list}/{urllib.parse.quote_plus(req.email)}/{code}"

# Support both HTML and plain text emails
# https://stackoverflow.com/a/882770/4527337
msg = MIMEMultipart("alternative")
msg["Subject"] = f"Confirm Your Email Subscription for '{req.mailing_list}'"
msg["From"] = os.getenv("SMTP_SEND_AS", os.environ["SMTP_USERNAME"])
msg["To"] = req.email
msg["Reply-To"] = os.getenv("SMTP_REPLY_TO", os.environ["SMTP_USERNAME"])

msg_html_body = f"""
<body>
<h1>Confirm Your Subscription</h1>
<p>Thanks for signing up for updates from "{req.mailing_list}"!</p>
<p>Please confirm your subscription by clicking the button below. This confirmation email will expire in {CODE_TTL_SEC // 60} minutes.</p>
<a class="confirmation-button" href="{confirmation_url}">Confirm Email</a>
<p>If the button above does not work, please copy and paste the following URL into your browser:</p>
<pre class="monospace-text">{confirmation_url}</pre>
<p> This email was sent to {req.email}. If you did not request this subscription, no further action is required. You won't be subscribed if you don't click the confirmation link.</p>
</body>
"""
msg_html = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Confirmation</title>
<style>
body {{
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
color: #333;
background-color: #f4f4f4;
padding: 20px;
}}
.confirmation-button {{
background-color: #007BFF;
color: white;
padding: 10px 20px;
text-decoration: none;
border-radius: 5px;
font-size: 18px;
}}
.confirmation-button:hover {{
background-color: #0056b3;
}}
.monospace-text {{
font-family: 'Courier New', monospace;
}}
</style>
</head>
{msg_html_body}
</html>
"""
msg_html_parser = HTMLTextFilter()
msg_html_parser.feed(msg_html_body)
msg_text = msg_html_parser.text

msg.attach(MIMEText(msg_text, "plain"))
msg.attach(MIMEText(msg_html, "html"))

with SMTP(os.environ["SMTP_HOST"], port=os.environ["SMTP_PORT"]) as smtp:
try:
smtp.starttls()
except SMTPNotSupportedError as e:
logger.warning(f"SMTP server does not support STARTTLS: {e}. Attempting to send email without encryption.")
logger.warning(
f"SMTP server does not support STARTTLS: {e}. Attempting to send email without encryption."
)
smtp.login(os.environ["SMTP_USERNAME"], os.environ["SMTP_PASSWORD"])
smtp.sendmail(
os.environ["SMTP_USERNAME"],
req.email,
dedent(
f"""
Subject: Confirm Your Email Subscription for '{req.mailing_list}'
From: {os.getenv("SMTP_SEND_AS", os.environ["SMTP_USERNAME"])}
To: {req.email}
Reply-To: {os.getenv("SMTP_REPLY_TO", os.environ["SMTP_USERNAME"])}
MIME-Version: 1.0
Content-Type: text/html; charset="utf-8"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Confirmation</title>
<style>
body {{
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
color: #333;
background-color: #f4f4f4;
padding: 20px;
}}
a {{
background-color: #007BFF;
color: white;
padding: 10px 20px;
text-decoration: none;
border-radius: 5px;
font-size: 18px;
}}
a:hover {{
background-color: #0056b3;
}}
.link-text {{
font-family: 'Courier New', monospace;
}}
</style>
</head>
<body>
<h1>Confirm Your Email</h1>
<p>Please confirm your email address by clicking the button or the link below to receiving updates from "{req.mailing_list}":</p>
<a href="{confirmation_url}">Confirm Email</a>
<p>If the button above does not work, please copy and paste the following URL into your browser:</p>
<p class="link-text">{confirmation_url}</p>
<p>If you did not request this subscription, no further action is required.</p>
</body>
</html>
"""
),
)
smtp.send_message(msg)

app.runtime_info["num_signups"] += 1

Expand Down

0 comments on commit a903360

Please sign in to comment.