[{"data":1,"prerenderedAt":2521},["ShallowReactive",2],{"search":3,"content-developer\u002Fsetup-cli":442,"surround-\u002Fdeveloper\u002Fsetup-cli":2516},[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,323,327,331,335,339,343,347,351,355,359,363,367,371,375,379,383,387,391,395,399,403,407,411,415,419,423,426,430,434,438],{"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 five-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 five-step campaign wizard: Basics, Audience, Content, A\u002FB Test, 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, tray badges, 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\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":165,"title":166,"description":167},"\u002Fguide\u002Fmedia-library","Media Library","Manage, organize, search, and reuse images and files across your emails from one centralized hub.",{"path":169,"title":170,"description":171},"\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":173,"title":174,"description":175},"\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":177,"title":178,"description":179},"\u002Fguide\u002Fcontacts","Contacts","How to add, view, organize, and manage contacts in Owlat, including sources, the contact detail tabs, and subscription compliance.",{"path":181,"title":182,"description":183},"\u002Fapi","API Overview","Owlat exposes authenticated API endpoints under your Convex site URL.",{"path":185,"title":186,"description":187},"\u002Fapi\u002Fwebhooks","Webhooks","Owlat supports both outbound customer webhooks and inbound provider webhooks.",{"path":189,"title":190,"description":191},"\u002Fapi\u002Fpublic-endpoints","Public Endpoints","These routes are public-facing and usually accessed from email links or embedded forms.",{"path":193,"title":194,"description":195},"\u002Fapi\u002Fwebhook-payloads","Webhook Payloads","The authoritative wire contract for outbound webhooks: envelope, signature headers, per-event data shapes, and payload versioning.",{"path":197,"title":198,"description":199},"\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":201,"title":202,"description":203},"\u002Fapi\u002Fauthentication","Authentication","Secure API access with organization-scoped API keys.",{"path":205,"title":206,"description":207},"\u002Fapi\u002Fsdk","TypeScript SDK","Typed client for the Owlat API, usable from Node.js, Bun, Deno, or any server-side JavaScript runtime.",{"path":209,"title":210,"description":211},"\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":213,"title":214,"description":215},"\u002Fapi\u002Fcontacts","Contacts API","Manage contacts for your organization.",{"path":217,"title":218,"description":219},"\u002Fapi\u002Ftopics","Topics API","Manage topic membership through authenticated endpoints.",{"path":221,"title":222,"description":223},"\u002Fapi\u002Fevents","Events API","Send contact events to drive segmentation and automation triggers.",{"path":225,"title":226,"description":227},"\u002Fapi\u002Ftransactional","Transactional API","Send published transactional templates to a recipient.",{"path":229,"title":230,"description":231},"\u002Fapi\u002Fforms","Forms API","Capture subscribers through public form endpoints.",{"path":233,"title":234,"description":235},"\u002Fdeveloper","Developer Guide","Technical architecture, feature-flag model, and provider abstractions used by Owlat.",{"path":237,"title":238,"description":239},"\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":241,"title":242,"description":243},"\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":245,"title":246,"description":247},"\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":249,"title":250,"description":251},"\u002Fdeveloper\u002Femail-security","Email Security","Content scanning, attachment validation, URL reputation checking, and malware detection for outbound emails.",{"path":253,"title":254,"description":255},"\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":257,"title":258,"description":259},"\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":261,"title":262,"description":263},"\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":265,"title":266,"description":267},"\u002Fdeveloper\u002Faudience-internals","Audience Internals","Backend reference for contact resolution, the double opt-in lifecycle, topic subscription, the conditions registry, and segment evaluation.",{"path":269,"title":270,"description":271},"\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":273,"title":274,"description":275},"\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":277,"title":278,"description":279},"\u002Fdeveloper\u002Farchitecture","Architecture Overview","Owlat follows a modern serverless architecture with real-time capabilities.",{"path":281,"title":282,"description":283},"\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":285,"title":286,"description":287},"\u002Fdeveloper\u002Fscopes","Scopes","What each app and package in the Owlat monorepo is responsible for.",{"path":289,"title":290,"description":291},"\u002Fdeveloper\u002Fself-hosting","Self-Hosting","Deploy Owlat on your own infrastructure with Docker Compose. Complete guide from first boot to production.",{"path":293,"title":294,"description":295},"\u002Fdeveloper\u002Fself-hosting-config","Self-Hosting Configuration","Complete reference for Docker environment variables, Convex backend variables, service topology, and volume persistence.",{"path":297,"title":298,"description":299},"\u002Fdeveloper\u002Fself-hosting-dns-email","DNS & Email Setup","Configure DNS records, DKIM signing, SPF, DMARC, and bounce handling for reliable email delivery.",{"path":301,"title":302,"description":303},"\u002Fdeveloper\u002Fself-hosting-production","Production Deployment","Secure your self-hosted Owlat instance with TLS, firewall rules, backups, and monitoring.",{"path":305,"title":306,"description":307},"\u002Fdeveloper\u002Fself-hosting-maintenance","Maintenance & Updates","Keep your self-hosted Owlat instance up to date, manage backups, scale performance, and troubleshoot common issues.",{"path":309,"title":310,"description":311},"\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":313,"title":314,"description":315},"\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":317,"title":318,"description":319},"\u002Fdeveloper\u002Fconvex","Convex Backend","Owlat uses Convex as its serverless backend, providing real-time subscriptions, ACID transactions, and TypeScript-first development.",{"path":321,"title":202,"description":322},"\u002Fdeveloper\u002Fauthentication","Owlat uses BetterAuth with the Convex adapter for authentication and organization (team) management.",{"path":324,"title":325,"description":326},"\u002Fdeveloper\u002Femail-system","Email System","Owlat's email system consists of a visual editor, template management, and multi-provider sending infrastructure.",{"path":328,"title":329,"description":330},"\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":332,"title":333,"description":334},"\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":336,"title":337,"description":338},"\u002Fdeveloper\u002Fcomponents","Component Library","Reference for the reusable, auto-imported Vue UI components shipped in the packages\u002Fui layer.",{"path":340,"title":341,"description":342},"\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":344,"title":345,"description":346},"\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":348,"title":349,"description":350},"\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":352,"title":353,"description":354},"\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":356,"title":357,"description":358},"\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":360,"title":361,"description":362},"\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":364,"title":365,"description":366},"\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":368,"title":369,"description":370},"\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":372,"title":373,"description":374},"\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":376,"title":377,"description":378},"\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":380,"title":381,"description":382},"\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":384,"title":385,"description":386},"\u002Fexamples","Examples","Copy-pasteable integration patterns for common Owlat use cases.",{"path":388,"title":389,"description":390},"\u002Fexamples\u002Fwelcome-email","Welcome Email","Send a personalized welcome email when a new user signs up.",{"path":392,"title":393,"description":394},"\u002Fexamples\u002Fbilling-email","Billing Email","Send a billing receipt with an invoice PDF attached after a successful payment.",{"path":396,"title":397,"description":398},"\u002Fexamples\u002Fevent-automation","Event Automation","Trigger automations with custom events for trial lifecycle, feature adoption, and more.",{"path":400,"title":401,"description":402},"\u002Fexamples\u002Fcontact-sync","Contact Sync","Sync contacts from your database to Owlat using upsert patterns and bulk operations.",{"path":404,"title":405,"description":406},"\u002Fexamples\u002Fwebhook-handler","Webhook Handler","Handle Owlat delivery webhooks with signature verification and event routing.",{"path":408,"title":409,"description":410},"\u002Fexamples\u002Fmultilingual-email","Multilingual Email","Send emails in the recipient's preferred language using template translations.",{"path":412,"title":413,"description":414},"\u002Fvision","Vision","Where Owlat is heading — from email platform to unified communication intelligence powered by AI agents.",{"path":416,"title":417,"description":418},"\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":420,"title":421,"description":422},"\u002Fvision\u002Fagent-pipeline","Agent Pipeline","Technical architecture for the inbound email agent pipeline — step modules, the walker, security scanning, threading, and human review.",{"path":424,"title":106,"description":425},"\u002Fvision\u002Fknowledge-graph","Technical architecture for Owlat's typed knowledge storage — how organizational knowledge is stored, searched, decayed, and maintained.",{"path":427,"title":428,"description":429},"\u002Fvision\u002Fmulti-channel","Multi-Channel & CRM","Technical architecture for channel adapters, unified messaging, contact identity unification, and the CRM hub.",{"path":431,"title":432,"description":433},"\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":435,"title":436,"description":437},"\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":439,"title":440,"description":441},"\u002Fvision\u002Froadmap","Roadmap","What's planned next for Owlat — the documented-but-unbuilt pieces still being wired, and the enhancements on our radar.",{"id":443,"title":314,"body":444,"description":315,"extension":2511,"meta":2512,"navigation":802,"path":313,"seo":2513,"stem":2514,"__hash__":2515},"content\u002F3.developer\u002F36.setup-cli.md",{"type":445,"value":446,"toc":2477},"minimark",[447,468,473,482,515,521,622,634,638,761,764,830,837,847,859,862,1004,1019,1023,1121,1159,1163,1177,1304,1306,1323,1347,1351,1400,1437,1441,1458,1543,1579,1586,1589,1606,1624,1652,1654,1657,1678,1690,1694,1710,1817,1833,1837,1857,1950,1979,2024,2028,2047,2050,2113,2130,2142,2146,2155,2235,2243,2259,2262,2275,2306,2309,2343,2405,2415,2419,2456,2460,2464,2467,2470,2473],[448,449,450,451,455,456,459,460,463,464,467],"p",{},"Owlat ships a single self-host toolchain: a one-liner installer (",[452,453,454],"code",{},"install.sh",") that bootstraps a host, and the ",[452,457,458],{},"owlat-setup"," CLI (built from ",[452,461,462],{},"apps\u002Fsetup-cli\u002F",") that does everything from config through a fully deployed, seeded instance. This page is the command reference for both. For the narrative deploy guide, start at ",[465,466,290],"a",{"href":289},"; this page documents the individual commands those guides invoke.",[469,470,472],"h2",{"id":471},"the-one-liner-installer","The one-liner installer",[448,474,475,477,478,481],{},[452,476,454],{}," is served at ",[452,479,480],{},"https:\u002F\u002Fget.owlat.app",". On a fresh Linux VPS with Docker and Docker Compose v2 installed:",[483,484,489],"pre",{"className":485,"code":486,"language":487,"meta":488,"style":488},"language-sh shiki shiki-themes github-light github-dark-dimmed","curl -fsSL https:\u002F\u002Fget.owlat.app | bash\n","sh","",[452,490,491],{"__ignoreMap":488},[492,493,496,500,504,508,512],"span",{"class":494,"line":495},"line",1,[492,497,499],{"class":498},"sOLd2","curl",[492,501,503],{"class":502},"sviXB"," -fsSL",[492,505,507],{"class":506},"s-HuK"," https:\u002F\u002Fget.owlat.app",[492,509,511],{"class":510},"s7YZ4"," |",[492,513,514],{"class":498}," bash\n",[448,516,517,518,520],{},"The script (",[452,519,454],{},") does four things, then hands off to the CLI:",[522,523,524,529,550,554,581,589,607,611],"steps",{},[525,526,528],"h3",{"id":527},"preflight","Preflight",[448,530,531,532,534,535,534,538,541,542,545,546,549],{},"Verifies ",[452,533,499],{},", ",[452,536,537],{},"git",[452,539,540],{},"docker",", that the Docker daemon is reachable (",[452,543,544],{},"docker info","), and that ",[452,547,548],{},"docker compose version"," works (Compose v2 is required).",[525,551,553],{"id":552},"clone-or-detect","Clone or detect",[448,555,556,557,560,561,564,565,568,569,572,573,576,577,580],{},"If you are already inside a clone (it looks for ",[452,558,559],{},"scripts\u002Fsetup.sh"," + ",[452,562,563],{},"docker-compose.yml","), it uses it. Otherwise it shallow-clones ",[452,566,567],{},"github.com\u002Fwolvesdotink\u002Fowlat"," into ",[452,570,571],{},"\u002Fopt\u002Fowlat"," (the default ",[452,574,575],{},"OWLAT_INSTALL_DIR","). If the install directory's parent is not writable, it is created with ",[452,578,579],{},"sudo"," and chowned to you. An existing clone is fetched and checked out (detached) at the target ref.",[525,582,584,585,588],{"id":583},"install-the-owlat-cli","Install the ",[452,586,587],{},"owlat"," CLI",[448,590,591,592,595,596,599,600,602,603,606],{},"Symlinks ",[452,593,594],{},"scripts\u002Fowlat"," to ",[452,597,598],{},"\u002Fusr\u002Flocal\u002Fbin\u002Fowlat"," (using ",[452,601,579],{}," if needed) so you can run ",[452,604,605],{},"owlat \u003Ccommand>"," from anywhere afterward.",[525,608,610],{"id":609},"run-the-wizard","Run the wizard",[448,612,613,614,617,618,621],{},"Execs the containerized quickstart: ",[452,615,616],{},"scripts\u002Fowlat quickstart --terminal",". The ",[452,619,620],{},"--terminal"," flag is forced because the browser-based web wizard cannot run inside the containerized installer.",[448,623,624,625,628,629,633],{},"Because the wizard runs inside the ",[452,626,627],{},"wolvesdotink\u002Fsetup"," Docker image, the host needs only Docker and Compose — ",[630,631,632],"strong",{},"no Bun or Node",".",[525,635,637],{"id":636},"installer-environment-variables","Installer environment variables",[639,640,641,654],"table",{},[642,643,644],"thead",{},[645,646,647,651],"tr",{},[648,649,650],"th",{},"Variable",[648,652,653],{},"Effect",[655,656,657,672,689,704,716,733,746],"tbody",{},[645,658,659,665],{},[660,661,662],"td",{},[452,663,664],{},"OWLAT_REPO",[660,666,667,668,671],{},"Git URL to clone (default ",[452,669,670],{},"https:\u002F\u002Fgithub.com\u002Fwolvesdotink\u002Fowlat.git",")",[645,673,674,679],{},[660,675,676],{},[452,677,678],{},"OWLAT_REF",[660,680,681,682,685,686,688],{},"Tag\u002Fbranch\u002Fref to install (default: latest release tag; falls back to ",[452,683,684],{},"main"," only if the repo has no published release — HTTP 404. A hard API failure or rate-limit aborts the install rather than silently installing ",[452,687,684],{},".)",[645,690,691,696],{},[660,692,693],{},[452,694,695],{},"OWLAT_BRANCH",[660,697,698,699,701,702],{},"Honored only as a fallback when ",[452,700,678],{}," is unset; it does not default to ",[452,703,684],{},[645,705,706,710],{},[660,707,708],{},[452,709,575],{},[660,711,712,713,715],{},"Where to clone (default ",[452,714,571],{},"; created with sudo + chowned to you if the parent isn't writable)",[645,717,718,723],{},[660,719,720],{},[452,721,722],{},"OWLAT_ASSUME_YES",[660,724,725,728,729,732],{},[452,726,727],{},"1"," passes ",[452,730,731],{},"--assume-yes"," to the wizard (CI \u002F Ansible)",[645,734,735,740],{},[660,736,737],{},[452,738,739],{},"OWLAT_CONFIG_FILE",[660,741,742,743],{},"Path to an answers file, passed as ",[452,744,745],{},"--config \u003Cpath>",[645,747,748,753],{},[660,749,750],{},[452,751,752],{},"OWLAT_LEGACY_WIZARD",[660,754,755,757,758,760],{},[452,756,727],{}," runs the legacy bash wizard (",[452,759,559],{},") instead",[448,762,763],{},"Non-interactive examples:",[483,765,767],{"className":485,"code":766,"language":487,"meta":488,"style":488},"# Accept all defaults\nOWLAT_ASSUME_YES=1 curl -fsSL https:\u002F\u002Fget.owlat.app | bash\n\n# Drive from a config file (CI \u002F Ansible)\nOWLAT_CONFIG_FILE=\u002Fpath\u002Fto\u002Fanswers.env curl -fsSL https:\u002F\u002Fget.owlat.app | bash\n",[452,768,769,775,797,804,810],{"__ignoreMap":488},[492,770,771],{"class":494,"line":495},[492,772,774],{"class":773},"sDN9O","# Accept all defaults\n",[492,776,778,781,784,786,789,791,793,795],{"class":494,"line":777},2,[492,779,722],{"class":780},"sYgZi",[492,782,783],{"class":510},"=",[492,785,727],{"class":506},[492,787,788],{"class":498}," curl",[492,790,503],{"class":502},[492,792,507],{"class":506},[492,794,511],{"class":510},[492,796,514],{"class":498},[492,798,800],{"class":494,"line":799},3,[492,801,803],{"emptyLinePlaceholder":802},true,"\n",[492,805,807],{"class":494,"line":806},4,[492,808,809],{"class":773},"# Drive from a config file (CI \u002F Ansible)\n",[492,811,813,815,817,820,822,824,826,828],{"class":494,"line":812},5,[492,814,739],{"class":780},[492,816,783],{"class":510},[492,818,819],{"class":506},"\u002Fpath\u002Fto\u002Fanswers.env",[492,821,788],{"class":498},[492,823,503],{"class":502},[492,825,507],{"class":506},[492,827,511],{"class":510},[492,829,514],{"class":498},[469,831,833,836],{"id":832},"owlat-quickstart-end-to-end-deploy",[452,834,835],{},"owlat quickstart"," — end-to-end deploy",[448,838,839,842,843,846],{},[452,840,841],{},"quickstart"," (",[452,844,845],{},"apps\u002Fsetup-cli\u002Fsrc\u002Fcommands\u002Fquickstart.ts",") takes a fresh clone all the way to a running, admin-bootstrapped, optionally-seeded instance. Every step is idempotent, so re-running is safe.",[483,848,850],{"className":485,"code":849,"language":487,"meta":488,"style":488},"owlat quickstart\n",[452,851,852],{"__ignoreMap":488},[492,853,854,856],{"class":494,"line":495},[492,855,587],{"class":498},[492,857,858],{"class":506}," quickstart\n",[448,860,861],{},"The flow:",[863,864,865,880,899,921,929,939,950,970,983,994],"ol",{},[866,867,868,871,872,875,876,879],"li",{},[630,869,870],{},"Sanity checks"," — confirms you are in the monorepo (a ",[452,873,874],{},"turbo.json"," exists at ",[452,877,878],{},"--owlat-dir",") and that the Docker daemon is reachable.",[866,881,882,885,886,889,890,893,894,898],{},[630,883,884],{},"Config"," — if ",[452,887,888],{},".env"," or ",[452,891,892],{},"docker-compose.override.yml"," is missing, it runs the ",[465,895,897],{"href":896},"#setup-wizard-web-vs-terminal","setup wizard"," first; otherwise it reuses the existing config.",[866,900,901,904,905,908,909,912,913,916,917,920],{},[630,902,903],{},"Mode prompt"," — ",[452,906,907],{},"populated"," (admin + demo data, the default), ",[452,910,911],{},"blank"," (no admin, no data — to test the real ",[452,914,915],{},"\u002Fauth\u002Fregister"," signup flow), or ",[452,918,919],{},"custom"," (decide per step).",[866,922,923,928],{},[630,924,925],{},[452,926,927],{},"docker compose up -d"," — brings up the self-host stack.",[866,930,931,934,935,938],{},[630,932,933],{},"Wait for Convex"," — polls ",[452,936,937],{},"\u002Fversion"," on the Convex cloud port (3210). The application HTTP routes only exist after functions deploy, so the cloud port is probed here.",[866,940,941,944,945,949],{},[630,942,943],{},"Deploy the backend"," — see ",[465,946,948],{"href":947},"#pushing-convex-runtime-env-vars-convex-deploy","Pushing Convex runtime env vars",". This mints the admin key, deploys functions, and pushes runtime env vars.",[866,951,952,934,955,958,959,962,963,534,966,969],{},[630,953,954],{},"Wait for HTTP routes",[452,956,957],{},"\u002Fapi\u002Fv1\u002Fhealth"," on the Convex site proxy (3211), where the freshly-deployed ",[452,960,961],{},"http.route"," handlers (",[452,964,965],{},"\u002Fseed\u002F*",[452,967,968],{},"\u002Fdev\u002F*",", tracking, webhooks) live.",[866,971,972,975,976,978,979,633],{},[630,973,974],{},"Bootstrap admin"," (unless ",[452,977,911],{},") — see ",[465,980,982],{"href":981},"#admin-bootstrap","Admin bootstrap",[866,984,985,975,988,889,990,993],{},[630,986,987],{},"Seed demo data",[452,989,911],{},[452,991,992],{},"--no-seed",").",[866,995,996,999,1000,1003],{},[630,997,998],{},"Summary"," — prints the web app URL (",[452,1001,1002],{},"http:\u002F\u002Flocalhost:3000","), the Convex URL, and the admin email.",[1005,1006,1009],"callout",{"title":1007,"type":1008},"Why a fresh backend needs the deploy step","info",[448,1010,1011,1012,1015,1016,1018],{},"A freshly-booted self-hosted Convex backend is ",[630,1013,1014],{},"empty",": it serves the sync protocol and ",[452,1017,937],{}," on the cloud port but has zero application functions, no schema, and no function-runtime env vars. Step 6 is what turns that empty backend into a working deployment — it is the step earlier installers omitted entirely.",[525,1020,1022],{"id":1021},"quickstart-flags","Quickstart flags",[639,1024,1025,1035],{},[642,1026,1027],{},[645,1028,1029,1032],{},[648,1030,1031],{},"Flag",[648,1033,1034],{},"Purpose",[655,1036,1037,1054,1064,1074,1084,1093,1111],{},[645,1038,1039,1044],{},[660,1040,1041],{},[452,1042,1043],{},"--mode \u003Cm>",[660,1045,1046,1048,1049,1048,1051,1053],{},[452,1047,907],{}," | ",[452,1050,911],{},[452,1052,919],{}," (skips the mode prompt)",[645,1055,1056,1061],{},[660,1057,1058],{},[452,1059,1060],{},"--email \u003Ce>",[660,1062,1063],{},"Admin email for bootstrap",[645,1065,1066,1071],{},[660,1067,1068],{},[452,1069,1070],{},"--name \u003Cn>",[660,1072,1073],{},"Admin display name",[645,1075,1076,1081],{},[660,1077,1078],{},[452,1079,1080],{},"--password \u003Cp>",[660,1082,1083],{},"Admin password (minimum 12 characters)",[645,1085,1086,1090],{},[660,1087,1088],{},[452,1089,992],{},[660,1091,1092],{},"Skip demo-data seeding",[645,1094,1095,1102],{},[660,1096,1097,534,1099],{},[452,1098,731],{},[452,1100,1101],{},"-y",[660,1103,1104,1105,1107,1108,671],{},"Accept defaults (implies ",[452,1106,907],{}," mode, admin ",[452,1109,1110],{},"dev@example.com",[645,1112,1113,1118],{},[660,1114,1115],{},[452,1116,1117],{},"--owlat-dir \u003Cdir>",[660,1119,1120],{},"Install directory (default: walk up to the monorepo root)",[483,1122,1124],{"className":485,"code":1123,"language":487,"meta":488,"style":488},"# Fully scripted populated install\nowlat quickstart -y --email admin@example.com --name \"Admin\" --password \"a-strong-password\"\n",[452,1125,1126,1131],{"__ignoreMap":488},[492,1127,1128],{"class":494,"line":495},[492,1129,1130],{"class":773},"# Fully scripted populated install\n",[492,1132,1133,1135,1138,1141,1144,1147,1150,1153,1156],{"class":494,"line":777},[492,1134,587],{"class":498},[492,1136,1137],{"class":506}," quickstart",[492,1139,1140],{"class":502}," -y",[492,1142,1143],{"class":502}," --email",[492,1145,1146],{"class":506}," admin@example.com",[492,1148,1149],{"class":502}," --name",[492,1151,1152],{"class":506}," \"Admin\"",[492,1154,1155],{"class":502}," --password",[492,1157,1158],{"class":506}," \"a-strong-password\"\n",[469,1160,1162],{"id":1161},"command-reference","Command reference",[448,1164,1165,1166,1168,1169,1172,1173,1176],{},"Run any command as ",[452,1167,605],{}," (after the installer symlink) or ",[452,1170,1171],{},"bunx owlat-setup \u003Ccommand>"," from a clone. Pass ",[452,1174,1175],{},"--help"," for the full usage banner.",[639,1178,1179,1189],{},[642,1180,1181],{},[645,1182,1183,1186],{},[648,1184,1185],{},"Command",[648,1187,1188],{},"What it does",[655,1190,1191,1204,1219,1229,1239,1249,1259,1269,1279,1291],{},[645,1192,1193,1197],{},[660,1194,1195],{},[452,1196,841],{},[660,1198,1199,1200,1203],{},"End-to-end deploy (config → ",[452,1201,1202],{},"up"," → deploy → bootstrap → seed).",[645,1205,1206,1211],{},[660,1207,1208],{},[452,1209,1210],{},"setup",[660,1212,1213,1214,560,1216,1218],{},"First-run config wizard — writes ",[452,1215,888],{},[452,1217,892],{}," only.",[645,1220,1221,1226],{},[660,1222,1223],{},[452,1224,1225],{},"config",[660,1227,1228],{},"Re-open the wizard for an existing install (skips already-set fields).",[645,1230,1231,1236],{},[660,1232,1233],{},[452,1234,1235],{},"bootstrap-org",[660,1237,1238],{},"Create the first admin user + the singleton org.",[645,1240,1241,1246],{},[660,1242,1243],{},[452,1244,1245],{},"seed [--reset]",[660,1247,1248],{},"Populate the running instance with realistic demo data.",[645,1250,1251,1256],{},[660,1252,1253],{},[452,1254,1255],{},"reset",[660,1257,1258],{},"Wipe the instance back to blank (to re-test the signup flow).",[645,1260,1261,1266],{},[660,1262,1263],{},[452,1264,1265],{},"feature \u003Ckey> \u003Con|off>",[660,1267,1268],{},"Toggle a single feature flag and regenerate the compose override.",[645,1270,1271,1276],{},[660,1272,1273],{},[452,1274,1275],{},"pack \u003Ckey> \u003Con|off>",[660,1277,1278],{},"Toggle every flag in a feature pack.",[645,1280,1281,1286],{},[660,1282,1283],{},[452,1284,1285],{},"env \u003CKEY> \u003CVALUE>",[660,1287,1288,1289,633],{},"Set a single environment variable in ",[452,1290,888],{},[645,1292,1293,1298],{},[660,1294,1295],{},[452,1296,1297],{},"doctor",[660,1299,1300,1301,1303],{},"Diagnose a broken install (",[452,1302,888],{},", required vars, override, container health).",[525,1305,1235],{"id":1235},[448,1307,1308,1309,1312,1313,1315,1316,1319,1320,633],{},"Creates the first admin user and the singleton organization by POSTing to ",[452,1310,1311],{},"\u002Fseed\u002Fadmin"," (see ",[465,1314,982],{"href":981},"). It hashes the password with BetterAuth's scrypt format client-side, so the backend stores a credential that matches a normal login. It is idempotent: a ",[452,1317,1318],{},"409"," (admin already exists) exits ",[452,1321,1322],{},"0",[483,1324,1326],{"className":485,"code":1325,"language":487,"meta":488,"style":488},"owlat bootstrap-org --email admin@example.com --name \"Admin\" --password \"a-strong-password\"\n",[452,1327,1328],{"__ignoreMap":488},[492,1329,1330,1332,1335,1337,1339,1341,1343,1345],{"class":494,"line":495},[492,1331,587],{"class":498},[492,1333,1334],{"class":506}," bootstrap-org",[492,1336,1143],{"class":502},[492,1338,1146],{"class":506},[492,1340,1149],{"class":502},[492,1342,1152],{"class":506},[492,1344,1155],{"class":502},[492,1346,1158],{"class":506},[525,1348,1350],{"id":1349},"seed-and-reset","seed and reset",[448,1352,1353,1356,1357,1360,1361,1356,1363,1366,1367,1370,1371,1312,1374,1378,1379,1382,1383,1386,1387,1389,1390,1393,1394,1396,1397,1399],{},[452,1354,1355],{},"seed"," POSTs to ",[452,1358,1359],{},"\u002Fseed\u002Fdemo","; ",[452,1362,1255],{},[452,1364,1365],{},"\u002Fdev\u002Freset",". Both endpoints are ",[630,1368,1369],{},"fail-closed"," behind ",[452,1372,1373],{},"OWLAT_DEV_MODE",[465,1375,1377],{"href":1376},"#dev-only-endpoints","the dev-mode note",") and require the ",[452,1380,1381],{},"X-Instance-Secret"," header. ",[452,1384,1385],{},"seed --reset"," wipes seed-tagged rows before re-inserting. ",[452,1388,1255],{}," deletes ",[630,1391,1392],{},"all"," users, the org, and every seeded row, so the next visit to ",[452,1395,1002],{}," redirects to ",[452,1398,915],{}," — use it only on throwaway instances.",[483,1401,1403],{"className":485,"code":1402,"language":487,"meta":488,"style":488},"owlat seed            # idempotent insert\nowlat seed --reset    # wipe seed rows, then re-seed\nowlat reset           # full wipe (prompts unless --assume-yes)\n",[452,1404,1405,1415,1427],{"__ignoreMap":488},[492,1406,1407,1409,1412],{"class":494,"line":495},[492,1408,587],{"class":498},[492,1410,1411],{"class":506}," seed",[492,1413,1414],{"class":773},"            # idempotent insert\n",[492,1416,1417,1419,1421,1424],{"class":494,"line":777},[492,1418,587],{"class":498},[492,1420,1411],{"class":506},[492,1422,1423],{"class":502}," --reset",[492,1425,1426],{"class":773},"    # wipe seed rows, then re-seed\n",[492,1428,1429,1431,1434],{"class":494,"line":799},[492,1430,587],{"class":498},[492,1432,1433],{"class":506}," reset",[492,1435,1436],{"class":773},"           # full wipe (prompts unless --assume-yes)\n",[525,1438,1440],{"id":1439},"feature-and-pack","feature and pack",[448,1442,1443,1446,1447,1450,1451,1453,1454,1457],{},[452,1444,1445],{},"feature"," toggles one flag from the ",[465,1448,1449],{"href":241},"feature flags"," catalogue and regenerates ",[452,1452,892],{},"; dependency cascades are applied and reported. ",[452,1455,1456],{},"pack"," toggles a whole grouping at once. The three packs are:",[639,1459,1460,1470],{},[642,1461,1462],{},[645,1463,1464,1467],{},[648,1465,1466],{},"Pack",[648,1468,1469],{},"Flags it controls",[655,1471,1472,1490,1508],{},[645,1473,1474,1479],{},[660,1475,1476],{},[452,1477,1478],{},"emailClient",[660,1480,1481,534,1484,534,1487],{},[452,1482,1483],{},"inbox",[452,1485,1486],{},"chat",[452,1488,1489],{},"postbox",[645,1491,1492,1497],{},[660,1493,1494],{},[452,1495,1496],{},"marketing",[660,1498,1499,534,1502,534,1505],{},[452,1500,1501],{},"campaigns",[452,1503,1504],{},"automations",[452,1506,1507],{},"transactional",[645,1509,1510,1515],{},[660,1511,1512],{},[452,1513,1514],{},"ai",[660,1516,1517,534,1519,534,1522,534,1525,534,1528,534,1531,534,1534,534,1537,534,1540],{},[452,1518,1514],{},[452,1520,1521],{},"ai.agent",[452,1523,1524],{},"ai.autonomy",[452,1526,1527],{},"ai.knowledge",[452,1529,1530],{},"ai.knowledge.autoLink",[452,1532,1533],{},"ai.knowledge.graphRetrieval",[452,1535,1536],{},"ai.knowledge.analytics",[452,1538,1539],{},"ai.assistant",[452,1541,1542],{},"ai.visualizations",[483,1544,1546],{"className":485,"code":1545,"language":487,"meta":488,"style":488},"owlat feature ai on        # enable a single flag\nowlat pack marketing on    # enable campaigns + automations + transactional\n",[452,1547,1548,1564],{"__ignoreMap":488},[492,1549,1550,1552,1555,1558,1561],{"class":494,"line":495},[492,1551,587],{"class":498},[492,1553,1554],{"class":506}," feature",[492,1556,1557],{"class":506}," ai",[492,1559,1560],{"class":506}," on",[492,1562,1563],{"class":773},"        # enable a single flag\n",[492,1565,1566,1568,1571,1574,1576],{"class":494,"line":777},[492,1567,587],{"class":498},[492,1569,1570],{"class":506}," pack",[492,1572,1573],{"class":506}," marketing",[492,1575,1560],{"class":506},[492,1577,1578],{"class":773},"    # enable campaigns + automations + transactional\n",[448,1580,1581,1582,1585],{},"Both print the new active compose profiles and remind you to run ",[452,1583,1584],{},"owlat restart"," to apply.",[525,1587,1588],{"id":1588},"env",[448,1590,1591,1592,1594,1595,534,1598,1601,1602,1605],{},"Sets a single variable in ",[452,1593,888],{},". The key must be an uppercase shell-safe identifier; values for ",[452,1596,1597],{},"*_KEY",[452,1599,1600],{},"*_SECRET",", and ",[452,1603,1604],{},"*_PASSWORD"," are masked in the output.",[483,1607,1609],{"className":485,"code":1608,"language":487,"meta":488,"style":488},"owlat env LLM_API_KEY sk-...\n",[452,1610,1611],{"__ignoreMap":488},[492,1612,1613,1615,1618,1621],{"class":494,"line":495},[492,1614,587],{"class":498},[492,1616,1617],{"class":506}," env",[492,1619,1620],{"class":506}," LLM_API_KEY",[492,1622,1623],{"class":506}," sk-...\n",[1005,1625,1628],{"title":1626,"type":1627},"env only updates .env","warning",[448,1629,1630,1632,1633,1635,1636,1639,1640,1643,1644,1647,1648,1651],{},[452,1631,1588],{}," writes the compose ",[452,1634,888],{}," file, which configures the container processes (web, convex, mta). It does ",[630,1637,1638],{},"not"," push the value into the Convex function runtime. Variables read by Convex functions (the ",[465,1641,1642],{"href":947},"runtime env keys",") must additionally be set with ",[452,1645,1646],{},"convex env set"," — re-run the deploy step, or set them through the ",[452,1649,1650],{},"convex-deploy"," container, for the functions to see them.",[525,1653,1297],{"id":1297},[448,1655,1656],{},"Runs a checklist against the install and exits non-zero on any failure:",[863,1658,1659,1664,1667,1672],{},[866,1660,1661,1663],{},[452,1662,888],{}," exists and parses.",[866,1665,1666],{},"Every env var required by the active feature set is populated.",[866,1668,1669,1671],{},[452,1670,892],{}," exists.",[866,1673,1674,1675,993],{},"At least one compose service is running (",[452,1676,1677],{},"docker compose ps",[483,1679,1681],{"className":485,"code":1680,"language":487,"meta":488,"style":488},"owlat doctor\n",[452,1682,1683],{"__ignoreMap":488},[492,1684,1685,1687],{"class":494,"line":495},[492,1686,587],{"class":498},[492,1688,1689],{"class":506}," doctor\n",[525,1691,1693],{"id":1692},"wrapper-lifecycle-commands","Wrapper lifecycle commands",[448,1695,1696,1697,1699,1700,1702,1703,1706,1707,1709],{},"The installed ",[452,1698,598],{}," wrapper (",[452,1701,594],{},") also exposes day-2 ops commands that are pure ",[452,1704,1705],{},"docker compose"," ceremony — these are wrapper-only and have no ",[452,1708,458],{}," equivalent:",[639,1711,1712,1720],{},[642,1713,1714],{},[645,1715,1716,1718],{},[648,1717,1185],{},[648,1719,1188],{},[655,1721,1722,1734,1746,1758,1769,1781,1798],{},[645,1723,1724,1729],{},[660,1725,1726],{},[452,1727,1728],{},"start",[660,1730,1731,1733],{},[452,1732,927],{}," (activating the profiles for enabled features).",[645,1735,1736,1741],{},[660,1737,1738],{},[452,1739,1740],{},"stop",[660,1742,1743,633],{},[452,1744,1745],{},"docker compose down",[645,1747,1748,1753],{},[660,1749,1750],{},[452,1751,1752],{},"restart [service]",[660,1754,1755,633],{},[452,1756,1757],{},"docker compose restart [service]",[645,1759,1760,1765],{},[660,1761,1762],{},[452,1763,1764],{},"status",[660,1766,1767,633],{},[452,1768,1677],{},[645,1770,1771,1776],{},[660,1772,1773],{},[452,1774,1775],{},"logs [service]",[660,1777,1778,633],{},[452,1779,1780],{},"docker compose logs -f [service]",[645,1782,1783,1788],{},[660,1784,1785],{},[452,1786,1787],{},"shell \u003Cservice>",[660,1789,1790,1793,1794,1797],{},[452,1791,1792],{},"docker compose exec \u003Cservice> sh"," (defaults to the ",[452,1795,1796],{},"web"," service).",[645,1799,1800,1805],{},[660,1801,1802],{},[452,1803,1804],{},"version",[660,1806,1807,1808,1810,1811,1813,1814,993],{},"Print the running stack version (read from the ",[452,1809,1796],{}," container, falling back to the local ",[452,1812,888],{}," \u002F ",[452,1815,1816],{},"package.json",[448,1818,1819,1820,534,1823,534,1826,1601,1829,1832],{},"The wrapper additionally provides ",[452,1821,1822],{},"backup",[452,1824,1825],{},"restore",[452,1827,1828],{},"backup-schedule",[452,1830,1831],{},"upgrade"," for backups and in-app updates.",[469,1834,1836],{"id":1835},"pushing-convex-runtime-env-vars-convex-deploy","Pushing Convex runtime env vars (convex-deploy)",[448,1838,1839,1840,1843,1844,1846,1847,1849,1850,1853,1854,1856],{},"Convex ",[630,1841,1842],{},"functions"," read their configuration from the deployment, not from the compose ",[452,1845,888],{},". The compose ",[452,1848,888],{}," only configures the container processes; it does not reach the Convex function sandbox. So a deploy has three host-side steps (",[452,1851,1852],{},"apps\u002Fsetup-cli\u002Fsrc\u002Flib\u002FconvexDeploy.ts","), all of which are pure ",[452,1855,1705],{}," calls:",[522,1858,1859,1863,1884,1888,1901,1928,1932],{},[525,1860,1862],{"id":1861},"mint-the-admin-key","Mint the admin key",[448,1864,1865,1868,1869,1872,1873,1876,1877,1879,1880,1883],{},[452,1866,1867],{},"generateConvexAdminKey()"," runs ",[452,1870,1871],{},"docker compose exec convex .\u002Fgenerate_admin_key.sh",". The key is ",[630,1874,1875],{},"issued by the running backend"," and cannot be fabricated client-side — a random string is rejected. The minted key is written to ",[452,1878,888],{}," as ",[452,1881,1882],{},"CONVEX_ADMIN_KEY"," so the next two steps can authenticate via compose interpolation.",[525,1885,1887],{"id":1886},"deploy-functions","Deploy functions",[448,1889,1890,1893,1894,1897,1898,1900],{},[452,1891,1892],{},"deployConvexFunctions()"," runs the one-shot deploy profile, pushing ",[452,1895,1896],{},"apps\u002Fapi"," functions, schema, and ",[452,1899,961],{}," handlers:",[483,1902,1904],{"className":485,"code":1903,"language":487,"meta":488,"style":488},"docker compose --profile deploy run --rm convex-deploy\n",[452,1905,1906],{"__ignoreMap":488},[492,1907,1908,1910,1913,1916,1919,1922,1925],{"class":494,"line":495},[492,1909,540],{"class":498},[492,1911,1912],{"class":506}," compose",[492,1914,1915],{"class":502}," --profile",[492,1917,1918],{"class":506}," deploy",[492,1920,1921],{"class":506}," run",[492,1923,1924],{"class":502}," --rm",[492,1926,1927],{"class":506}," convex-deploy\n",[525,1929,1931],{"id":1930},"push-runtime-env-vars","Push runtime env vars",[448,1933,1934,1868,1937,1939,1940,1942,1943,560,1946,1949],{},[452,1935,1936],{},"setConvexEnvVars()",[452,1938,1646],{}," for every populated runtime var, again through the ",[452,1941,1650],{}," container (which already pins the CLI and receives ",[452,1944,1945],{},"CONVEX_SELF_HOSTED_URL",[452,1947,1948],{},"CONVEX_SELF_HOSTED_ADMIN_KEY","). Values are passed as argv to the container shell and consumed via positional parameters, so secrets are never interpolated by a host shell.",[448,1951,1952,1953,1956,1957,1960,1961,1963,1964,1967,1968,1971,1972,534,1975,1978],{},"The list of keys that get pushed is ",[452,1954,1955],{},"CONVEX_RUNTIME_ENV_KEYS",", defined in ",[452,1958,1959],{},"packages\u002Fshared\u002Fsrc\u002FconvexRuntimeEnv.ts"," and re-exported from ",[452,1962,1852],{},", kept in sync with the ",[452,1965,1966],{},"EnvKey"," union in ",[452,1969,1970],{},"apps\u002Fapi\u002Fconvex\u002Flib\u002Fenv.ts",". It covers auth and instance secrets, site URLs, email\u002FMTA defaults, provider keys (Resend, SES), the LLM configuration, vector store, analytics, and the inbound-channel webhook secrets. Compose-only vars (ports, image versions, ",[452,1973,1974],{},"NUXT_PUBLIC_*",[452,1976,1977],{},"REDIS_*",") are deliberately excluded — they never belong in the deployment.",[1005,1980,1982],{"title":1981,"type":1008},"The cloud port vs the site proxy",[448,1983,1984,1985,1987,1988,1991,1992,1995,1996,1998,1999,534,2001,534,2003,2005,2006,842,2009,2012,2013,2015,2016,2018,2019,595,2021,2023],{},"Convex serves the sync protocol and the built-in ",[452,1986,937],{}," on the ",[630,1989,1990],{},"cloud"," port (",[452,1993,1994],{},"3210","), but the application ",[452,1997,961],{}," handlers — ",[452,2000,1311],{},[452,2002,1359],{},[452,2004,1365],{},", tracking, webhooks, unsubscribe — live on the ",[630,2007,2008],{},"site proxy",[452,2010,2011],{},"3211",") and only exist after functions deploy. The CLI probes ",[452,2014,1994],{}," while waiting for the backend to boot, then ",[452,2017,2011],{}," once functions are deployed. Posting ",[452,2020,965],{},[452,2022,1994],{}," silently 404s.",[469,2025,2027],{"id":2026},"setup-wizard-web-vs-terminal","Setup wizard: web vs terminal",[448,2029,2030,842,2033,2036,2037,2039,2040,2043,2044,2046],{},[452,2031,2032],{},"owlat setup",[452,2034,2035],{},"apps\u002Fsetup-cli\u002Fsrc\u002Fcommands\u002Fsetup.ts",") is the first-run config wizard. It writes ",[452,2038,888],{}," and the compose override ",[630,2041,2042],{},"only"," — it does not deploy or create your admin. (",[452,2045,1225],{}," is the same wizard, re-opened for an existing install.)",[448,2048,2049],{},"There are two paths:",[2051,2052,2053,2101],"ul",{},[866,2054,2055,2058,2059,2061,2062,2065,2066,2069,2070,2073,2074,2077,2078,534,2081,1813,2084,2087,2088,2092,2093,2095,2096,2098,2099,633],{},[630,2056,2057],{},"Web wizard"," (default when a browser is available) — primes ",[452,2060,888],{}," for setup mode and points you at ",[452,2063,2064],{},"http:\u002F\u002Flocalhost:3000\u002Fsetup",". For SSH installs without a local browser, use the terminal wizard (below) instead. On the final \"Launch\" step its ",[452,2067,2068],{},"\u002Fapi\u002Fsetup\u002Fapply"," endpoint creates the admin (",[452,2071,2072],{},"POST \u002Fseed\u002Fadmin",") ",[630,2075,2076],{},"and"," pushes the function-runtime env vars (",[452,2079,2080],{},"EMAIL_PROVIDER",[452,2082,2083],{},"RESEND_API_KEY",[452,2085,2086],{},"AWS_SES_*",", and the rest of ",[465,2089,2090],{"href":947},[452,2091,1955],{},") into the Convex deployment over the admin API — the HTTP equivalent of ",[452,2094,1646],{},", since the read-only web container can't run the ",[452,2097,1650],{}," step. So the email-provider choice actually reaches the sending code, not just ",[452,2100,888],{},[866,2102,2103,842,2106,2108,2109,2112],{},[630,2104,2105],{},"Terminal wizard",[452,2107,620],{},") — a full TUI covering the same questions: deployment mode, ",[465,2110,2111],{"href":241},"feature picker",", sending provider (Owlat MTA \u002F Resend \u002F Amazon SES), AI provider (OpenRouter \u002F OpenAI \u002F custom OpenAI-compatible), optional integrations (Google Safe Browsing, PostHog), the admin account, and — for the self-hosted MTA — your EHLO and bounce domains.",[448,2114,2115,2116,2119,2120,534,2122,1601,2124,2126,2127,2129],{},"The wizard generates all required secrets automatically (see below), mirrors the resolved flag state to ",[452,2117,2118],{},".owlat-flags.json"," (so ",[452,2121,1297],{},[452,2123,1445],{},[452,2125,1456],{}," share the same baseline), and finishes by telling you to run ",[452,2128,835],{}," to actually bring the stack up.",[1005,2131,2133],{"title":2132,"type":1008},"Web wizard inside the installer",[448,2134,2135,2136,2138,2139,2141],{},"The one-liner forces ",[452,2137,620],{}," because the browser-based wizard cannot run inside the containerized installer. Choose the web wizard only when running ",[452,2140,2032],{}," directly on a host with a browser.",[525,2143,2145],{"id":2144},"generated-secrets","Generated secrets",[448,2147,2148,842,2151,2154],{},[452,2149,2150],{},"ensureSecrets()",[452,2152,2153],{},"apps\u002Fsetup-cli\u002Fsrc\u002Flib\u002Fsecrets.ts",") fills in any missing secret while preserving operator edits:",[639,2156,2157,2166],{},[642,2158,2159],{},[645,2160,2161,2163],{},[648,2162,650],{},[648,2164,2165],{},"Format",[655,2167,2168,2178,2191,2200,2213,2225],{},[645,2169,2170,2175],{},[660,2171,2172],{},[452,2173,2174],{},"BETTER_AUTH_SECRET",[660,2176,2177],{},"48-char URL-safe token",[645,2179,2180,2185],{},[660,2181,2182],{},[452,2183,2184],{},"INSTANCE_SECRET",[660,2186,2187,2190],{},[630,2188,2189],{},"64 hex chars"," (32 bytes) — see the note below",[645,2192,2193,2198],{},[660,2194,2195],{},[452,2196,2197],{},"UNSUBSCRIBE_SECRET",[660,2199,2177],{},[645,2201,2202,2207],{},[660,2203,2204],{},[452,2205,2206],{},"MTA_API_KEY",[660,2208,2209,2212],{},[452,2210,2211],{},"mta_"," + 40-char token",[645,2214,2215,2220],{},[660,2216,2217],{},[452,2218,2219],{},"MTA_WEBHOOK_SECRET",[660,2221,2222,2212],{},[452,2223,2224],{},"whsec_",[645,2226,2227,2232],{},[660,2228,2229],{},[452,2230,2231],{},"REDIS_PASSWORD",[660,2233,2234],{},"32-char token",[448,2236,2237,2239,2240,2242],{},[452,2238,1882],{}," is intentionally ",[630,2241,1638],{}," generated here — it must be minted by the running backend during the deploy step.",[1005,2244,2246],{"title":2245,"type":1627},"INSTANCE_SECRET must be hex",[448,2247,2248,2249,2251,2252,2255,2256,633],{},"The self-hosted Convex backend hex-decodes ",[452,2250,2184],{}," at boot and crashes (",[452,2253,2254],{},"Couldn't hexdecode key",") if it is not valid hex. The wizard generates 64 hex chars; if you set it by hand, mirror the legacy installer's ",[452,2257,2258],{},"openssl rand -hex 32",[469,2260,982],{"id":2261},"admin-bootstrap",[448,2263,2264,2266,2267,2269,2270,842,2272,993],{},[452,2265,1235],{}," (and ",[452,2268,841],{},") create the first admin by POSTing to ",[452,2271,2072],{},[452,2273,2274],{},"apps\u002Fapi\u002Fconvex\u002FseedAdmin.ts",[483,2276,2280],{"className":2277,"code":2278,"language":2279,"meta":488,"style":488},"language-http shiki shiki-themes github-light github-dark-dimmed","POST \u002Fseed\u002Fadmin\nX-Instance-Secret: \u003CINSTANCE_SECRET>\nContent-Type: application\u002Fjson\n\n{ \"email\": \"admin@example.com\", \"name\": \"Admin\", \"passwordHash\": \"\u003Cscrypt-hash>\" }\n","http",[452,2281,2282,2287,2292,2297,2301],{"__ignoreMap":488},[492,2283,2284],{"class":494,"line":495},[492,2285,2286],{},"POST \u002Fseed\u002Fadmin\n",[492,2288,2289],{"class":494,"line":777},[492,2290,2291],{},"X-Instance-Secret: \u003CINSTANCE_SECRET>\n",[492,2293,2294],{"class":494,"line":799},[492,2295,2296],{},"Content-Type: application\u002Fjson\n",[492,2298,2299],{"class":494,"line":806},[492,2300,803],{"emptyLinePlaceholder":802},[492,2302,2303],{"class":494,"line":812},[492,2304,2305],{},"{ \"email\": \"admin@example.com\", \"name\": \"Admin\", \"passwordHash\": \"\u003Cscrypt-hash>\" }\n",[448,2307,2308],{},"The handler:",[2051,2310,2311,2323,2332],{},[866,2312,2313,2314,2316,2317,2319,2320,633],{},"Requires a matching ",[452,2315,1381],{}," header (timing-safe comparison against the deployment's ",[452,2318,2184],{},"); a mismatch returns ",[452,2321,2322],{},"401",[866,2324,2325,2326,2329,2330,633],{},"Is ",[630,2327,2328],{},"one-shot",": if any user already exists it returns ",[452,2331,1318],{},[866,2333,2334,2335,2338,2339,2342],{},"On success (",[452,2336,2337],{},"201",") creates the BetterAuth user, the credential account (storing the scrypt password hash), the singleton organization (slug derived from the email local part), an ",[452,2340,2341],{},"owner"," membership, the user profile, and the instance settings.",[639,2344,2345,2355],{},[642,2346,2347],{},[645,2348,2349,2352],{},[648,2350,2351],{},"Status",[648,2353,2354],{},"Meaning",[655,2356,2357,2366,2385,2396],{},[645,2358,2359,2363],{},[660,2360,2361],{},[452,2362,2337],{},[660,2364,2365],{},"Admin + org created",[645,2367,2368,2373],{},[660,2369,2370],{},[452,2371,2372],{},"400",[660,2374,2375,2376,1813,2379,1813,2382],{},"Invalid JSON or missing ",[452,2377,2378],{},"email",[452,2380,2381],{},"name",[452,2383,2384],{},"passwordHash",[645,2386,2387,2391],{},[660,2388,2389],{},[452,2390,2322],{},[660,2392,2393,2394],{},"Missing or wrong ",[452,2395,1381],{},[645,2397,2398,2402],{},[660,2399,2400],{},[452,2401,1318],{},[660,2403,2404],{},"A user already exists (endpoint is one-shot)",[448,2406,2407,2408,2410,2411,2414],{},"The CLI hashes the password before sending: ",[452,2409,1235],{}," derives a BetterAuth-compatible scrypt hash (",[452,2412,2413],{},"apps\u002Fsetup-cli\u002Fsrc\u002Flib\u002FpasswordHash.ts",") so the seeded credential matches a normal login. You never send the plaintext password to the backend.",[525,2416,2418],{"id":2417},"dev-only-endpoints","Dev-only endpoints",[448,2420,2421,2423,2424,2426,2427,842,2429,2432,2433,2435,2436,2438,2439,2442,2443,2446,2447,2449,2450,2452,2453,2455],{},[452,2422,1359],{}," and ",[452,2425,1365],{}," are guarded by ",[452,2428,1373],{},[452,2430,2431],{},"apps\u002Fapi\u002Fconvex\u002FdevShortcuts\u002F_guard.ts",") and are ",[630,2434,1369],{},": unless ",[452,2437,1373],{}," is truthy in the Convex function runtime, they return ",[452,2440,2441],{},"403",". Local ",[452,2444,2445],{},"dev"," installs default it on; production self-host leaves it off. ",[452,2448,841],{}," flips it on only when you ask it to seed demo data. ",[452,2451,1311],{}," itself is ",[630,2454,1638],{}," dev-gated — it is the production admin-bootstrap path, protected by the instance secret and the one-shot check.",[469,2457,2459],{"id":2458},"related","Related",[2461,2462],"link-card",{"description":2463,"title":290,"to":289},"The narrative deploy guide — first boot to a running stack.",[2461,2465],{"description":2466,"title":294,"to":293},"Every environment variable, service topology, and volume.",[2461,2468],{"description":2469,"title":242,"to":241},"The flag catalogue, packs, and cascade rules behind feature and pack.",[2461,2471],{"description":2472,"title":333,"to":332},"The full env var reference for the backend and services.",[2474,2475,2476],"style",{},"html pre.shiki code .sOLd2, html code.shiki .sOLd2{--shiki-default:#6F42C1;--shiki-dark:#F69D50}html pre.shiki code .sviXB, html code.shiki .sviXB{--shiki-default:#005CC5;--shiki-dark:#6CB6FF}html pre.shiki code .s-HuK, html code.shiki .s-HuK{--shiki-default:#032F62;--shiki-dark:#96D0FF}html pre.shiki code .s7YZ4, html code.shiki .s7YZ4{--shiki-default:#D73A49;--shiki-dark:#F47067}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sDN9O, html code.shiki .sDN9O{--shiki-default:#6A737D;--shiki-dark:#768390}html pre.shiki code .sYgZi, html code.shiki .sYgZi{--shiki-default:#24292E;--shiki-dark:#ADBAC7}",{"title":488,"searchDepth":777,"depth":777,"links":2478},[2479,2487,2491,2499,2504,2507,2510],{"id":471,"depth":777,"text":472,"children":2480},[2481,2482,2483,2485,2486],{"id":527,"depth":799,"text":528},{"id":552,"depth":799,"text":553},{"id":583,"depth":799,"text":2484},"Install the owlat CLI",{"id":609,"depth":799,"text":610},{"id":636,"depth":799,"text":637},{"id":832,"depth":777,"text":2488,"children":2489},"owlat quickstart — end-to-end deploy",[2490],{"id":1021,"depth":799,"text":1022},{"id":1161,"depth":777,"text":1162,"children":2492},[2493,2494,2495,2496,2497,2498],{"id":1235,"depth":799,"text":1235},{"id":1349,"depth":799,"text":1350},{"id":1439,"depth":799,"text":1440},{"id":1588,"depth":799,"text":1588},{"id":1297,"depth":799,"text":1297},{"id":1692,"depth":799,"text":1693},{"id":1835,"depth":777,"text":1836,"children":2500},[2501,2502,2503],{"id":1861,"depth":799,"text":1862},{"id":1886,"depth":799,"text":1887},{"id":1930,"depth":799,"text":1931},{"id":2026,"depth":777,"text":2027,"children":2505},[2506],{"id":2144,"depth":799,"text":2145},{"id":2261,"depth":777,"text":982,"children":2508},[2509],{"id":2417,"depth":799,"text":2418},{"id":2458,"depth":777,"text":2459},"md",{},{"title":314,"description":315},"3.developer\u002F36.setup-cli","4j3AI52qb3diSC7pPuYMbG_cCPXAtG0IzPAAVLvvp68",[2517,2519],{"title":310,"path":309,"stem":2518,"children":-1},"3.developer\u002F35.self-hosting-desktop",{"title":318,"path":317,"stem":2520,"children":-1},"3.developer\u002F4.convex",1782846428863]