what

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.

what.toml
[email]
from = "[email protected]"
from_name = "My App"

[email.smtp]
host = "smtp.gmail.com"
port = 587
username = "${SMTP_USER}"
password = "${SMTP_PASS}"
KeyDefaultDescription
fromSender email address (required)
from_nameDisplay name shown to recipients
template_diremailsDirectory containing HTML email templates
hostSMTP server hostname (required)
port587SMTP port (587 for STARTTLS, 465 for SSL)
usernameSMTP username (supports ${ENV_VAR})
passwordSMTP password (supports ${ENV_VAR})
Tip: Most SMTP providers (Gmail, Mailgun, Postmark, SendGrid) use port 587 with STARTTLS. The What framework connects using STARTTLS by default. For port 465, SMTP over TLS is used automatically.

Common SMTP hosts:

ProviderHostPort
Gmailsmtp.gmail.com587
Mailgunsmtp.mailgun.org587
Postmarksmtp.postmarkapp.com587
SendGridsmtp.sendgrid.net587
Amazon SESemail-smtp.us-east-1.amazonaws.com587

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).

what.toml
[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.

Tip: To use Resend, your sender email must be on a verified domain in the Resend dashboard. For testing, Resend provides a shared [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.

emails/welcome.html
<!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.

site/contact.html
<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 FieldDescription
w-email-toRecipient address — required to trigger an email
w-email-subjectEmail subject line (default: "Notification")
w-email-templateTemplate 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.

emails/contact.html
<h2>New Contact Submission</h2>
<p><strong>From:</strong> #name# &lt;#email#&gt;</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:

.env
[email protected]
SMTP_PASS=your-app-password
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx
Note: Never commit .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.