A temporary inbox for raw information before it's processed into an Obsidian vault.
This service acts as a buffer between various information sources (email, HTTP) and your Obsidian vault. Instead of directly processing incoming content into notes, this inbox:
- Receives raw information from multiple sources (email, API calls)
- Stores unprocessed items temporarily with their attachments
- Provides a simple API for clients to retrieve and process items
- Manages the lifecycle: unprocessed → processed → auto-deleted after 30 days
This separation allows for flexible processing workflows using different clients (Claude Desktop via MCP, custom scripts, etc.).
- A Val.town account (free tier works)
- Deno runtime installed locally (for MCP server)
-
Remix on Val.town
-
Run one-time setup
Run the one-time setup script to create the database table in your val.town sqlite.
-
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 (for Claude Desktop integration)
Create
~/Library/Application Support/Claude/claude_desktop_config.json:{ "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:
"command": "deno", "args": [ "run", "--allow-net", "--allow-env", "https://esm.town/v/your-username/inbox/localMcpServer.ts" ]
This project runs on Val.town, a Deno-based serverless platform that provides:
- SQLite database via standard library
- Blob storage for binary attachments
- Email sending/receiving built-in
- HTTP endpoints for API access
- Cron scheduling for cleanup tasks
┌─────────────────┐
│ Email Sources │
└────────┬────────┘
│
v
┌────────────────┐
│ emailHandler │──┐
└────────────────┘ │
│
┌─────────────────┐ │ ┌──────────────────┐
│ HTTP/API │────>├───>│ InboxService │
│ Sources │ │ │ (SQLite) │
└─────────────────┘ │ └──────────────────┘
│
┌────────────────┐ │ ┌──────────────────┐
│ httpHandler │──┘ │ AttachmentService│
│ (Hono API) │<──────│ (Blob Storage) │
└────────┬───────┘ └──────────────────┘
│
v
┌────────────────┐
│ MCP Server │───> Claude Desktop
│ (Local) │
└────────────────┘
┌────────────────┐
│ Custom Clients │───> Your Scripts
└────────────────┘
obsidian-inbox/
├── server/ # Core server logic
│ ├── app.ts # Hono HTTP app with routes
│ ├── InboxService.ts # SQLite persistence layer
│ ├── AttachmentService.ts # Blob storage for files
│ ├── auth.ts # Bearer token & email allowlist
│ └── db.ts # Drizzle ORM schema
│
├── mcp/ # Model Context Protocol server
│ ├── server.ts # MCP server setup
│ └── tools/ # MCP tool implementations
│ ├── readInbox.ts
│ ├── addRecord.ts
│ ├── markRecordProcessed.ts
│ ├── getRecordAttachments.ts
│ └── downloadRecordAttachment.ts
│
├── 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
└── 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": [] }'
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 (except GET /).
| 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 |
InboxRecord:
{
raw: string, // Full text content
summary?: string, // Optional summary
from?: string, // Source (email address, etc.)
subject?: string, // Subject/title
date?: Date, // When received
attachmentBlobIds: string[] // Blob storage keys
}
Stored with metadata:
id- UUID primary keycreated_at- Timestamp when savedprocessed_at- Null until marked processed
The MCP server can run locally and connect to your deployed Val.town service:
# Set environment variables export INBOX_API_URL="https://your-val.web.val.run" export INBOX_API_TOKEN="your-token" # Run MCP server deno run --allow-net --allow-env localMcpServer.ts
Edit vals directly in the Val.town web interface. Key points:
- No test suite - test manually via web interface
- No migrations - change table names instead (
inbox_records→inbox_records_2) - Deno imports - use
https://esm.sh/packagefor npm packages - Platform stdlib - use
https://esm.town/v/std/for Val.town features
See CLAUDE.md for AI assistant guidelines and PROJECT.md for detailed architecture.
Schema Changes:
- Update
inboxRecordSchemainserver/InboxService.ts - Increment table name in
server/db.ts(e.g.,inbox_records_2) - Redeploy vals to Val.town
Adding Endpoints:
- Add route in
server/app.ts - Add auth middleware if needed
- Update this README
Adding MCP Tools:
- Create tool file in
mcp/tools/ - Register in
mcp/tools/index.ts - Test with Claude Desktop
- Simple buffer - Not a full app, just a temporary holding area
- Client-side processing - Complex logic lives in clients, not here
- Stateless - Each record is independent
- Val.town native - Leverage platform features over external services
- Fail-fast - Let errors bubble with context
- Send email with notes/attachments to Val.town address
- Email handler saves to inbox
- Ask Claude Desktop: "What's in my inbox?"
- Claude reads items, processes into Obsidian vault
- Claude marks items as processed
- Auto-deleted after 30 days
- POST content via HTTP API
- Poll
/api/inboxendpoint - Process items with your script
- PATCH to mark processed
- Auto-deleted after 30 days
Emails not appearing:
- Check sender is in
INCOMING_EMAIL_ALLOWLIST - Verify email was sent to correct Val.town address
- Check Val.town logs for errors
MCP connection fails:
- Verify
INBOX_API_URLandINBOX_API_TOKENin config - Check Deno is installed and in PATH
- Restart Claude Desktop after config changes
401 Unauthorized:
- Check
Authorization: Bearer TOKENheader is set - Verify token matches
INBOX_API_TOKENin Val.town secrets
Go nuts