feat: replace the worker's flat mailbox-sync rate limiter, which fetched every message of every IMAP folder oldest first on first sight and then deactivated the mailbox after 100 new emails in five minutes with no resume state while Gmail and Outlook imported nothing historical at all, with a per-mailbox sync governor and a real backfill on every provider: three Redis fixed-window lanes shared across workers (priority for mail in a conversation the mailbox owns, resolved through the internal own-conversation endpoint and never held behind ordinary inbound mail; live for new mail after connect with burst, hourly, per-mailbox daily and per-organization daily budgets; backfill paced per minute and charged to the org budget) that defer over-budget mail with the provider cursor pinned before the first deferred message rather than dropping it, escalate to the existing EMAIL_RATE_LIMITED path only for a flood (SyncFloodPerHour new live messages seen in one hour, deduped through a lane cache so a held backlog is not re-counted every pass) or chronic overage (per-mailbox daily budget exhausted on three of the last seven days), cancel the mailbox context on termination so the sync goroutine no longer keeps ticking against a removed entry, and fail open on a Redis outage; an initial import newest first inside the policy window and cap with a resumable per-provider cursor relayed as SYNC_STATE, walking IMAP via UID SEARCH SINCE per eligible folder (skipping trash, drafts, junk and Gmail's All Mail) below a saved UID floor, Gmail via messages.list with a page token, and Graph via a filtered ordered folder listing over inbox, sent items and archive; IMAP live sync moved from CHANGEDSINCE window fetches to UID SEARCH MODSEQ plus envelope-only fetches with bodies read only for admitted messages, first sight of a folder now baselining its HIGHESTMODSEQ instead of walking it, and saved folder cursors seeded from the payload; Gmail history and Graph delta callbacks reshaped to offer ids first so dedupe and admission happen before hydration, with history checkpoints and delta links only advancing past fully stored records and pages, bounded pages per pass, Graph now tracking sent items live so a thread shows both sides, Graph priming walking uncapped and unpersisted to its deltaLink, and a Graph 404 on a just-deleted message treated as a skip; Gmail and Graph bodies capped at MaxEmailBodySize like IMAP already was; a provider 429 during sync backing the loop off instead of being relayed as a rate-limit event that deactivated the mailbox; the loop interval now adaptive with jitter and a five minute ceiling while held; a republished ADD_EMAIL applying a changed policy to an already loaded mailbox; and the dead StartImapWorker removed
2026-08-18 08:43:35 -07:00
2026-08-16 07:54:45 +02:00
feat: make self-hosted onboarding survivable by fixing invite_only, which could not onboard anyone (the accept route is JWT-only, so redeeming the invitation that would create your account required already having one, making the self-host default silently identical to fully closed), threading the invitation token through registration so an invited person lands in the inviting organization instead of a stray workspace, gating SSO just-in-time provisioning behind DISABLE_REGISTRATION (it bypassed the gate entirely, so an instance set to true was still open to anyone the IdP would assert) with SSO_AUTO_PROVISION as the opt-out, correcting the OIDC redirect URL that pointed at /api/v1 against a route at /v1 and 404'd every SSO login, scoping the first-launch exemption so it no longer overrides an explicit lockdown, preserving the remaining TTL when restoring a losing setup token so a public endpoint cannot hold the claim window open forever, replacing a generic 403 with typed registration_invite_only, registration_closed, invitation_invalid, setup_token_invalid and setup_already_complete codes that name the next step, logging why no claim link was issued on an already-claimed instance instead of staying silent, adding a warmblyctl operator CLI (status with health checks and a non-zero exit, reissuable setup-link, user create/list/reset-password/grant-admin/revoke-admin/disable-2fa, hash-password) so a locked-out operator no longer needs hand-written psql, adding read-only instance configuration over 104 environment variables with structural secret redaction and fingerprints, 35 health checks, a database-backed settings tier for the three keys no environment variable owns, hiding the signup form when the config already says invite_only rather than failing the whole form with a toast, and documenting first run, accounts and access, configuration, instance health and troubleshooting alongside the root .env.example the README told operators to write but never shipped (#114)
2026-08-16 05:58:11 +02:00
2026-08-16 07:54:45 +02:00
2026-08-16 07:54:45 +02:00
2026-01-17 09:19:43 +00:00
feat: make self-hosted onboarding survivable by fixing invite_only, which could not onboard anyone (the accept route is JWT-only, so redeeming the invitation that would create your account required already having one, making the self-host default silently identical to fully closed), threading the invitation token through registration so an invited person lands in the inviting organization instead of a stray workspace, gating SSO just-in-time provisioning behind DISABLE_REGISTRATION (it bypassed the gate entirely, so an instance set to true was still open to anyone the IdP would assert) with SSO_AUTO_PROVISION as the opt-out, correcting the OIDC redirect URL that pointed at /api/v1 against a route at /v1 and 404'd every SSO login, scoping the first-launch exemption so it no longer overrides an explicit lockdown, preserving the remaining TTL when restoring a losing setup token so a public endpoint cannot hold the claim window open forever, replacing a generic 403 with typed registration_invite_only, registration_closed, invitation_invalid, setup_token_invalid and setup_already_complete codes that name the next step, logging why no claim link was issued on an already-claimed instance instead of staying silent, adding a warmblyctl operator CLI (status with health checks and a non-zero exit, reissuable setup-link, user create/list/reset-password/grant-admin/revoke-admin/disable-2fa, hash-password) so a locked-out operator no longer needs hand-written psql, adding read-only instance configuration over 104 environment variables with structural secret redaction and fingerprints, 35 health checks, a database-backed settings tier for the three keys no environment variable owns, hiding the signup form when the config already says invite_only rather than failing the whole form with a toast, and documenting first run, accounts and access, configuration, instance health and troubleshooting alongside the root .env.example the README told operators to write but never shipped (#114)
2026-08-16 05:58:11 +02:00
feat: add a warmblyctl reference page at docs/content/docs/development/warmblyctl.mdx documenting all nine commands with every flag, because the README and the recovery sections only ever showed 'warmblyctl user create --email ... --admin' without saying where the password comes from, leaving self-hosters with an account they could not sign in to and no page that answered it, covering that user create prompts for the password twice on a terminal and refuses on a non-TTY unless --password-stdin is passed, that docker compose exec allocates the TTY those prompts need unless -T is given and piped input needs -T precisely because it removes it, that the password rule is the dashboard's own 8 to 128 characters, and that signing in afterwards needs nothing else on a stock self-host since AUTH_LOGIN_CODE defaults to off and REQUIRE_EMAIL_VERIFICATION to false when self-hosted, captcha stays off without TURNSTILE_SECRET, and --admin opens the panel on ADMIN_URL rather than APP_URL, plus the status JSON contract and exit codes, the four environment variables each command reads, the per-command behaviour when Redis is down, the admin role masks read from AdminRolePermissions, and the make wrappers, registering it in the Development meta.json between accounts-and-access and configuration, linking it from the four pages that already print these commands, correcting the super-admin mask in the first-run sample output from 4294967295 to the 4194303 that AllAdminPermissions actually is since it is (1 << 22) - 1 and not the full uint32 range, and shortening the README self-hosting section by folding the three-bullet gotcha list into a four-row table that also names make doctor and dropping the duplicated make dev claim warning already stated above it, keeping every fact (#128)
2026-08-16 08:39:57 +02:00

