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

drewmcdonald

inbox

Public
Like
inbox
Home
Code
12
mcp
2
server
5
.vtignore
CLAUDE.md
MCP-README.md
PROJECT.md
TODO.md
deno.json
E
emailHandler.ts
H
httpHandler.ts
mcp-config.example.json
serverConfig.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
/
PROJECT.md
Code
/
PROJECT.md
Search
10/21/2025
Viewing readonly version of main branch: v220
View latest version
PROJECT.md

Obsidian Inbox

A Val.town service that acts as a temporary inbox for raw information before it's processed into an Obsidian vault.

Purpose

This service provides a buffer layer between various information sources (email, HTTP POST) and Obsidian vault management systems. It:

  • Receives raw information from multiple sources
  • Stores unprocessed items with attachments
  • Provides a simple API for clients to retrieve and process items
  • Manages the lifecycle of inbox items (unprocessed → processed → deleted)

Architecture

Platform

  • Runtime: Deno on Val.town serverless environment
  • Database: SQLite (Val.town standard library)
  • Storage: Blob storage (Val.town standard library)
  • Language: TypeScript

Core Components

1. Input Handlers

  • emailHandler.ts: Email trigger that processes incoming emails

    • Validates sender against allowlist
    • Extracts email content and metadata
    • Saves attachments to blob storage
    • Creates inbox record
  • httpHandler.ts: HTTP API using Hono framework

    • GET / - Retrieve all unprocessed items
    • POST / - Create new inbox item (for non-email sources)
    • PATCH /:id/processed - Mark item as processed (returns 404 if record not found)
    • GET /:recordId/attachments - Get attachment metadata for a record

2. Services

  • InboxService.ts: Core data persistence for inbox records

    • Stores records in SQLite with UUID keys
    • Tracks processing state (unprocessed/processed timestamp)
    • Provides lifecycle operations (save, markProcessed, retrieveUnprocessed)
    • Auto-deletes processed records after 30 days
  • AttachmentService.ts: Blob storage for email attachments

    • Stores binary data and metadata separately
    • Supports get/save/delete operations
    • Uses UUID keys for blob identification
  • auth.ts: Authentication and authorization

    • Bearer token auth for HTTP endpoints (via API_TOKEN env var)
    • Email sender allowlist (via INCOMING_EMAIL_ALLOWLIST env var)

Data Model

InboxRecord

{ raw: string, // Full text content (email body, etc.) summary?: string, // Optional summary from: string, // Source identifier (email address, etc.) subject: string, // Subject line or title date: Date, // When item was received attachmentBlobIds: string[] // References to blob storage }

Stored in SQLite table inbox_records with:

  • id (UUID primary key)
  • record (JSON-serialized InboxRecord)
  • created_at (timestamp)
  • processed_at (nullable timestamp)

AttachmentMetadata

{ filename: string, contentType: string, size: number, data: ArrayBuffer // The actual binary content }

Stored in blob storage as:

  • {uuid} - binary data
  • {uuid}:meta - JSON metadata

Processing Lifecycle

  1. Ingestion: Item arrives via email or HTTP POST
  2. Storage: Saved to SQLite with processed_at = null
  3. Retrieval: Client calls GET / to fetch unprocessed items
  4. Processing: Client transforms item into Obsidian note(s) (outside this service)
  5. Completion: Client calls PATCH /:id/processed to mark as done
  6. Cleanup: After 30 days, processed records auto-delete (including attachments)

Note: Clients are responsible for idempotency. If processing fails partway through, the client should handle re-processing gracefully.

Environment Variables

Required configuration via Val.town secrets:

  • API_TOKEN - Bearer token for HTTP API authentication
  • INCOMING_EMAIL_ALLOWLIST - Comma-separated email addresses allowed to send

Client Integration

MCP Server (Recommended)

A Model Context Protocol (MCP) server is provided that runs locally and communicates with the remote API. This allows Claude Desktop to interact with your inbox.

Setup: See MCP-README.md for complete setup instructions.

Architecture: The MCP server (mcp.ts) runs on your local machine and uses apiClient.ts to call the remote Val.town HTTP API. This keeps the MCP logic separate from the serverless service.

Custom Clients

Other clients that consume this inbox should:

  1. Poll GET /api/inbox endpoint for unprocessed items
  2. Get attachment metadata via GET /api/record/:recordId/attachments
  3. Download attachments via GET /api/record/:recordId/attachments/:blobId
  4. Process items into desired format (e.g., Obsidian notes)
  5. Mark each item as processed via PATCH /api/record/:id/processed
  6. Handle failures gracefully (service doesn't retry)

All API calls require Bearer token authentication.

Development Guidelines

Val.town Specifics

  • All code must be compatible with Deno runtime
  • Use https://esm.sh for npm imports
  • Storage limited to SQLite + blob storage (no filesystem)
  • See .cursor/rules/townie.mdc for full Val.town development guidelines

Common Modification Patterns

Schema Changes:

  • Modify inboxRecordSchema in InboxService.ts
  • Change table name (e.g., inbox_records → inbox_records_2) to avoid migration issues
  • Update both email and HTTP handlers if needed

Adding Input Sources:

  1. Create new handler file (e.g., webhookHandler.ts)
  2. Map to appropriate trigger type (HTTP, cron, etc.)
  3. Transform input to InboxRecord format
  4. Call InboxService.save()

Processing Logic Changes:

  • Most business logic lives in handlers (emailHandler.ts, httpHandler.ts)
  • Keep services (InboxService, AttachmentService) focused on data operations
  • Prefer small, focused functions over complex class hierarchies

Testing

  • Manual testing via Val.town web interface
  • No formal test suite currently
  • Focus on edge cases: malformed emails, missing attachments, auth failures

Known TODOs

  • Error handling and retry logic for failed processing
  • Consider webhook/push notification when new items arrive (vs polling)
  • Finalize client architecture (MCP vs custom scripts)
  • Evaluate if 30-day retention is appropriate for all use cases

File Reference

.
├── emailHandler.ts       # Email trigger entry point
├── httpHandler.ts        # HTTP API entry point (Hono app)
├── InboxService.ts       # Inbox record persistence
├── AttachmentService.ts  # Blob storage for attachments
├── auth.ts               # Authentication/authorization
├── db.ts                 # Database schema and connection
├── types.ts              # Shared TypeScript types
├── mcp.ts                # MCP server (runs locally)
├── apiClient.ts          # HTTP client for remote API
├── deno.json             # Deno/Val.town configuration
├── MCP-README.md         # MCP server setup guide
├── mcp-config.example.json # Example Claude Desktop config
└── .cursor/rules/
    └── townie.mdc        # Val.town development guidelines

Design Principles

  1. Simple buffer: This is NOT a full email client or note-taking app. It's a temporary holding area.
  2. Client-side processing: Complex transformations happen in clients, not here.
  3. Stateless: Each inbox item is independent. No complex workflows or state machines.
  4. Val.town native: Leverage platform services (SQLite, blob, email) rather than external dependencies.
  5. Fail-fast: Let errors bubble up with context rather than hiding them.
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
© 2026 Val Town, Inc.