• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
paulkinlan

paulkinlan

email-agent

Public
Like
2
email-agent
Home
Code
8
admin
2
db
1
lib
6
CLAUDE.md
README.md
E
email-handler-v2.ts
H
main.ts
C
task-runner-v2.ts
Connections
Environment variables
2
Branches
2
Pull requests
Remixes
History
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
Code
/
Search
README.md

📧 Email Agent

An email-based personal AI agent built on Val Town. Interact entirely via email — send messages, tasks, ideas, and memories to memory-do@valtown.email and the agent processes everything inside a persistent per-user sandbox.

How It Works

Send an email. The agent reads it, figures out what you mean, and acts on it.

You → "Remind me Thursday to call the dentist"
     → "I like chickens"
     → "What's the latest news on WebAssembly?"
     → "Every Monday, give me a summary of my active tasks"

Under the hood:

  1. Receive — Your email arrives and is written to your personal sandbox inbox
  2. Boot — A Deno sandbox spins up with your persistent volume mounted
  3. Execute — Claude reads your CLAUDE.md, checks the inbox, and handles everything using full Linux tooling (Bash, file I/O, git, curl, etc.)
  4. Reply — You get an email back with results, follow-up questions, and any generated file attachments (rendered as HTML with a plain-text fallback)

Voice-Friendly

Emails sent via voice transcription work great. The agent is tuned for misspellings, run-on sentences, and implicit intent — it interprets what you mean, not what you literally typed.

Sandbox Architecture

Each user gets their own persistent Deno sandbox volume — a private filesystem that acts as the agent's brain. There's no classifier step and no custom tool-use loop. The agent runs inside the sandbox using the Claude Agent SDK with native Linux tools.

┌───────────────────────────────────────────────────────┐
│              VAL TOWN (orchestrator)                   │
│                                                       │
│  email-handler-v2.ts → receives email                 │
│       ├─ Get/create user                              │
│       ├─ Write email to sandbox inbox                 │
│       ├─ Boot sandbox → run agent-loop.mjs            │
│       ├─ Read response from sandbox outbox            │
│       ├─ Process schedule.json → scheduled_tasks      │
│       └─ Send reply email (HTML + plain text)         │
│                                                       │
│  task-runner-v2.ts → runs every 15 min                │
│       ├─ Query due scheduled_tasks                    │
│       ├─ Boot sandbox for each due task               │
│       └─ Email results, reschedule if recurring       │
└───────────────────┬───────────────────────────────────┘
                    │
                    ▼
┌───────────────────────────────────────────────────────┐
│          DENO SANDBOX (per-user microVM)               │
│                                                       │
│  Runs: node /opt/agent/agent-loop.mjs                 │
│  Uses: Claude Agent SDK with Bash/Read/Write/Edit/    │
│        Glob/Grep tools                                │
│  Mounts: user's persistent volume at /home/user/      │
│                                                       │
│  /home/user/                                          │
│  ├── CLAUDE.md        ← agent instructions            │
│  ├── agents.md        ← persona/identity              │
│  ├── TODO.md          ← persistent task list          │
│  ├── memories/        ← knowledge as markdown files   │
│  ├── ideas/           ← captured ideas                │
│  ├── people/          ← people directory              │
│  ├── repos/           ← cloned git repositories       │
│  ├── workspace/       ← scratch area                  │
│  ├── .ssh/            ← SSH keys                      │
│  └── .mail/                                           │
│      ├── inbox/       ← tasks arrive here             │
│      └── outbox/      ← responses go here             │
└───────────────────────────────────────────────────────┘

The Volume IS the User's Brain

All user state lives on their persistent sandbox volume as plain files:

WhatWhere
Memories & factsmemories/*.md (one file per topic, Claude organizes)
Tasks & remindersTODO.md (Claude maintains it directly)
Ideasideas/*.md (one per idea)
Peoplepeople/*.md (one per person)
PreferencesEmbedded in CLAUDE.md (Claude edits it!)
Git reposrepos/ (persistent across sessions)
SSH keys.ssh/ (standard Unix location)

No SQL for content storage. Claude manages files naturally because it has full filesystem access.

Available Tools (in the sandbox)

The agent uses the Claude Agent SDK's built-in tools — not custom function calls:

ToolWhat It Does
BashRun any command — git, npm, python, curl, etc.
ReadRead any file on the volume
WriteWrite any file on the volume
EditEdit files with diffs
GlobFind files by pattern
GrepSearch file contents

This means the agent can do multi-step work: search your memories with grep, fetch a web page with curl, cross-reference with your stored projects, save what it learned as a markdown file, clone a repo, run code — all in one email exchange.

Task Types

TypeBehaviorExample
InstantExecutes immediately, reply in same email"What's the weather in London?"
FutureScheduled for a specific time"Remind me Thursday at 10am to call dentist"
RecurringRepeats on a schedule"Every Monday, summarize my active tasks"

Future and recurring tasks are processed by a task runner that checks every 15 minutes. The agent writes a schedule.json to its outbox, and the orchestrator picks it up and inserts it into the scheduled_tasks table.

Per-User Personalization

Each user's sandbox has a CLAUDE.md and agents.md that the agent reads at the start of every session. The agent can edit these files directly — so when you say:

  • "Be more casual and use emojis"
  • "Call me Paul"
  • "Always use bullet points"

…the agent updates its own instructions. Changes persist across sessions because the volume is persistent.

Attachments

Full bidirectional support:

  • Inbound — Email attachments (CSV, JSON, text, code files, etc.) are included in the task description sent to the sandbox
  • Outbound — The agent can generate files inside the sandbox, which are read from the outbox and sent back as email attachments

Project Structure

email-agent/
  README.md                   ← You are here
  CLAUDE.md                   — Internal dev guide and conventions
  main.ts                     — HTTP admin/status endpoint
  email-handler-v2.ts         — Email ingestion → sandbox execution
  task-runner-v2.ts           — Scheduled task processor (every 15 min)
  lib/
    sandbox-orchestrator.ts   — Volume CRUD, sandbox boot, result reading
    sandbox-agent-loop.ts     — Source for agent-loop.mjs (runs in sandbox)
    sandbox-claude-md.ts      — Builds CLAUDE.md and agents.md for volumes
    scheduler.ts              — Natural language → ISO datetime
  db/
    schema-v2.ts              — SQLite schema (users, scheduled_tasks, executions)
    schema.ts                 — Legacy v1 schema (entries, FTS, links)
    queries.ts                — Legacy v1 queries
  admin/
    build-snapshot.ts         — Builds the toolchain sandbox snapshot
    test-orchestrator.ts      — Test harness for sandbox orchestration

Tech Stack

  • Runtime: Val Town (Deno-based serverless)
  • Agent: Claude Agent SDK running inside Deno sandboxes
  • Sandboxes: Deno Sandbox SDK — per-user persistent volumes with full Linux tooling
  • Database: Project-scoped SQLite (thin routing layer only — users, scheduled tasks, execution logs)
  • Email: Val Town email sending/receiving
  • LLM: Claude via Anthropic SDK (agent loop + scheduler)

Database (SQLite)

SQLite is a thin routing layer — it does not store user content (that lives on sandbox volumes):

TablePurpose
usersEmail → user mapping + volume reference
scheduled_tasksWhen to wake up a sandbox (task runner polls this)
sandbox_executionsExecution log for debugging and cost tracking
email_dedupShort-lived deduplication table for email retries

Environment Variables

VariableRequiredDescription
ANTHROPIC_API_KEYYesUsed by Claude API calls (agent loop, scheduler)
DENO_DEPLOY_TOKENYesUsed for Deno sandbox and volume management

Admin / Debugging

The HTTP endpoint (main.ts) provides:

  • GET / or GET /stats — System-wide stats (entry counts, users, links)
  • GET /user/:email — Full user profile with entries grouped by type
  • GET /migrate — Run one-time data migration from legacy tables

Note: main.ts currently queries the legacy v1 schema tables (entries, entry_links). A future update will switch it to the v2 tables.

Limitations

  • Sandbox boot latency — ~2–5 seconds per execution; not ideal for trivial questions
  • Concurrency — Deno Sandbox has a concurrent execution limit during pre-release; may need queuing with multiple simultaneous users
  • No admin auth — HTTP endpoints are open
  • No rate limiting — No flood protection
  • No cost alerting — sandbox_executions logs cost but there's no per-user limits or alerts yet
  • File attachments (outbound) — Stubbed but not yet fully implemented
  • SSH key management — Infrastructure exists (.ssh/ dir on volume) but no user-facing flow yet
  • Snapshot rollout — No automated process for updating the sandbox toolchain across existing users

License

Built on Val Town. Do what you want with it.

Code
admindblibCLAUDE.mdREADME.md
E
email-handler-v2.ts
H
main.ts
C
task-runner-v2.ts
FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
AboutAlternativesPricingBlogNewsletterCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.