Warmbly

The open-source agentic cold email and warmup platform.

Discord Follow @WarmblyHQ on X Docs CI status Latest release License

Features · How it works · Quick start · Self-hosting · Docs · Community · Support

Help us reach more senders and grow the Warmbly community. Star this repo!

Warmbly

Warmbly runs cold email campaigns from the mailboxes you already own and warms them so they keep landing in the inbox. Opens, clicks, and replies land in a shared dashboard the moment they happen, and it's AI-native, so your team and its agents work in it together, live.

https://github.com/user-attachments/assets/378a510a-bb99-425f-925e-04300184938b

Features

  • Campaigns - multi-step sequences with per-mailbox caps and spacing
  • Unified inbox - every mailbox and reply in one place
  • CRM - contacts, pipelines, deals, tasks, meetings
  • Warmup - a pool of monitored mailboxes, not throwaway accounts
  • Deliverability - bounces, complaints, suppression, inbox placement
  • Automations - visual reply playbooks with AI steps
  • Integrations - HubSpot, Slack, Zapier, REST API, webhooks
  • Realtime - live presence and edits across your team

Campaigns Unified inbox

How it works

Warmbly splits into a control plane (backend API, consumer, Postgres, Redis, and the event bus) that owns all state, and an execution plane of interchangeable Go workers that send and sync mail. Workers never touch Postgres, and outbound mail leaves through each mailbox's own provider, not the worker's IP, so you add throughput by running more workers.

flowchart LR
  MB["Your mailboxes"] --> API
  subgraph CP["Control plane"]
    direction TB
    API["Backend API"] --> DB[("Postgres")]
    API --> BUS{{"Event bus"}}
  end
  BUS --> W1["Worker"]
  BUS --> W2["Worker"]
  BUS --> W3["Worker"]
  W1 --> P["Gmail · Microsoft · SMTP"]
  W2 --> P
  W3 --> P
  P --> R["Recipients"]

Secrets use envelope encryption, with a local AES master key by default or AWS KMS if you prefer. Full write-up in the architecture docs.

Quick start

You need Docker, Go 1.25, and pnpm.

git clone https://github.com/warmbly/warmbly && cd warmbly
make dev

One command brings up the backing services in Docker, applies migrations, seeds demo data, and starts the backend, worker, and dashboard natively. Open http://localhost:5173 and log in with dev@warmbly.com / password123, then read the login code out of Mailpit at http://localhost:18025 (the native dev stack keeps the emailed code on so the flow stays exercised; a self-hosted install does not). Full setup lives in the local development guide.

Warning

