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

drewmcdonald

inbox

Public
Like
inbox
Home
Code
13
mcp
2
server
5
.vtignore
CLAUDE.md
PROJECT.md
README.md
apiClient.ts
C
cleanupCron.ts
deno.json
E
emailHandler.ts
H
httpHandler.ts
localMcpServer.ts
onetimeSetup.ts
Branches
1
Pull requests
Remixes
History
Environment variables
2
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
/
README.md
Code
/
README.md
Search
10/21/2025
README.md

POP Inbox

An POP-like inbox for my note taking system. Rather than pushing things directly into my notes, I hold them here until I'm ready to thoughtfully engage with them. This separation allows me to capture freely in the moment, then process intentionally when I'm ready.

Ok, but why?

When I yeet raw information into my vault, I don't get any benefit from it. This POP-like approach forces me to engage with things before storing them more permanently.

Also, more technically, Obsidian doesn't have a push-friendly API - you can't just send data to your vault from anywhere. Instead, you have to pull data into the vault from a client. Doing so requires a unified receptacle, that's this val!

Lifecycle

The inbox follows a simple three-stage lifecycle:

  1. Ingest - Content arrives via email or HTTP API and is stored as an unprocessed record
  2. Process - You review and integrate the content into your knowledge system (e.g., via Claude or your custom scripts)
  3. Archive - Once integrated, the record is marked as processed to keep your inbox clean

A cleanup cron job periodically removes old processed records.

Quick Start

See CLAUDE.md for AI assistant guidelines and PROJECT.md for detailed architecture.

Setup

  1. Remix on Val.town

  2. Run one-time database setup

    Run the one-time setup script to create the database table in your val.town sqlite database.

  3. Configure Environment Variables

    In val.town, set these secrets:

    • INBOX_API_TOKEN - Create a secure random token for API auth (via e.g. openssl rand -hex 32)
    • INCOMING_EMAIL_ALLOWLIST - Comma-separated email addresses allowed to send records to the inbox (e.g. name@gmail.com,name@work.org)
  4. Set up MCP Server

    { "mcpServers": { "inbox": { "command": "deno", "args": [ "run", "--allow-net", "--allow-env", "/path/to/inbox/localMcpServer.ts" ], "env": { "INBOX_API_URL": "https://your-username-httphandler.web.val.run", "INBOX_API_TOKEN": "your-api-token" } } } }

    Alternatively, use the published val URL without downloading:

    "command": "deno", "args": [ "run", "--allow-net", "--allow-env", "https://esm.town/v/your-username/inbox/localMcpServer.ts" ]

    Consider adding -r to deno run to capture frequent updates.

  5. Capture and Process your Inbox!

    You can now forward emails to your inbox, create records from an MCP, or set up your own automations to create records directly.

    You can then chat with Claude about your inbox, or write a program that dumps them into your obsidian vault for you, or whatever you want! You have MCP, an HTTP API, and a typescript client. Go nuts!

File Organization

obsidian-inbox/
├── server/            # Core http server logic
├── mcp/               # Model Context Protocol server
├── emailHandler.ts    # Val.town email trigger
├── httpHandler.ts     # Val.town HTTP endpoint
├── cleanupCron.ts     # Val.town cron job
├── localMcpServer.ts  # Entry point for local MCP
├── onetimeSetup.ts    # One-time setup script
└── apiClient.ts       # HTTP client (used by MCP)

Usage

Sending Content to Inbox

Via Email: Send email to your Val.town email address from an allowlisted sender. The email body and attachments are automatically saved.

Via HTTP API:

curl -X POST https://your-val.web.val.run/api/record \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "raw": "Content to process later", "subject": "Meeting notes", "from": "manual-entry", "date": "2025-10-21T10:00:00Z", "attachmentBlobIds": [] }'

Via typescript client:

import { getClient } from 'https://esm.town/v/${VALPATH}/apiClient.ts'; const client = getClient(); // see the MCP tools for more const res = await client.api.record.$post({ json: params, });

Processing Inbox Items

With Claude Desktop (MCP): In Claude Desktop, ask:

  • "What's in my inbox?"
  • "Download the attachments from inbox item XYZ"
  • "Mark inbox item ABC as processed"

With Custom Scripts:

# Get unprocessed items curl https://your-val.web.val.run/api/inbox \ -H "Authorization: Bearer YOUR_TOKEN" # Mark item as processed curl -X PATCH https://your-val.web.val.run/api/record/{id}/processed \ -H "Authorization: Bearer YOUR_TOKEN"

API Reference

All endpoints require Authorization: Bearer YOUR_TOKEN header.

Endpoints

MethodPathDescription
GET/Health check (no auth required)
GET/api/inboxRetrieve all unprocessed records
POST/api/recordCreate new inbox record
PATCH/api/record/:id/processedMark record as processed
GET/api/record/:recordId/attachmentsGet attachment metadata
GET/api/record/:recordId/attachments/:blobIdDownload attachment file

License

Go nuts, but know this was largely vibe-coded

FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.