Email Setup
Configure transactional email for your app. The What framework sends emails via a background job queue — form submissions return immediately while delivery happens asynchronously.
How It Works
When a form submission includes w-email-* hidden fields, the What framework queues an email job immediately after saving the record. The user is redirected without waiting for the SMTP handshake or API call to complete. A slow or failed delivery doesn't block the form response.
- Emails are sent via a background job queue — non-blocking
- Templates use the same
#variable#syntax as page templates - All form field values are available as template variables
- Failed deliveries are logged server-side but don't surface errors to users
SMTP Setup
Add an [email] section and [email.smtp] to what.toml. Use ${ENV_VAR} to keep credentials out of your config file.
[email]
from = "[email protected]"
from_name = "My App"
[email.smtp]
host = "smtp.gmail.com"
port = 587
username = "${SMTP_USER}"
password = "${SMTP_PASS}"
| Key | Default | Description |
|---|---|---|
from | — | Sender email address (required) |
from_name | — | Display name shown to recipients |
template_dir | emails | Directory containing HTML email templates |
host | — | SMTP server hostname (required) |
port | 587 | SMTP port (587 for STARTTLS, 465 for SSL) |
username | — | SMTP username (supports ${ENV_VAR}) |
password | — | SMTP password (supports ${ENV_VAR}) |
Common SMTP hosts:
| Provider | Host | Port |
|---|---|---|
| Gmail | smtp.gmail.com | 587 |
| Mailgun | smtp.mailgun.org | 587 |
| Postmark | smtp.postmarkapp.com | 587 |
| SendGrid | smtp.sendgrid.net | 587 |
| Amazon SES | email-smtp.us-east-1.amazonaws.com | 587 |
Resend API
Use the Resend API instead of SMTP. Resend is simpler to set up and has a generous free tier (3,000 emails/month).
[email]
from = "[email protected]"
from_name = "My App"
[email.api]
provider = "resend"
api_key = "${RESEND_API_KEY}"
Configure either [email.smtp] or [email.api], not both. The What framework uses whichever is present.
[email protected] sender you can use before verifying your own domain.
Email Templates
Email templates are HTML files in the emails/ directory (configurable via template_dir). They use the same #variable# syntax as page templates, including all filters.
<!DOCTYPE html>
<html>
<body style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<h1 style="color: #111;">Welcome, #name|capitalize#!</h1>
<p>Thanks for signing up at #site_name#. Your account is ready.</p>
<p style="margin: 30px 0;">
<a href="#dashboard_url#"
style="background: #2563eb; color: white; padding: 12px 24px;
text-decoration: none; border-radius: 6px; font-weight: bold;">
Go to Dashboard
</a>
</p>
<p style="color: #666; font-size: 0.875rem;">
If you didn't create this account, you can safely ignore this email.
</p>
</body>
</html>
Templates support all built-in filters:
#name|capitalize#— capitalize the first letter#amount|currency#— format as currency#created_at|date:"%B %d, %Y"#— format dates#message|truncate:200#— truncate long text
Trigger Email from a Form
Add hidden fields with the w-email- prefix to any form. When the form is submitted, the What framework saves the record and queues an email automatically.
<form w-action="create" w-store="messages" w-redirect="/contact?sent=1" w-validate>
<input type="hidden" name="w-email-to" value="[email protected]">
<input type="hidden" name="w-email-subject" value="New Contact Message">
<input type="hidden" name="w-email-template" value="contact">
<label>Name</label>
<input type="text" name="name" w-required placeholder="Your name">
<label>Email</label>
<input type="email" name="email" w-required w-type="email" placeholder="[email protected]">
<label>Message</label>
<textarea name="message" w-required placeholder="How can we help?"></textarea>
<button type="submit" class="btn btn-primary">Send Message</button>
</form>
| Hidden Field | Description |
|---|---|
w-email-to | Recipient address — required to trigger an email |
w-email-subject | Email subject line (default: "Notification") |
w-email-template | Template filename from emails/, without .html |
All form field values are passed as template variables. In this example, #name#, #email#, and #message# are available inside emails/contact.html.
<h2>New Contact Submission</h2>
<p><strong>From:</strong> #name# <#email#></p>
<hr>
<p>#message#</p>
Environment Variables
Any value in what.toml can reference an environment variable using ${VAR_NAME} syntax. Variables are resolved at server startup from the process environment.
For local development, create a .env file in your project root. The What framework loads it automatically if present:
[email protected]
SMTP_PASS=your-app-password
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx
.env files to version control. Add .env to your .gitignore. In production, set environment variables through your hosting platform (Docker env flags, systemd environment files, Hetzner/DigitalOcean app settings).
If an environment variable is not set at runtime, the What framework logs a warning and uses the raw string as-is (e.g., "${SMTP_PASS}"), which will cause the email to fail. Check your server logs if email is not working.