make dev and make up share one Docker Compose project, one volume, and one warmbly_dev database, and make dev seeds fixture accounts by default. Those accounts become your instance's accounts, which permanently retires the first-run claim link make up prints. Use make dev SEED=false on a database you intend to self-host from. See first run.

Self-hosting

Runs on Docker Compose

Warmbly runs with no cloud account of any kind: no AWS, no GCP, no Stripe, no Kafka. One command brings up the whole platform on local, open-source pieces:

git clone https://github.com/warmbly/warmbly && cd warmbly
make up

That is the whole install. make up waits for the backend and prints a one-time link that claims the instance and makes you its admin. Open it, pick a password, and you are in. You need Docker with Compose v2 and about 10 GB of free disk; the first run builds the images once, which takes roughly 6 minutes.

Nothing else is required: no SMTP relay, no captcha keys, no cloud account, no .env to hand-write, and no separate command to grant yourself admin. To change something, cp .env.example .env and edit it. The template boots as-is and carries the commands that generate your own secrets; set those and APP_ENV=prod before anyone else can reach the instance.

If Then
The claim link is gone It is single use and lasts 24 hours. make claim prints a fresh one
No link was printed at all The database already has accounts, so the instance is claimed. make cli ARGS="user create --email you@example.com --admin" adds you, prompting for a password to set
You would rather skip the link Set WARMBLY_BOOTSTRAP_EMAIL and WARMBLY_BOOTSTRAP_PASSWORD_HASH before the first start and the owner exists when it comes up
Something is wrong make doctor prints the instance state and every failing check

Those all run warmblyctl, the operator CLI baked into the backend image. It talks to the database directly, so it works when signing in does not.

Note

Signing in never depends on outbound mail. Platform email defaults to MAIL_TRANSPORT=log under Compose, so password resets and invitations go to the backend logs until you point SMTP_* at a relay. Invitations still work without one: invite the person under Settings > Members, then copy the link from their row and send it yourself. See accounts and access.

➡️ Follow the step-by-step self-hosting guide for the full walkthrough: your own secrets, verifying the stack is healthy, mail and single sign-on, HTTPS, connecting Gmail and Microsoft mailboxes, scaling workers, backups, and a troubleshooting table.

Every external dependency is picked by an environment variable, so you swap in a cloud service only if you want one:

Concern Self-host default Optional / cloud
Database PostgreSQL 16 RDS / Cloud SQL, any Postgres
Cache Redis (or Valkey) ElastiCache
Event bus NATS JetStream Kafka (-tags kafka)
Blob storage Filesystem S3, MinIO, R2, B2
KMS / root key Local AES master key AWS KMS
Payments Off (everything unlocked) Stripe

Scaling is by mailboxes and workers, not IPs.

Documentation

The full docs live at docs.warmbly.com.

Read this To learn
Self-hosting guide Step-by-step install, then production, backups, and scaling the worker fleet
First run Claiming the instance, reissuing the setup link, and what to do when accounts already exist
Accounts and access Registration modes, inviting people with or without a mail relay, SSO, and recovering access
warmblyctl The operator CLI: creating accounts, setting passwords, granting admin, and instance status
Configuration reference Every environment variable, its default, and whether changing it needs a restart
Instance health The checks the admin panel runs against your deployment, and make doctor
Troubleshooting The errors self-hosters actually hit, and the command that fixes each one
Local development Every make target, the native services, and how seeding works
Architecture How the control plane and workers split the job, plus the encryption model
API reference Endpoints, auth, permissions, and webhooks

Community

Have a question, found a bug, or want to shape where Warmbly goes next?

  • Discord - chat with the team and other senders
  • GitHub Issues - report bugs and request features
  • X / @WarmblyHQ - follow along for updates and releases
  • Email - reach us at team@warmbly.com

Support and enterprise

Note

Need a hand? We are happy to help. Ask in Discord, open a GitHub issue, or email team@warmbly.com, and someone on the team will get back to you.

Running Warmbly at scale, or would you rather we run it for you? We offer enterprise support and managed infrastructure: we can host and operate the whole platform for your organization, help you deploy and scale the worker fleet, tune deliverability, migrate your sending onto Warmbly, and stand behind it with a support agreement built around your team. Tell us what you need at team@warmbly.com or reach out on X.

Follow @WarmblyHQ on X Join the Discord Email the team

Star the repository

warmbly-star

Contributing

Pull requests are welcome. Keep each one to a single logical change, and open an issue first for larger design or product changes. Before you open a PR, run the checks for the tree you touched (make fmt and make lint for Go, pnpm typecheck and pnpm lint for the frontends). See CONTRIBUTING.md.

Security

Found a vulnerability? Email team@warmbly.com instead of opening a public issue. We prefer responsible disclosure and credit reporters in the release notes.

License

Apache License 2.0. Copyright 2026 Mindroot Ltd. See LICENSE.

Languages
Go 41.1%
TypeScript 36.6%
Swift 10%
Astro 9.4%
Elixir 0.9%
Other 1.9%