Skip to content

Tutorial 6 — Email with Mailer

This tutorial demonstrates PyGo’s native mailer package — SMTP email sending with templates and bulk support.

A contact form that sends email notifications using:

  • SMTP configuration
  • HTML templates
  • Bulk sending capabilities
pygo.toml
[mailer]
host = "smtp.gmail.com"
port = 587
username = "your-app@gmail.com"
password = "your-app-password" # Use environment variable in production
from = "no-reply@yourapp.com"
# Optional: enable queueing for high volume
queue = true

Create app/templates/emails/contact.html:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Thank You for Your Message</title>
</head>
<body style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
<h1>Thank You, {{ name }}!</h1>
<p>We received your message:</p>
<blockquote style="border-left: 3px solid #ccc; padding-left: 1em; margin: 1em 0;">
{{ message }}
</blockquote>
<p>Our team will respond within 24 hours.</p>
<hr>
<p style="color: #666; font-size: 0.9em;">
PyGo Framework • Automated Email System
</p>
</body>
</html>
from mailer import Send, WithTemplate
# Load template once at startup
contact_template = WithTemplate("emails/contact.html")
handler contact:
contact(name: String, email: Email, message: String) -> String:
# Log the contact submission
log.info("Contact form submitted", {"email": email})
# Send confirmation email to user
Send(
to=[email],
subject="Thank you for your message!",
html=contact_template.render({
"name": name,
"message": message,
}),
)
# Send notification to admin
Send(
to=["admin@yourapp.com"],
subject=f"New contact form submission from {name}",
html=f"""
<h2>New Contact Submission</h2>
<p><strong>Name:</strong> {name}</p>
<p><strong>Email:</strong> {email}</p>
<p><strong>Message:</strong> {message}</p>
""",
)
return '<div class="alert alert-success">✅ Message sent! We will reply soon.</div>'
<form
hx-post="/contact"
hx-swap="outerHTML"
x-data="{ submitting: false }"
@submit="submitting = true"
>
<div>
<label>Name</label>
<input type="text" name="name" required />
</div>
<div>
<label>Email</label>
<input type="email" name="email" required />
</div>
<div>
<label>Message</label>
<textarea name="message" rows="5" required></textarea>
</div>
<button type="submit" :disabled="submitting">
<span x-show="!submitting">Send Message</span>
<span x-show="submitting">⏳ Sending...</span>
</button>
</form>
from mailer import Send
Send(
to=["user@example.com"],
subject="Welcome!",
text="Your account was created.",
html="<h1>Welcome!</h1><p>Your account was created.</p>",
)
from mailer import WithTemplate
tmpl = WithTemplate("emails/welcome.html")
Send(
to=[user.email],
subject="Welcome to the platform!",
html=tmpl.render({"name": user.name, "verify_url": verify_url}),
)
from mailer import SendBulk
messages = []
for user in users:
messages.append({
"to": [user.email],
"subject": "Newsletter Update",
"html": render_template("emails/newsletter.html", {"user": user}),
})
# Send all emails (uses queue if enabled)
SendBulk(messages)
# When queue=true in config, emails are queued and sent
# asynchronously by the pygo worker process
# No code changes needed — Send() automatically queues
Terminal window
# Use environment variables for credentials
export MAIL_HOST="smtp.sendgrid.net"
export MAIL_PORT="587"
export MAIL_USERNAME="apikey"
export MAIL_PASSWORD="SG.xxxxx"
export MAIL_FROM="noreply@yourapp.com"
# pygo.toml — reference env vars
[mailer]
host = "${MAIL_HOST}"
port = "${MAIL_PORT}"
username = "${MAIL_USERNAME}"
password = "${MAIL_PASSWORD}"
from = "${MAIL_FROM}"
from mailer import TestMailer
test_mailer = TestMailer()
# Capture sent emails instead of sending
test_mailer.capture()
Send(to=["test@example.com"], subject="Test", html="<p>Hi</p>")
# Inspect captured emails
emails = test_mailer.sent()
assert len(emails) == 1
assert emails[0].to == ["test@example.com"]
assert emails[0].subject == "Test"
  1. Always use templates for HTML emails — never inline HTML in .pgo files
  2. Enable queueing for high volume (>100 emails/minute)
  3. Use environment variables for credentials — never hardcode
  4. Test with TestMailer — never send real emails in tests