what

Email

Send transactional emails via SMTP or the Resend API. Use HTML templates with #variable# syntax.

Email Configuration

Add an [email] section to what.toml. Choose either SMTP or Resend as the transport.

what.toml
[email]
from = "[email protected]"
from_name = "My App"
template_dir = "emails"     # default: "emails"
KeyDefaultDescription
fromSender email address (required)
from_nameDisplay name (optional, e.g., "My App")
template_diremailsDirectory containing email templates

SMTP Setup

Configure SMTP credentials under [email.smtp]. Use ${ENV_VAR} syntax to keep secrets out of your config file.

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

[email.smtp]
host = "smtp.mailgun.org"
port = 587                     # default: 587
username = "${SMTP_USER}"
password = "${SMTP_PASS}"

The ${SMTP_USER} and ${SMTP_PASS} placeholders are resolved from environment variables at send time. If the variable is not set, the raw string is used as-is (and a warning is logged).

Tip: Most SMTP providers use port 587 with STARTTLS. The framework connects using STARTTLS by default.

Resend API

Use the Resend API as an alternative to SMTP:

what.toml
[email]
from = "[email protected]"

[email.api]
provider = "resend"
api_key = "${RESEND_API_KEY}"

Configure either [email.smtp] or [email.api], not both. The framework uses whichever is present.

Email Templates

Email templates are HTML files in the emails/ directory (or whatever template_dir is set to). They use the same #variable# syntax as page templates.

emails/welcome.html
<!DOCTYPE html>
<html>
<body style="font-family: sans-serif; padding: 20px;">
  <h1>Welcome, #name#!</h1>
  <p>Thanks for signing up. Your account is ready.</p>
  <p>
    <a href="https://example.com/dashboard"
       style="background: #2563eb; color: white; padding: 10px 20px;
              text-decoration: none; border-radius: 4px;">
      Go to Dashboard
    </a>
  </p>
  <p style="color: #666; font-size: 0.875rem;">
    If you didn't create this account, you can ignore this email.
  </p>
</body>
</html>

Templates support all filters: #name|capitalize#, #amount|currency#, #date|date:"%B %d, %Y"#, etc.

Sending from Forms

Trigger emails from form submissions using hidden fields with the w-email-* prefix:

site/contact.html
<form w-action="create" w-store="contacts">
  <input type="hidden" name="w-email-to" value="[email protected]">
  <input type="hidden" name="w-email-subject" value="New contact form submission">
  <input type="hidden" name="w-email-template" value="contact-notification">

  <label>Name</label>
  <input type="text" name="name" required>

  <label>Email</label>
  <input type="email" name="email" required>

  <label>Message</label>
  <textarea name="message" required></textarea>

  <button type="submit" class="btn btn-primary">Send</button>
</form>
Hidden FieldDescription
w-email-toRecipient email address (required to trigger email)
w-email-subjectEmail subject line (default: "Notification")
w-email-templateTemplate name from emails/ directory (without .html)

All form field values are passed as template variables. In the example above, #name#, #email#, and #message# are available in the contact-notification.html template.

emails/contact-notification.html
<h2>New Contact Submission</h2>
<p><strong>From:</strong> #name# (#email#)</p>
<p><strong>Message:</strong></p>
<p>#message#</p>

Async Delivery

Emails are sent via a background job queue. The form submission responds immediately while the email is delivered asynchronously. This means:

  • Users don't wait for the SMTP handshake or API call
  • A slow or failed email delivery doesn't affect the form response
  • Failed deliveries are logged but don't return errors to the user
Note: If no [email] section is configured in what.toml, forms with w-email-to fields log a warning and skip the email silently. The form submission itself still succeeds.