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.
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!
The inbox follows a simple three-stage lifecycle:
- Ingest - Content arrives via email or HTTP API and is stored as an unprocessed record
- Process - You review and integrate the content into your knowledge system (e.g., via Claude or your custom scripts)
- Archive - Once integrated, the record is marked as processed to keep your inbox clean
A cleanup cron job periodically removes old processed records.
See CLAUDE.md for AI assistant guidelines and PROJECT.md for detailed architecture.
-
Remix on Val.town
-
Run one-time database setup
Run the one-time setup script to create the database table in your val.town sqlite database.
-
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)
-
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
-rtodeno runto capture frequent updates. -
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!
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)
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,
});
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"
All endpoints require Authorization: Bearer YOUR_TOKEN header.
| Method | Path | Description |
|---|---|---|
GET | / | Health check (no auth required) |
GET | /api/inbox | Retrieve all unprocessed records |
POST | /api/record | Create new inbox record |
PATCH | /api/record/:id/processed | Mark record as processed |
GET | /api/record/:recordId/attachments | Get attachment metadata |
GET | /api/record/:recordId/attachments/:blobId | Download attachment file |
Go nuts, but know this was largely vibe-coded