Documentation

Ledger

Documentation

OutboundOS is a self-hosted outbound workspace. Find or import a list, write a sequence, send from the customer’s SMTP, and handle the reply. Campaigns never leave from a shared warmup pool or from outboundos.org.

This is the Envato buyer guide. Same tokens as the rest of the product. Teal is healthy. Orange is in progress. Red blocks send. The visual contract is /design.

Demo

Live app: webbuddy-outbound.fly.dev/login. On that box, User and Admin fill the fields in one click.

RoleEmailPasswordLands in
Userdemo@demo.comdemo1234Demo workspace — lists, campaigns, replies
Adminadmin@demo.comadmin1234Same workspace, then Open operator

Demo data lives only on that hosted database. A buyer install starts empty.

Requirements

  • Node.js 20 or newer
  • Postgres 16
  • Redis 7 (BullMQ send queue)
  • SMTP + IMAP on the sending domain, or Mailpit for local
  • A VPS, Fly, Railway, Docker, or similar. Not Vercel alone — the worker is a long process.

Install

cp .env.example .env
npm install
npm run infra
npm run db:push
npm run test
npm run dev
npm run worker

Open http://localhost:3000. First visit is /install — owner email, password, workspace name. Mailpit UI is http://localhost:8025. Compose Postgres is on :5433 so it does not collide with a local Postgres on 5432.

Full stack in one command: docker compose --profile full up --build

Two processes

npm run dev (or start) is the app. npm run worker sends and polls IMAP. Without the worker, campaigns queue and never leave.

First campaign

  1. ICP — write who you sell to. Industry, size, titles, geos, pain. This feeds find filters and first-line hints.
  2. Inboxes — add SMTP + IMAP. Send a test. Wait until SPF, DKIM, and DMARC pass (or use Mailpit locally, which skips DNS).
  3. Settings — add a postal address. CAN-SPAM requires it before launch.
  4. Lists — create a list. Import a CSV, find leads, or fetch Apollo. Verify. Clean risk must stay under 3%.
  5. Sequences — add email steps. Every body must include {{unsubscribe_url}}. Add wait steps between sends.
  6. Campaigns — attach list + sequence + inbox. If Launch is locked, the row says why. Fix that, then launch.
  7. Inbox — replies land here. Mark interested to open a deal. A reply stops remaining steps for that lead.

Environment

Copy .env.example. Never commit a real .env. Rotate AUTH_SECRET and KMS_KEY in production.

KeyRequiredNotes
DATABASE_URLYesPostgres. Local compose uses port 5433.
REDIS_URLYesDefault redis://localhost:6379.
AUTH_SECRETYesLong random string. Session signing.
KMS_KEYYesEncrypts SMTP, API, and AI keys at rest.
APP_URLYesPublic origin. Unsubscribe and invite links.
HOSTEDNofalse = /install. true = /signup, no Mailpit.
SUPER_ADMIN_EMAILSNoComma list. Those users see Open operator.
DEMO_MODENoUser / Admin fill on /login. Off for buyers.
RESEND_API_KEYNoAccount mail: welcome, invite, reset.
RESEND_FROMNoFrom header for account mail.
OPENAI_API_KEYNoOptional first lines. Also in Settings.
HUNTER_API_KEYNoFind leads. Workspace Settings override env.
PROSPEO_API_KEYNoFills Hunter misses.
APOLLO_API_KEYNoFetch search into a list. BYO key.
MILLIONVERIFIER_API_KEYNoInbox-exists verify. Else MX only.
STRIPE_*NoHosted billing only. Not needed to send.
MAILPIT_*NoLocal test inbox. Ignored when HOSTED=true.

Self-host vs hosted

  • HOSTED=false First visit is /install. One owner on the box. Mailpit is allowed for test sends. This is the default buyer path.
  • HOSTED=true Signup creates a workspace. Shared workers, isolated data. Still their SMTP only. Plan caps can apply. Use this if you resell as SaaS (Extended license).

The loop

Six steps, in order. Skip one and launch stays locked.

  1. ICP — who you sell to, written down before the list exists.
  2. Inbox — their SMTP and IMAP. DNS must pass before the first live send.
  3. List — find, Apollo fetch, or CSV. Suppression survives re-import. Clean gate is 3%.
  4. Sequence — every email step carries {{unsubscribe_url}}.
  5. Launch — locked until list, sequence, and inbox are green.
  6. Replies — stop-on-reply cancels the rest. Interested becomes a deal.

Screens

