Every email-triggered val on Val Town gets its own email address. When anyone sends a message to that address, Val Town runs your handler function with the parsed email — sender, subject, body, attachments — as its argument. There's no mail server to run, no inbox to poll, and no automation tool in the middle.
Here's the 30-second version:
- Create a val and add a file.
- Click the
+button in the top right of the editor and selectEMAIL. - Your file is assigned an email address ending in
@valtown.email. Click the Email trigger to see it (or pick a custom address). - Any email sent to that address runs your handler.
The handler
Section titled “The handler”An email-triggered file exports a function that receives the incoming message
as an Email object:
export default async function (email: Email) { console.log("Email received!", email.from, email.subject, email.text); for (const file of email.attachments) { console.log(`Filename: ${file.name}`); console.log(`Content Type: ${file.type}`); console.log(`Content: ${await file.text()}`); }}The Email type has this shape:
interface Email { from: string; to: string[]; cc: string | string[] | undefined; bcc: string | string[] | undefined; subject: string | undefined; text: string | undefined; html: string | undefined; attachments: File[]; headers: Record<string, string>;}See the Email trigger reference for the full details,
including custom @valtown.email addresses and the 30MB inbound size limit.
Things to build with it
Section titled “Things to build with it”- Forward receipts to a parser — give the address to your billing tools,
then extract amounts and dates from
email.textand log them to SQLite. - Email-to-todo — send yourself a subject line from your phone and have it land in a todo table (worked example below).
- Support triage — point
support@forwarding at your val and post each message to Slack or Discord, tagged by keyword.
Worked example: email-to-todo
Section titled “Worked example: email-to-todo”This handler turns each incoming email into a row in the val's SQLite database. Send a message with the task as the subject line, and it's saved:
import { sqlite } from "https://esm.town/v/std/sqlite/main.ts";
export default async function (email: Email) { await sqlite.execute(`create table if not exists todos ( id integer primary key, task text not null, sender text, created_at text default current_timestamp )`);
await sqlite.execute({ sql: `insert into todos (task, sender) values (?, ?)`, args: [email.subject ?? "(no subject)", email.from], });}Add an EMAIL trigger to this file, send a message to its address, and check
the val's SQLite tab to see your todo.
Next steps
Section titled “Next steps”- Email trigger reference — custom addresses, limits, and the
full
Emailtype - Send email with std/email — reply from inside your handler
- Forward Gmail to a val — run code on email sent to your existing address