[{"data":1,"prerenderedAt":1077},["ShallowReactive",2],{"search":3,"content-developer\u002Fsealed-mail-at-rest":478,"surround-\u002Fdeveloper\u002Fsealed-mail-at-rest":1072},[4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,160,164,168,172,176,180,184,188,192,196,200,204,208,212,216,220,224,228,232,236,240,244,248,252,256,260,264,268,272,276,280,284,288,292,296,300,304,308,312,316,320,324,328,332,336,340,344,348,352,356,359,363,367,371,375,379,383,387,391,395,399,403,407,411,415,419,423,427,431,435,439,443,447,451,455,459,462,466,470,474],{"path":5,"title":6,"description":7},"\u002Fguide","Guide","Product guides for Owlat — a modular, self-hosted email platform. Learn how to send campaigns, run a personal mailbox, manage a team inbox, and more.",{"path":9,"title":10,"description":11},"\u002Fguide\u002Fgetting-started","Welcome to Owlat","Set up your Owlat workspace and send your first email — from deploying the stack to verifying a domain, building your audience, and launching a campaign.",{"path":13,"title":14,"description":15},"\u002Fguide\u002Fcontact-properties","Contact Properties","Custom fields that extend built-in contact data with your own values for segmentation.",{"path":17,"title":18,"description":19},"\u002Fguide\u002Ftopics","Topics","Topics are explicit audience groups you manage by hand — ideal for opt-in subscribers, imported cohorts, and organized contact buckets you target with campaigns.",{"path":21,"title":22,"description":23},"\u002Fguide\u002Fsegments","Segments","Build dynamic, rule-based contact groups from properties, email activity, and topic membership, re-evaluated from current data each time they're used.",{"path":25,"title":26,"description":27},"\u002Fguide\u002Fforms","Forms","Form Endpoints collect new contacts from your website or landing pages by exposing a public endpoint that accepts submissions and feeds them into a topic.",{"path":29,"title":30,"description":31},"\u002Fguide\u002Fcampaigns","Campaigns & Reporting","Build and send marketing campaigns to a topic or segment with the three-step wizard, optional A\u002FB testing, and full delivery reporting.",{"path":33,"title":34,"description":35},"\u002Fguide\u002Fab-testing","A\u002FB Testing","Compare two variants of a campaign on a test group, then automatically or manually send the winning version to the rest of your audience.",{"path":37,"title":38,"description":39},"\u002Fguide\u002Fautomations","Automations","Send emails automatically based on triggers, delays, and conditions — build welcome series, trial flows, and follow-ups once and let Owlat run them.",{"path":41,"title":42,"description":43},"\u002Fguide\u002Ftransactional","Transactional Emails","One-to-one emails your application triggers in response to a user action — password resets, order confirmations, welcome emails, and similar notifications.",{"path":45,"title":46,"description":47},"\u002Fguide\u002Fcreate-campaign","Create a Campaign","Walk through Owlat's three-step campaign wizard: Setup, Content, and Review & Send.",{"path":49,"title":50,"description":51},"\u002Fguide\u002Fsend-campaign","Send & Monitor a Campaign","How to send your campaign and track its performance with real-time metrics.",{"path":53,"title":54,"description":55},"\u002Fguide\u002Fquick-start","Quick Start","The fastest path from a blank Owlat workspace to a live email campaign, from your first template through sending and reviewing results.",{"path":57,"title":58,"description":59},"\u002Fguide\u002Ftransactional-setup","Transactional Email Setup","Set up and send transactional emails like password resets and order confirmations via the Owlat API and SDKs.",{"path":61,"title":62,"description":63},"\u002Fguide\u002Fdeliverability","Deliverability","Verify sending domains, manage your blocklist, monitor sending reputation, and stay compliant so your emails reach the inbox.",{"path":65,"title":66,"description":67},"\u002Fguide\u002Fapi-keys-webhooks","API Keys & Webhooks","Create API keys for programmatic access and set up outbound webhooks to receive real-time notifications for email and contact events.",{"path":69,"title":70,"description":71},"\u002Fguide\u002Ffeature-flags","Feature flags","Owlat is modular — every feature listed in this guide can be turned on or off. This page is the user-facing overview of how to do it.",{"path":73,"title":74,"description":75},"\u002Fguide\u002Fteam-permissions","Team & Permissions","Use role-based access to control what each member of your organization can do, with Owner, Admin, and Editor roles.",{"path":77,"title":78,"description":79},"\u002Fguide\u002Faudit-logs","Audit Logs","A chronological record of significant actions in your Owlat organization, so you can see who did what and when.",{"path":81,"title":82,"description":83},"\u002Fguide\u002Fshare-links","Share Links","Create temporary preview links to share email designs with stakeholders who don't have dashboard access.",{"path":85,"title":86,"description":87},"\u002Fguide\u002Fpostbox","Postbox — Personal Email","Per-user mailboxes with a webmail interface and native IMAP\u002FSMTP support. Run your own Gmail-equivalent personal mailbox on your Owlat instance.",{"path":89,"title":90,"description":91},"\u002Fguide\u002Fmigrate-from-google","Migrate from Google","Import your full Gmail history into Owlat over IMAP, and let your AI assistant learn from every imported conversation.",{"path":93,"title":94,"description":95},"\u002Fguide\u002Fteam-inbox","Team Inbox","Triage inbound email as a team: read AI-classified threads, approve, edit or reject agent drafts, work the review queue, and manage quarantine.",{"path":97,"title":98,"description":99},"\u002Fguide\u002Femail-editor","Email Editor","A block-based visual editor for building responsive emails that render consistently across desktop, mobile, Outlook, Gmail, and Apple Mail.",{"path":101,"title":102,"description":103},"\u002Fguide\u002Fai-agent","AI Agent & Autonomy","Configure the AI agent that classifies and drafts replies to inbound mail: auto-reply settings, the health dashboard, circuit breakers, autonomy rules, and the knowledge backfill.",{"path":105,"title":106,"description":107},"\u002Fguide\u002Fknowledge-graph","Knowledge Graph","Browse, search, and manage Owlat's typed organizational knowledge — the 7 entry types, source attribution, confidence decay, relations, and how entries are extracted from mail.",{"path":109,"title":110,"description":111},"\u002Fguide\u002Ffiles","Files","Upload, browse, search, tag, and version documents in the file library.",{"path":113,"title":114,"description":115},"\u002Fguide\u002Fchat","Team Chat","Use Owlat's built-in team chat: public and private channels, direct messages, mentions, attachments, and channels linked to an inbox conversation.",{"path":117,"title":118,"description":119},"\u002Fguide\u002Fcode-tasks","Code Tasks","Queue coding-agent tasks, watch them move from queued through review, and run the code-worker sidecar that opens the pull requests.",{"path":121,"title":122,"description":123},"\u002Fguide\u002Faudience-data","Audience Data: Identities, Relationships & Timeline","Unify a contact across email, phone, and messaging channels, merge duplicates, map relationships, and read the cross-channel interaction timeline.",{"path":125,"title":126,"description":127},"\u002Fguide\u002Fimporting-contacts","Importing & Exporting Contacts","Bring contacts into Owlat from a CSV or from Mailchimp and Stripe, export them back out, and run bulk operations on your audience.",{"path":129,"title":130,"description":131},"\u002Fguide\u002Faccount","Your Account & Data","Export your data as JSON or CSV, request account deletion with a 30-day grace period, and use the onboarding checklist and the public preference center.",{"path":133,"title":134,"description":135},"\u002Fguide\u002Fchannels","Communication Channels","Configure SMS, WhatsApp, and generic-webhook channels, monitor channel health, and understand which channels are fully live today.",{"path":137,"title":138,"description":139},"\u002Fguide\u002Fdesktop-app","Desktop App","Install the Owlat desktop app, connect one or more workspaces, switch between them, and use native notifications, the dock\u002Ftaskbar unread badge, shortcuts, and deep links.",{"path":141,"title":142,"description":143},"\u002Fguide\u002Femail-templates","Email Templates","Reusable email designs that define the structure, content, and personalization of every campaign and transactional message you send in Owlat.",{"path":145,"title":146,"description":147},"\u002Fguide\u002Fai-assistant","AI Assistant","Owlat's multi-turn, streaming, tool-calling AI assistant — a private chat surface that can search your workspace and draft copy, plus @assistant replies inside team chat.",{"path":149,"title":150,"description":151},"\u002Fguide\u002Fsecurity-scanning","Sending Security & Scanning","Owlat's security scanning: a content check for spam and phishing, an attachment scan for malware, and a Google Safe Browsing URL check. Suspicious content goes to a review queue.",{"path":153,"title":154,"description":155},"\u002Fguide\u002Fsystem-updates","System & Updates","The owner-only System & Updates screen: your current Owlat version, container health, LLM spend, and the in-app one-click updater with history.",{"path":157,"title":158,"description":159},"\u002Fguide\u002Foperating-modes","Operating Modes","The different ways to run Owlat at a company — read external mailboxes over IMAP, send transactional or marketing email through a delivery provider, host your own mail server, or run a team inbox with AI — and the rules that keep each combination coherent.",{"path":161,"title":162,"description":163},"\u002Fguide\u002Freply-queue","Reply Queue","A task list of emails waiting on your reply — Postbox detects unanswered asks, ranks them by urgency and age, and clears them the moment you respond.",{"path":165,"title":166,"description":167},"\u002Fguide\u002Fsmart-inbox","Smart Inbox","Split your Postbox inbox into People, Newsletters, Notifications, and Receipts — a deterministic classifier first, AI refinement for the ambiguous middle, off by default, and a per-sender override that's remembered.",{"path":169,"title":170,"description":171},"\u002Fguide\u002Fpostbox-settings","Postbox settings reference","Every Postbox behavior toggle in one place — auto-advance, reply defaults, density, writing suggestions, auto-summaries, notifications, the on-device cache, and the send sound — with what each does and its default.",{"path":173,"title":174,"description":175},"\u002Fguide\u002Fcalendar-availability","Calendar Availability for Scheduling Replies","Point Owlat at a read-only calendar feed so AI scheduling replies propose your real open times instead of only echoing the sender's.",{"path":177,"title":178,"description":179},"\u002Fguide\u002Fconnect-your-ai","Connect your AI","Pick the AI backend every Owlat AI feature uses — a hosted provider via an API key (OpenAI, Anthropic, Google, Azure OpenAI, OpenRouter) or a model you host yourself (Ollama, vLLM, llama.cpp) — plus the local-by-default embeddings that make retrieval work under any choice.",{"path":181,"title":182,"description":183},"\u002Fguide\u002Fsealed-mail","Sealed Mail — End-to-End Encryption","How Owlat encrypts personal mail end-to-end between Owlat workspaces, when a message auto-seals versus sends in the clear, and how to read the Sealed and sender-verified badges.",{"path":185,"title":186,"description":187},"\u002Fguide\u002Fsaved-blocks","Saved Blocks","Create reusable, linked content blocks you can drop into any email — edit one and every email that uses it updates automatically.",{"path":189,"title":190,"description":191},"\u002Fguide\u002Fsealed-mail-recovery-kit","Sealed Mail Recovery Kit","What a Sealed Mail recovery kit is, when it's offered, how to download and store it, how to restore access after a rebuild, and the blunt warning about losing it.",{"path":193,"title":194,"description":195},"\u002Fguide\u002Fmedia-library","Media Library","Manage, organize, search, and reuse images and files across your emails from one centralized hub.",{"path":197,"title":198,"description":199},"\u002Fguide\u002Femail-theme","Email Theme","Set your organization's default colors, font, and email width so every new template starts from a consistent baseline.",{"path":201,"title":202,"description":203},"\u002Fguide\u002Ftranslations","Translations","Send one email in multiple languages: add per-language translations to a single template and Owlat picks the right version for each recipient.",{"path":205,"title":206,"description":207},"\u002Fguide\u002Fcontacts","Contacts","How to add, view, organize, and manage contacts in Owlat, including sources, the contact detail tabs, and subscription compliance.",{"path":209,"title":210,"description":211},"\u002Fapi","API Overview","Owlat exposes authenticated API endpoints under your Convex site URL.",{"path":213,"title":214,"description":215},"\u002Fapi\u002Fwebhooks","Webhooks","Owlat supports both outbound customer webhooks and inbound provider webhooks.",{"path":217,"title":218,"description":219},"\u002Fapi\u002Fpublic-endpoints","Public Endpoints","These routes are public-facing and usually accessed from email links or embedded forms.",{"path":221,"title":222,"description":223},"\u002Fapi\u002Fwebhook-payloads","Webhook Payloads","The authoritative wire contract for outbound webhooks: envelope, signature headers, per-event data shapes, and payload versioning.",{"path":225,"title":226,"description":227},"\u002Fapi\u002Finbound-channels","Inbound Channel Webhooks","Provider webhook reference for inbound SMS, WhatsApp, and generic-channel messages, plus the MTA mailbox and credential callbacks.",{"path":229,"title":230,"description":231},"\u002Fapi\u002Fauthentication","Authentication","Secure API access with organization-scoped API keys.",{"path":233,"title":234,"description":235},"\u002Fapi\u002Fsdk","TypeScript SDK","Typed client for the Owlat API, usable from Node.js, Bun, Deno, or any server-side JavaScript runtime.",{"path":237,"title":238,"description":239},"\u002Fapi\u002Fsdk-java","Java SDK","The official `owlat-sdk` package provides a typed client for interacting with the Owlat API from any JVM application. Requires Java 11+.",{"path":241,"title":242,"description":243},"\u002Fapi\u002Fcontacts","Contacts API","Manage contacts for your organization.",{"path":245,"title":246,"description":247},"\u002Fapi\u002Ftopics","Topics API","Manage topic membership through authenticated endpoints.",{"path":249,"title":250,"description":251},"\u002Fapi\u002Fevents","Events API","Send contact events to drive segmentation and automation triggers.",{"path":253,"title":254,"description":255},"\u002Fapi\u002Ftransactional","Transactional API","Send published transactional templates to a recipient.",{"path":257,"title":258,"description":259},"\u002Fapi\u002Fforms","Forms API","Capture subscribers through public form endpoints.",{"path":261,"title":262,"description":263},"\u002Fdeveloper","Developer Guide","Technical architecture, feature-flag model, and provider abstractions used by Owlat.",{"path":265,"title":266,"description":267},"\u002Fdeveloper\u002Fmta-system","MTA System","Owlat's custom Mail Transfer Agent for direct SMTP delivery with intelligent rate limiting, bounce processing, and IP warming.",{"path":269,"title":270,"description":271},"\u002Fdeveloper\u002Ffeature-flags","Feature flags — developer reference","How the Owlat feature flag system works: single source of truth, dependency resolution, docker profile mapping, and how to add a new flag.",{"path":273,"title":274,"description":275},"\u002Fdeveloper\u002Fhow-email-works","How Email Works","A technical deep-dive into how email actually works — from SMTP and DNS to authentication, deliverability, and the differences between marketing and private email.",{"path":277,"title":278,"description":279},"\u002Fdeveloper\u002Femail-security","Email Security","Content scanning, attachment validation, URL reputation checking, and malware detection for outbound emails.",{"path":281,"title":282,"description":283},"\u002Fdeveloper\u002Fpostbox-architecture","Postbox Architecture","How the Postbox personal-mail feature is wired — schema, IMAP server, app-password auth, outbound relay, inbound delivery, and external mailboxes.",{"path":285,"title":286,"description":287},"\u002Fdeveloper\u002Fproviders","Providers","Pluggable provider abstractions for LLM, email sending, notifications, vector stores, and analytics, selected per-deployment so self-hosters can swap implementations without code changes.",{"path":289,"title":290,"description":291},"\u002Fdeveloper\u002Fcampaign-internals","Campaign Internals","How the campaign backend works: two status machines, send pre-flight, the send orchestrator, emailSends records, and the priority workpools.",{"path":293,"title":294,"description":295},"\u002Fdeveloper\u002Faudience-internals","Audience Internals","Backend reference for contact resolution, the double opt-in lifecycle, topic subscription, the conditions registry, and segment evaluation.",{"path":297,"title":298,"description":299},"\u002Fdeveloper\u002Fautomation-internals","Automation Internals","How the automation run engine works: the step walker, the lifecycle state machine, trigger fanout, the three step types, and the resilience cron.",{"path":301,"title":302,"description":303},"\u002Fdeveloper\u002Fdeliverability-infrastructure","Deliverability Infrastructure","The Convex-side deliverability backend: provider routing, health-aware failover, sending reputation with auto-enforcement, IP warming cache, the blocklist, and the content-scan gate.",{"path":305,"title":306,"description":307},"\u002Fdeveloper\u002Farchitecture","Architecture Overview","Owlat follows a modern serverless architecture with real-time capabilities.",{"path":309,"title":310,"description":311},"\u002Fdeveloper\u002Fplatform-operations","Platform Operations","Operator reference for abuse status and the sending gate, the platform-admin roster, content review, org deletion, in-app self-update, dev endpoints, crons, and migrations.",{"path":313,"title":314,"description":315},"\u002Fdeveloper\u002Fsealed-mail-at-rest","Sealed Mail: Bodies at Rest","How Owlat seals every stored message body with an instance data key, and the deliberate search-index exceptions that stay plaintext.",{"path":317,"title":318,"description":319},"\u002Fdeveloper\u002Ftransport-security","Transport Security","Operator guide to Sealed Mail's transport hardening: requiring TLS for inbound delivery, publishing MTA-STS, outbound TLS and DANE posture, TLS-RPT, and secrets at rest.",{"path":321,"title":322,"description":323},"\u002Fdeveloper\u002Fscopes","Scopes","What each app and package in the Owlat monorepo is responsible for.",{"path":325,"title":326,"description":327},"\u002Fdeveloper\u002Fself-hosting","Self-Hosting","Deploy Owlat on your own infrastructure with Docker Compose. Complete guide from first boot to production.",{"path":329,"title":330,"description":331},"\u002Fdeveloper\u002Fself-hosting-config","Self-Hosting Configuration","Complete reference for Docker environment variables, Convex backend variables, service topology, and volume persistence.",{"path":333,"title":334,"description":335},"\u002Fdeveloper\u002Fself-hosting-dns-email","DNS & Email Setup","Configure DNS records, DKIM signing, SPF, DMARC, and bounce handling for reliable email delivery.",{"path":337,"title":338,"description":339},"\u002Fdeveloper\u002Fself-hosting-production","Production Deployment","Secure your self-hosted Owlat instance with TLS, firewall rules, backups, and monitoring.",{"path":341,"title":342,"description":343},"\u002Fdeveloper\u002Fself-hosting-maintenance","Maintenance & Updates","Keep your self-hosted Owlat instance up to date, manage backups, scale performance, and troubleshoot common issues.",{"path":345,"title":346,"description":347},"\u002Fdeveloper\u002Fself-hosting-desktop","Desktop Installer","Install Owlat on a bare Linux VPS straight from the desktop app over SSH — no terminal — with a live, animated provisioning timeline.",{"path":349,"title":350,"description":351},"\u002Fdeveloper\u002Fsetup-cli","Setup CLI & Installer","Operator reference for the Owlat self-host tooling: the install.sh one-liner, the owlat-setup CLI, the convex-deploy flow, and admin bootstrap.",{"path":353,"title":354,"description":355},"\u002Fdeveloper\u002Fconvex","Convex Backend","Owlat uses Convex as its serverless backend, providing real-time subscriptions, ACID transactions, and TypeScript-first development.",{"path":357,"title":230,"description":358},"\u002Fdeveloper\u002Fauthentication","Owlat uses BetterAuth with the Convex adapter for authentication and organization (team) management.",{"path":360,"title":361,"description":362},"\u002Fdeveloper\u002Femail-system","Email System","Owlat's email system consists of a visual editor, template management, and multi-provider sending infrastructure.",{"path":364,"title":365,"description":366},"\u002Fdeveloper\u002Femail-renderer","Email Renderer","The @owlat\u002Femail-renderer package converts editor JSON blocks into production-ready HTML emails with cross-client compatibility, CSS inlining, dark mode, and Outlook VML fallbacks.",{"path":368,"title":369,"description":370},"\u002Fdeveloper\u002Fenvironment-variables","Environment Variables","Reference for every environment variable Owlat reads across the Convex backend, web app, MTA, IMAP server, and mail-sync worker.",{"path":372,"title":373,"description":374},"\u002Fdeveloper\u002Fcomponents","Component Library","Reference for the reusable, auto-imported Vue UI components shipped in the packages\u002Fui layer.",{"path":376,"title":377,"description":378},"\u002Fdeveloper\u002Fdecisions","Architectural Decision Records","The architectural decision records for the Owlat project, each capturing the context, the decision, and the trade-offs involved.",{"path":380,"title":381,"description":382},"\u002Fdeveloper\u002Fdecisions\u002F009-model-routing","ADR-009: Task-Based Model Routing","Why Owlat supports per-task LLM model selection instead of using a single model for all pipeline steps.",{"path":384,"title":385,"description":386},"\u002Fdeveloper\u002Fdecisions\u002F010-listing-engine","ADR-010: Listing Engine","Why Owlat replaced four incompatible list-query contracts with one generic listing engine driven by per-entity descriptors.",{"path":388,"title":389,"description":390},"\u002Fdeveloper\u002Fdecisions\u002F001-custom-email-renderer","ADR-001: Custom Email Renderer Over MJML","Why Owlat built a custom table-based HTML email renderer instead of using MJML, gaining full control over VML, dark mode, and per-client rendering.",{"path":392,"title":393,"description":394},"\u002Fdeveloper\u002Fdecisions\u002F002-convex-backend","ADR-002: Convex as Backend","Why Owlat chose Convex over PostgreSQL and Firebase for real-time reactivity, co-located TypeScript logic, and zero-config scaling.",{"path":396,"title":397,"description":398},"\u002Fdeveloper\u002Fdecisions\u002F003-notion-like-builder","ADR-003: Notion-like Email Builder","Why Owlat replaced the traditional 3-panel email editor with a Notion-like single-column canvas for inline WYSIWYG editing.",{"path":400,"title":401,"description":402},"\u002Fdeveloper\u002Fdecisions\u002F004-monorepo-bun-workspaces","ADR-004: Monorepo with Bun Workspaces","Why Owlat uses a monorepo with Bun workspaces and Turborepo for fast installs, atomic cross-package changes, and cached CI.",{"path":404,"title":405,"description":406},"\u002Fdeveloper\u002Fdecisions\u002F005-custom-mta","ADR-005: Custom MTA","Why Owlat built a custom Mail Transfer Agent instead of relying solely on third-party email providers.",{"path":408,"title":409,"description":410},"\u002Fdeveloper\u002Fdecisions\u002F006-self-hosted-convex","ADR-006: Self-Hosted Convex","Why Owlat uses the open-source Convex backend for self-hosting instead of migrating to a different database.",{"path":412,"title":413,"description":414},"\u002Fdeveloper\u002Fdecisions\u002F007-pluggable-llm","ADR-007: Pluggable LLM Provider","Why Owlat uses the Vercel AI SDK with a provider abstraction layer instead of hardcoding a single LLM vendor.",{"path":416,"title":417,"description":418},"\u002Fdeveloper\u002Fdecisions\u002F008-process-architecture","ADR-008: Agent Process Architecture","Why Owlat processes inbound messages with a self-scheduling step walker plus a lifecycle coordinator instead of one sequential function.",{"path":420,"title":421,"description":422},"\u002Fexamples","Examples","Copy-pasteable integration patterns for common Owlat use cases.",{"path":424,"title":425,"description":426},"\u002Fexamples\u002Fwelcome-email","Welcome Email","Send a personalized welcome email when a new user signs up.",{"path":428,"title":429,"description":430},"\u002Fexamples\u002Fbilling-email","Billing Email","Send a billing receipt with an invoice PDF attached after a successful payment.",{"path":432,"title":433,"description":434},"\u002Fexamples\u002Fevent-automation","Event Automation","Trigger automations with custom events for trial lifecycle, feature adoption, and more.",{"path":436,"title":437,"description":438},"\u002Fexamples\u002Fcontact-sync","Contact Sync","Sync contacts from your database to Owlat using upsert patterns and bulk operations.",{"path":440,"title":441,"description":442},"\u002Fexamples\u002Fwebhook-handler","Webhook Handler","Handle Owlat delivery webhooks with signature verification and event routing.",{"path":444,"title":445,"description":446},"\u002Fexamples\u002Fmultilingual-email","Multilingual Email","Send emails in the recipient's preferred language using template translations.",{"path":448,"title":449,"description":450},"\u002Fvision","Vision","Where Owlat is heading — from email platform to unified communication intelligence powered by AI agents.",{"path":452,"title":453,"description":454},"\u002Fvision\u002Fself-hosting","Self-Hosting Architecture","How Owlat runs as a fully self-hosted stack using Docker Compose — open-source Convex backend, custom MTA, and a pluggable LLM provider.",{"path":456,"title":457,"description":458},"\u002Fvision\u002Fagent-pipeline","Agent Pipeline","Technical architecture for the inbound email agent pipeline — step modules, the walker, security scanning, threading, and human review.",{"path":460,"title":106,"description":461},"\u002Fvision\u002Fknowledge-graph","Technical architecture for Owlat's typed knowledge storage — how organizational knowledge is stored, searched, decayed, and maintained.",{"path":463,"title":464,"description":465},"\u002Fvision\u002Fmulti-channel","Multi-Channel & CRM","Technical architecture for channel adapters, unified messaging, contact identity unification, and the CRM hub.",{"path":467,"title":468,"description":469},"\u002Fvision\u002Ffile-system","Semantic File System","Technical architecture for Owlat's semantic file storage — version tracking with provenance today, plus the planned embedding-based retrieval and auto-tagging layer.",{"path":471,"title":472,"description":473},"\u002Fvision\u002Fdesktop-app","Desktop App & Advanced Agents","Architecture of the Owlat desktop shell, visualization agent, adaptive dashboard, agent health, graduated autonomy, and coding agents.",{"path":475,"title":476,"description":477},"\u002Fvision\u002Froadmap","Roadmap","What's planned next for Owlat — the documented-but-unbuilt pieces still being wired, and the enhancements on our radar.",{"id":479,"title":314,"body":480,"description":315,"extension":1066,"meta":1067,"navigation":1068,"path":313,"seo":1069,"stem":1070,"__hash__":1071},"content\u002F3.developer\u002F21.sealed-mail-at-rest.md",{"type":481,"value":482,"toc":1055},"minimark",[483,505,536,541,544,665,670,680,690,701,718,729,758,762,769,848,852,896,900,926,954,965,972,1020,1029,1033,1046],[484,485,486,487,491,492,496,497,500,501,504],"p",{},"Owlat seals every ",[488,489,490],"strong",{},"message body it stores"," — both the inline body columns and\nthe storage blobs (the raw ",[493,494,495],"code",{},".eml"," and large-body blobs) — with a single\ninstance-held data key, so a database ",[488,498,499],{},"or storage"," dump contains ciphertext\nrather than readable mail. This is the \"at rest\" layer of Sealed Mail (E8b) —\ndistinct from the instance-to-instance PGP\u002FMIME encryption that protects mail\n",[488,502,503],{},"on the wire",".",[506,507,509],"callout",{"type":508},"info",[484,510,511,514,515,518,519,522,523,526,531,532,535],{},[488,512,513],{},"Coverage."," Every body surface is sealed both ",[488,516,517],{},"going forward"," (every\nproduction write path seals as it stores) and ",[488,520,521],{},"retroactively"," (the resumable\nback-fill migration seals existing rows and blobs). In-process readers decrypt\nthrough the accessor plane; the naked signed-URL blob consumers (web reader,\nIMAP bridge, outbound MTA, raw download) fetch through the ",[493,524,525],{},"\u002Fsealed-blob",[527,528,530],"a",{"href":529},"#storage-blobs","decrypt-serving proxy",". Sealing requires ",[493,533,534],{},"INSTANCE_SECRET"," to be\nconfigured; it always is on a real deployment, so a database or storage dump\nholds ciphertext, not readable mail.",[537,538,540],"h2",{"id":539},"what-is-sealed","What is sealed",[484,542,543],{},"Every body-bearing surface across the message shapes is sealed:",[545,546,547,560],"table",{},[548,549,550],"thead",{},[551,552,553,557],"tr",{},[554,555,556],"th",{},"Surface",[554,558,559],{},"Sealed",[561,562,563,581,598,611,630,643],"tbody",{},[551,564,565,571],{},[566,567,568],"td",{},[493,569,570],{},"inboundMessages",[566,572,573,576,577,580],{},[493,574,575],{},"textBody",", ",[493,578,579],{},"htmlBody"," (AI-inbox inline bodies)",[551,582,583,589],{},[566,584,585,588],{},[493,586,587],{},"mailMessages"," (inline)",[566,590,591,576,594,597],{},[493,592,593],{},"textBodyInline",[493,595,596],{},"htmlBodyInline"," (personal-mailbox snippet)",[551,599,600,605],{},[566,601,602],{},[493,603,604],{},"unifiedMessages",[566,606,607,610],{},[493,608,609],{},"content"," (the JSON body blob)",[551,612,613,618],{},[566,614,615],{},[493,616,617],{},"mailDrafts",[566,619,620,576,623,576,626,629],{},[493,621,622],{},"bodyHtml",[493,624,625],{},"bodyText",[493,627,628],{},"bodyBlocks"," (compose drafts)",[551,631,632,637],{},[566,633,634],{},[493,635,636],{},"conversationThreads",[566,638,639,642],{},[493,640,641],{},"lastPreview"," (team-inbox row preview)",[551,644,645,650],{},[566,646,647,649],{},[493,648,587],{}," (blobs)",[566,651,652,655,656,658,659,576,662],{},[493,653,654],{},"rawStorageId"," (raw ",[493,657,495],{},"), ",[493,660,661],{},"textBodyStorageId",[493,663,664],{},"htmlBodyStorageId",[666,667,669],"h3",{"id":668},"the-cipher","The cipher",[484,671,672,673,675,676,679],{},"The sealing key is derived from ",[493,674,534],{}," via HKDF-SHA256 with a\nversion-pinned salt and info label (",[493,677,678],{},"owlat:at-rest:bodies:v1","), which\ndomain-separates it from every other use of the instance secret (external-mail\ncredentials, MTA transport secrets, the E2EE key vault). Bodies are encrypted\nwith AES-256-GCM and stored as a self-describing envelope string:",[681,682,687],"pre",{"className":683,"code":685,"language":686},[684],"language-text","atrest:1:\u003Cbase64(iv)>:\u003Cbase64(ciphertext‖gcmTag)>\n","text",[493,688,685],{"__ignoreMap":689},"",[484,691,692,693,696,697,700],{},"The ",[493,694,695],{},"atrest:"," prefix and version let a reader tell a sealed value from a\nlegacy-plaintext one ",[488,698,699],{},"without the key",", which is what makes the back-fill\nmigration resumable — a half-migrated table is a mix of sealed and plaintext\nrows and every reader tolerates both.",[484,702,703,704,707,708,710,711,713,714,717],{},"Sealed detection is ",[488,705,706],{},"structurally strict",", not a bare prefix test: message\nbodies are attacker-controlled and can literally start with ",[493,709,695],{},", so a\nvalue only counts as sealed when it is a well-formed envelope (exactly four\ncolon parts, a known numeric version, canonical base64, a 12-byte IV, and a\nciphertext of at least the 16-byte GCM tag). A plaintext body that merely\nstarts with ",[493,712,695],{}," is read verbatim, never decrypted. In the other\ndirection, the seal path's idempotency check is ",[488,715,716],{},"keyed"," — a value is treated\nas already-sealed only when it actually decrypts under this instance's key — so\nan envelope-shaped plaintext is encrypted like any other body rather than\nmistaken for ciphertext.",[484,719,720,721,724,725,728],{},"The whole cipher lives in ",[493,722,723],{},"apps\u002Fapi\u002Fconvex\u002Flib\u002FatRestBodies.ts",", and the single\ndecrypt choke point is ",[493,726,727],{},"apps\u002Fapi\u002Fconvex\u002Flib\u002FmessageBody.ts",": every body reader\nfunnels through those accessors, so \"unseal on read\" has one home instead of\nbeing scattered across the codebase.",[484,730,731,732,735,736,738,739,742,743,746,747,750,751,754,755,504],{},"Storage ",[488,733,734],{},"blobs"," can carry non-UTF-8 bytes (8-bit MIME, binary attachments in\nthe raw ",[493,737,495],{},"), so they use a ",[488,740,741],{},"byte-level"," sibling of the same construction —\nAES-256-GCM under a domain-separated key (",[493,744,745],{},"owlat:at-rest:blobs:v1",") with a\ncompact binary envelope (",[493,748,749],{},"ARBLB1"," magic + version + IV + ciphertext) — in the\nsame ",[493,752,753],{},"lib\u002FatRestBodies.ts",". Blob helpers and the serving path live in\n",[493,756,757],{},"apps\u002Fapi\u002Fconvex\u002Flib\u002FsealedBlob.ts",[537,759,761],{"id":760},"deliberate-exceptions-stay-plaintext","Deliberate exceptions (stay plaintext)",[484,763,764,765,768],{},"Three surfaces are ",[488,766,767],{},"intentionally"," left plaintext-derived. Each is annotated\nat its schema index definition so the exception is discoverable in code:",[770,771,772,822,835],"ul",{},[773,774,775,778,779,782,783],"li",{},[488,776,777],{},"Full-text search fields."," Convex indexes the plaintext of a ",[493,780,781],{},"searchField",",\nso sealing it would break server-side search. These fields hold a short\nsnippet or extracted keywords, never the full body:\n",[770,784,785,795,803,811,817],{},[773,786,787,790,791,794],{},[493,788,789],{},"mailMessages.snippet"," (",[493,792,793],{},"search_messages",")",[773,796,797,790,800,794],{},[493,798,799],{},"knowledgeEntries.searchableText",[493,801,802],{},"search_knowledge",[773,804,805,790,808,794],{},[493,806,807],{},"semanticFiles.searchableText",[493,809,810],{},"search_files",[773,812,813,816],{},[493,814,815],{},"contacts"," search fields",[773,818,819,816],{},[493,820,821],{},"campaigns",[773,823,824,827,828,576,831,834],{},[488,825,826],{},"Vector embeddings."," Derived from plaintext at ingest and stored as floats\n(",[493,829,830],{},"vector_knowledge",[493,832,833],{},"vector_files","). They are not reversible to the source\ntext and are required for semantic retrieval.",[773,836,837,840,841,840,844,847],{},[488,838,839],{},"Per-contact data export."," ",[493,842,843],{},"contacts\u002FdataExport.ts",[488,845,846],{},"decrypts"," bodies\nwhen building a GDPR data-subject access bundle — the owner's own data package\nmust be readable. This is the one documented place plaintext leaves the store.",[537,849,851],{"id":850},"back-fill-migration","Back-fill migration",[484,853,854,857,858,861,862,865,866,869,870,873,874,876,877,880,881,884,885,888,889,888,892,895],{},[493,855,856],{},"apps\u002Fapi\u002Fconvex\u002Fmigrations\u002F0035_seal_bodies_at_rest.ts"," walks every body-bearing\nsurface one page at a time — cursor-carrying internal mutations for the inline\ncolumns and an internal ",[488,859,860],{},"action"," for the storage blobs (blob contents are only\nreadable from an action) — driven to completion by a resumable ",[493,863,864],{},"run","\norchestrator. It is idempotent (re-running skips already-sealed rows and blobs)\nand never leaves a row unreadable mid-run. Because Convex storage is immutable\nper id, sealing a blob reads it, stores the sealed copy under a new id, repoints\nthe row, and deletes the old plaintext blob only after the row points at the\nsealed copy. Sealing is ",[488,867,868],{},"sharing-aware",": IMAP ",[493,871,872],{},"COPY"," shares one storage blob\nacross rows (the copy row reuses the original's ",[493,875,654],{},"\u002F",[493,878,879],{},"*BodyStorageId","),\nso the reseal repoints ",[488,882,883],{},"every"," row referencing an old blob — found via the\n",[493,886,887],{},"by_raw_storage"," \u002F ",[493,890,891],{},"by_text_body_storage",[493,893,894],{},"by_html_body_storage"," indexes —\nbefore deleting it, in a single mutation. A sibling copy is therefore never left\npointing at a deleted blob.",[537,897,899],{"id":898},"storage-blobs","Storage blobs",[484,901,902,903,905,906,908,909,911,912,915,916,919,920,922,923,504],{},"The raw ",[493,904,495],{}," at ",[493,907,654],{}," and the large-body ",[493,910,879],{}," blobs are\nsealed at rest with the byte cipher. They are still served to the naked-URL\nconsumers — the Postbox web reader (",[493,913,914],{},"fetch(url).text()","), the out-of-process IMAP\nbridge (",[493,917,918],{},"FETCH RFC822","), the outbound MTA \u002F external-SMTP worker (which fetches\nthe ",[493,921,495],{}," to transmit), and raw download — but instead of a bare signed storage\nURL those callers now receive a ",[488,924,925],{},"decrypt-serving proxy URL",[484,927,928,790,931,934,935,938,939,942,943,945,946,949,950,953],{},[493,929,930],{},"GET \u002Fsealed-blob",[493,932,933],{},"apps\u002Fapi\u002Fconvex\u002Fmail\u002FsealedBlobHttp.ts",") reads the sealed\nblob named by a ",[488,936,937],{},"capability token",", unseals it, and streams the plaintext\nbytes. The token is an HMAC over ",[493,940,941],{},"storageId . contentType . expiry"," keyed by\n",[493,944,534],{},", minted only after the caller has been authorized at the query\nsite (mailbox ownership is checked before any URL is returned) — matching the\nunguessable, time-limited nature of the Convex signed URL it replaces. A\nbad\u002Fexpired\u002Fforged token is a flat ",[493,947,948],{},"403",". Because every consumer keeps doing a\nplain ",[493,951,952],{},"GET"," that yields the original bytes, the proxy is a transparent drop-in\nand no out-of-process code changes.",[484,955,956,957,960,961,964],{},"The in-process reader path (",[493,958,959],{},"readMailMessageText",") unseals blobs directly through\n",[493,962,963],{},"readSealedBlobText",", so server-side body reads never touch ciphertext.",[484,966,967,968,971],{},"A mutation cannot read or re-store a blob's bytes (blob contents are action-only),\nso the two paths that accept a ",[488,969,970],{},"worker-uploaded plaintext blob"," seal it\nout-of-band rather than leaving a standing plaintext residual:",[770,973,974,994],{},[773,975,976,790,979,982,983,985,986,989,990,993],{},[488,977,978],{},"IMAP APPEND",[493,980,981],{},"mail.imap.appendMessage",") uploads the raw ",[493,984,495],{}," straight to\nstorage, then schedules a per-message reseal action (",[493,987,988],{},"resealMessageBlobs",",\n",[493,991,992],{},"runAfter(0)",") after inserting the row.",[773,995,996,790,999,1002,1003,1006,1007,1009,1010,1012,1013,888,1016,1019],{},[488,997,998],{},"External IMAP sync",[493,1000,1001],{},"mail.externalDelivery.ingestExternalMessage",") already\nseals at write: its sole caller ",[493,1004,1005],{},"ingestExternalRaw"," is an ",[488,1008,860],{}," that seals\nthe raw ",[493,1011,495],{}," and the body blobs (",[493,1014,1015],{},"storeSealedBlob",[493,1017,1018],{},"splitBodyForStorage",")\nbefore the mutation runs.",[484,1021,1022,1024,1025,1028],{},[493,1023,988],{}," is idempotent (an already-sealed blob reseals to a no-op) and\nrepoints + deletes the old plaintext blob in one mutation. In the brief window\nbetween the plaintext write and the scheduled reseal, the blob reads and serves\ncorrectly through the mixed-tolerance accessors and proxy (",[493,1026,1027],{},"openBytesAtRest","\npasses legacy plaintext through). The back-fill remains the catch-up path for\nrows written before E8b shipped.",[537,1030,1032],{"id":1031},"acceptance","Acceptance",[484,1034,1035,1036,1039,1040,1043,1044],{},"A database ",[488,1037,1038],{},"and storage"," dump of a seeded, migrated instance contains ",[488,1041,1042],{},"zero\nmessage-body plaintext"," — across the five inline body shapes and the raw ",[493,1045,495],{},[770,1047,1048],{},[773,1049,1050,1051,1054],{},"body blobs — outside the documented search-index exception above. This is\nasserted by the canary test in\n",[493,1052,1053],{},"apps\u002Fapi\u002Fconvex\u002F__tests__\u002FsealBodiesAtRest.integration.test.ts",", which dumps\nboth the DB columns and the stored blob bytes.",{"title":689,"searchDepth":1056,"depth":1056,"links":1057},2,[1058,1062,1063,1064,1065],{"id":539,"depth":1056,"text":540,"children":1059},[1060],{"id":668,"depth":1061,"text":669},3,{"id":760,"depth":1056,"text":761},{"id":850,"depth":1056,"text":851},{"id":898,"depth":1056,"text":899},{"id":1031,"depth":1056,"text":1032},"md",{},true,{"title":314,"description":315},"3.developer\u002F21.sealed-mail-at-rest","DhQbL7ft4KcHXPfmAX8RqAh_y2LqVfkOCjyJNl6tATs",[1073,1075],{"title":310,"path":309,"stem":1074,"children":-1},"3.developer\u002F20.platform-operations",{"title":318,"path":317,"stem":1076,"children":-1},"3.developer\u002F22.transport-security",1784224019001]