Dashboard
Playbook chips, reply / positive / bounce, campaign table. No open rate.
ICP
Industry, size, titles, geos, pain, buying trigger. Feeds first-line hints and find filters.
Inboxes
Connect SMTP + IMAP. Test send. SPF, DKIM, DMARC. Ramp starts at 40/day. Bounce over 3% pauses the inbox until you clear it.
Lists
Create a list. Find leads (Hunter, then Prospeo), fetch Apollo, or import CSV. Edit or delete a lead. Verify. Generate first lines.
Sequences
Email and wait steps. A/B subject. Missing unsubscribe token = not ready. Local spam score is a hint, not a gate.
Campaigns
Attach list + sequence + inbox. Launch lock says why. Pause, resume, stop. Sending hours and daily cap live on the campaign.
Inbox
Replies from IMAP. Keys: j / k move, e interested, u unsub, r reply, p poll. A reply halts remaining sends for that lead.
Pipeline
New, Interested, Meeting, Won, Lost. Meeting wants a URL. Won and Lost are terminal.
Analytics
Reply rate, positive replies, bounce. Subject winners. Never opens. There is no tracking pixel.
Settings
Workspace name, postal address, AI and finder keys. Export redacts secrets. Invite teammates.

CSV import

First row is headers. One column must be a work email. Apollo, Clay, Instantly, Smartlead, HubSpot, ZoomInfo, and a plain spreadsheet all map. Sales Nav rarely has email — enrich first.

Recognised email headers include email, work email, person email. Names and company fill when present. A one-column list of addresses is also accepted. Suppressed addresses are skipped on re-import.

Email tokens

Use these in sequence bodies and subjects.

TokenRequiredFills with
{{unsubscribe_url}}YesOne-click unsubscribe. Every email step.
{{first_name}}NoLead first name.
{{last_name}}NoLead last name.
{{company}}NoCompany.
{{title}}NoJob title.
{{first_line}}NoGenerated or written opener.

Unknown tokens render empty. Do not invent {{name}} — use {{first_name}}.

Launch gates

  • DNS must pass (or local skip). Fail = red, launch locked.
  • List bounce risk over 3% = not launchable.
  • Every email step must include {{unsubscribe_url}}.
  • Postal address is required for the compliance footer.
  • Inbox paused (bounce > 3%) blocks launch until cleared.
  • A reply sets halt. Remaining jobs for that lead are cancelled.
  • OOO re-queues. Unsubscribe writes a suppression that survives re-import.
  • Sent mail includes List-Unsubscribe. Analytics never count opens.

Worker

npm run worker runs src/workers/index.ts. It drains the BullMQ send queue and polls IMAP. Fly and Docker start this as a second process. If Redis is down, the worker logs a connection error and nothing sends.

Optional keys

Hunter, Prospeo, Apollo, MillionVerifier, and AI keys go in Settings (encrypted with KMS_KEY) or in env. Usage is billed by those providers. CSV import, sequences, campaigns, inbox, pipeline, and analytics work with no keys.

Find order: Hunter first, Prospeo fills misses. Apollo fetch is a separate button for a BYO Apollo key. Verify without MillionVerifier is MX-only.

Operator

Add emails to SUPER_ADMIN_EMAILS. Those users see Open operator and can manage workspaces, plans, and hosted mode. On a self-hosted box, the first owner can also open the panel.

Production

Set HOSTED=true, APP_URL to the public origin, and strong AUTH_SECRET / KMS_KEY. Run web + worker against managed Postgres and Redis. fly.toml in the repo is one working shape: web process, worker process, drizzle-kit push on release.

Regular license: one end product, typically one company box. Extended license: you may offer this as a hosted service to multiple customers. See Envato’s license FAQ.

Source tree

PathWhat lives there
src/appRoutes and API. Next.js App Router.
src/componentsShell, marketing, and design-system primitives.
src/lib/domainLaunch, verify, ramp, templates. Covered by Vitest.
src/lib/servicesWorkspace-scoped reads and writes.
src/workersSend queue and IMAP poller.
src/dbDrizzle schema.
src/emailsAccount mail (React Email).
documentationThis guide, offline HTML.

Gates live in src/lib/domain. Run npm test after you change them. Do not add a second color palette — see /design.

FAQ

Signup says use /install
HOSTED is false. That is correct for a self-hosted box.
Campaign stays locked
Read the lock. Fix DNS, list risk, unsubscribe token, or postal address.
Nothing sends
Is the worker up? Is Redis reachable? Is the inbox Ready, not paused?
Find is empty
Put a Hunter or Prospeo key in Settings. Or import a CSV.
Verify is only MX
Add a MillionVerifier key for inbox-exists.
Can I send from outboundos.org?
No. Campaigns use the inbox SMTP the workspace connected.
Does it track opens?
No. Replies and bounces only. There is no pixel.
Where is the demo data on my install?
Nowhere. Seeded data is only on the live listing demo.

Support

Item comments on CodeCanyon, for six months from purchase, as Envato requires. Include Node version, HOSTED, whether the worker is running, and the launch-lock text if send is blocked. Do not paste DATABASE_URL, KMS_KEY, or SMTP passwords.

Credits

Next.js 16, React 19, Drizzle, BullMQ, Tailwind CSS v4, Geist, Source Serif 4, Nodemailer, IMAPFlow, Zod. Optional: Hunter, Prospeo, Apollo, MillionVerifier, Resend, Stripe.

v1.0.0 — CodeCanyon release. Same product as the live demo.