Commit Graph
2140 Commits
Author SHA1 Message Date
Matthew Meszaros 372df39eaa feat: decide the hosted form URL scheme from the host rather than the port, so an install that terminates TLS on a non-default port (forms.example.com:8443) keeps https in its share links, embeds and base_url instead of being downgraded to http by the port check, with a private-network host now treated as the LAN install it is, one scheme helper shared by FormsBaseURL and FormURLOn so the builder's base_url and a form's share_url can never disagree, and a table test covering every install shape 2026-09-07 04:45:21 -07:00
Matthew Meszaros d51501bef1 Merge branch 'main' into fix/issue-343-behaviour 2026-09-07 04:36:42 -07:00
Matthew Meszaros 87a7e3e1ed feat: make a hosted form creatable again by binding an unset embed allowlist and field list as empty arrays instead of NULL in the forms repository writes, which is what made every New form fail with a not-null violation on forms.allowed_domains since the feature shipped (issue #343), and while proving the flow end to end keep the port on the shared forms host so share links and embeds resolve on a ported install, build the embed snippet on the form's own origin so an organization on a verified custom forms domain does not embed from the shared one, and apply the same empty-array fix to webhook endpoints created without event_types, which answered the raw Postgres error instead of the documented subscribe-to-everything 2026-09-07 04:35:56 -07:00
Matthew Meszaros d763e6e783 Merge pull request #354 from warmbly/fix/web-sentry-dsn-runtime-config
Read the dashboard's Sentry DSN from runtime config, so a self-host reports nowhere by default
2026-09-07 04:30:21 -07:00
Matthew Meszaros 9c63cbe0b6 Merge branch 'main' into fix/web-sentry-dsn-runtime-config 2026-09-07 04:25:16 -07:00
Matthew Meszaros b32a28778c Merge pull request #355 from warmbly/feat/agent-tools-forms-segments-suppressions
feat: expose forms, segments and the suppression list to agents
2026-09-07 04:06:57 -07:00
Matthew Meszaros 2e37dc8bb5 Merge pull request #350 from warmbly/feat/imap-every-server
feat(worker): sync every kind of IMAP mailbox
2026-09-07 03:47:29 -07:00
Matthew Meszaros 875cea477f feat: give a duplicate IMAP folder id its own error code and guidance instead of reusing the folder-limit one, because the two problems have different fixes: getting under the folder cap versus renaming the folder the mail server gave a duplicate UIDVALIDITY, and the drawer was telling a user to delete folders when neither the cap nor deleting anything was involved 2026-09-07 03:42:57 -07:00
Matthew Meszaros 63d6637d0c feat: expose forms, segments and the suppression list to agents by adding twenty-three tools to the shared registry, so an MCP client, a Hermes-style REST agent, the dashboard assistant and warmblyctl can all build an audience, publish a lead-capture form, link one to a campaign and manage who is unreachable, each gated on the same contact and campaign permissions its HTTP route already uses 2026-09-07 03:39:42 -07:00
Matthew Meszaros 5a49a39980 feat: state that the IMAP folder ranking applies whether or not the listing exceeds the cap, because the sync pass walks folders in that order and can run out of budget partway, so an outreach reply in the inbox should land before a mailing list in a user folder whatever order the server listed them in, and correct the under-cap test whose comment claimed the opposite of what it asserted 2026-09-07 03:36:34 -07:00
Matthew Meszaros 20c18bc2f7 feat: read the dashboard's Sentry DSN from the container-injected runtime config instead of a literal DSN in web/src/main.tsx, so a self-hosted install reports its users' browser errors, URLs and IPs nowhere unless the operator sets WARMBLY_SENTRY_DSN, and document the variable in configuration.mdx and the no-reporting-by-default stance in data-control.mdx 2026-09-07 03:36:27 -07:00
Matthew Meszaros 883178718e feat: address the CodeRabbit review on the IMAP compatibility PR by guarding the NIL hierarchy delimiter so a server that reports none is not given a NUL separator that hides the leaf fallback and loses the Sent folder, serializing the idle connection's deadline state under a mutex so a warmup action finishing cannot clear the deadline out from under a sync fetch still in flight on the same session, and threading the delimiter the server itself reports through the folder classification so a folder under a separator other than a dot or a slash is recognized instead of filing as inbox 2026-09-07 03:27:50 -07:00
Matthew Meszaros 90f5e8c625 feat: follow only one folder per UIDVALIDITY on IMAP, because the folder row is keyed on that id while RFC 3501 only promises UIDs are stable within a folder, so a server that derives it from the creation time gives a folder tree made in one second a single shared id and the two folders would advance each other's cursor and delete each other's row; the inbox and special folders win the collision, the dropped folder is reported in the mailbox drawer and named in the worker log, and the folder classification, ranking and localized-name matching gain direct tests 2026-09-07 03:20:46 -07:00
Matthew Meszaros 0df1a57589 feat: renumber the unibox_mailboxes uid_next migration from 000131 to 000132 after the campaign continuous lead sources migration took 000131 on main, so golang-migrate does not refuse to build its source driver once both are merged 2026-09-07 03:15:06 -07:00
Matthew Meszaros 1a87720d5a merge 2026-09-07 03:14:42 -07:00
Matthew Meszaros f2c35cf872 feat: sync every kind of IMAP mailbox (issues #345-#349): connect servers without CONDSTORE by following UIDNEXT per folder and mirroring read state with a periodic flag scan, so Outlook.com, Microsoft 365 over IMAP and Yahoo work instead of failing at load; fall back to a STATUS per folder when the server has no LIST-STATUS, which silently made an account look empty; bound the wait between IMAP responses so a peer that vanishes without a FIN cannot park a command forever; keep the inbox and special folders when a mailbox has more folders than the cap (raised to 100) and relay the overflow as a warning instead of failing the mailbox silently; recognize localized folder names and the server's own hierarchy delimiter so a Sent folder called Gesendete Elemente is not filed as inbox; and back off on a widening interval while a mail server is unreachable so one outage is one warning rather than one a minute 2026-09-07 03:12:22 -07:00
Matthew Meszaros 940211902e Merge pull request #344 from warmbly/fix/campaign-restart-keep-running
feat: fix the campaign restart dead end and keep lead-fed campaigns running (issue #340)
2026-09-07 03:09:50 -07:00
Matthew Meszaros fb7cf397db Merge remote-tracking branch 'origin/main' into fix/campaign-restart-keep-running 2026-09-07 03:05:06 -07:00
Matthew Meszaros 6f0284fc2b feat: state in the campaigns API reference that the start-and-wait behaviour applies to a campaign that has at least one lead and nothing left to send, so API clients can tell it apart from the no_leads refusal for a campaign that never had a lead 2026-09-07 03:00:10 -07:00
Matthew Meszaros 889b96c38e feat: address the CodeRabbit review on the campaign restart PR by turning on Keep running for a form's campaign only after every field is validated and failing the form save on any error other than a foreign campaign, running the automation's campaign flip before the automation is saved and failing the save visibly when the campaign is not the workspace's or the flip does not land, and clarifying in the error-code table and the campaigns API reference that no_leads covers a campaign that never had a lead while one whose leads all finished starts and waits 2026-09-07 02:55:12 -07:00
Matthew Meszaros e9754e939f Merge pull request #342 from rocker1166/fix/imap-dead-session-reconnect
fix(worker): reconnect dropped IMAP sessions and sync nested Gmail folders
2026-09-07 02:50:51 -07:00
Matthew Meszaros 5d60fe9a21 feat: fix campaign restart dead end (issue #340): resolve preflight tracking senders through the same explicit/tags/all pool the scheduler uses so a connected mailbox is never reported missing, fetch the launch dialog's step count from the steps endpoint instead of a field the campaign API never returns, turn on Keep running for new leads when a form or an automation feeds a campaign (migration 000131 backfills existing ones) and when a member starts a campaign whose every lead has finished so it goes active and waits for leads instead of answering 400, return waiting_for_leads from the start endpoint for the dialog's success screen, and document the no_leads and no_remaining_leads codes and the new behaviour in the campaigns, forms, automations and API docs 2026-09-07 02:26:50 -07:00
SUMAN JANA 61348a55f7 fix(worker): reuse only an authenticated IMAP session; drain LIST on the folder cap
Address review:

- ensureConnected kept any client not in Logout, so a session whose Login
  failed (NotAuthenticated) was reused instead of re-dialed. Reuse only
  Authenticated or Selected, and close the half-open session when auth or
  the CONDSTORE check fails.
- Folders returned from inside the LIST loop on the folder cap without
  closing the command, leaving unread results to stall the next command.
2026-09-07 07:25:21 +00:00
SUMAN JANA 5eaf4481d5 Merge branch 'fix/imap-dead-session-reconnect' of https://github.com/rocker1166/warmbly into fix/imap-dead-session-reconnect 2026-09-07 07:15:49 +00:00
SUMAN JANA b9fa144d1c fix(worker): guard the IMAP client across reconnects; Gmail-only name fallback
Address review:

- The client field was read by commands while ensureConnected could swap
  it. A lifecycle RWMutex now holds the write lock through dial, auth and
  assignment, and every command holds the read lock for its duration.
  Lock order is mu before lifecycle; sentMailbox resolves under mu before
  AppendToSent takes the read lock.
- The virtual-folder name fallback applied to any server, so a plain IMAP
  account with a real folder called "Important" or "Starred" would have
  been dropped from sync. It now applies only inside Gmail's own
  "[Gmail]/" and "[Google Mail]/" namespace; regression cases added.
2026-09-07 07:15:20 +00:00
Suman Jana c347d34d5a Merge branch 'main' into fix/imap-dead-session-reconnect 2026-09-07 12:38:52 +05:30
SUMAN JANA 04d73acafd fix(worker): reconnect dropped IMAP sessions and sync nested Gmail folders
The worker opened one IMAP session per mailbox at load and never re-dialed.
When the server dropped it (Gmail does after a while), handleError mapped
net.ErrClosed to nil, so every pass ran as a clean "no folders" pass:
nothing logged, no error record, last_synced_at kept moving, no new mail
for days, and sent copies failed with "use of closed network connection".

- imap.Client: ensureConnected re-dials a client parked in Logout state;
  every entry point (Folders, AppendToSent, warmup actions) runs through it.
- handleError: transport errors return a retryable server-unreachable
  error instead of nil, so the sync loop logs and retries.
- Folders: LIST "*" instead of "%", which stopped at the top level and
  never reached [Gmail]/Sent Mail (or Dovecot's INBOX.*); request
  SPECIAL-USE, without which Gmail reports no \Sent/\Trash/\Junk/\All.
- Sync: skip Gmail's virtual label views (All Mail, Starred, Important)
  so known mail is not re-filed as archive under a different UID; retire
  a cursor an earlier build baselined for them. "Bin" is trash.
2026-09-07 06:44:54 +00:00
Matthew Meszaros 885a7c2aa3 Merge pull request #338 from warmbly/feat/continuous-campaigns
feat: keep campaigns running for new leads and reopen finished ones on any lead add (#336)
2026-09-06 03:02:20 -07:00
Matthew Meszaros 54b5cba9a5 feat: address the CodeRabbit review on the continuous campaigns PR by clearing idle_since in the same update that turns the continuous setting off so the dashboard stops reading a campaign as waiting for leads, adding idle_since to the documented campaign response, qualifying the start endpoint's nothing-left-to-send 400 for continuous campaigns, and noting in the automations and forms guides that a finished campaign restarts through the launch checks and a refusal is written to its activity log 2026-09-06 02:58:33 -07:00
Matthew Meszaros 7b11489c85 feat: keep a campaign running for new leads (issue #336): add a continuous campaign setting (migration 000130, on by default once a segment is linked) under which a campaign that runs out of leads stays active and idle with idle_since set instead of finishing, is startable with no leads, is re-checked by the reconciler every pass and shows as waiting for leads in the dashboard list, header and live activity panel with a CAMPAIGN_IDLE realtime event; make WakeCampaigns restart a finished campaign through the full launch checks so a lead added by the contacts API, a bulk update, an import or the add-to-campaign automation action reopens it exactly like a linked segment does, and write a refused restart to the campaign activity log once an hour per reason instead of only the backend log; expose continuous on the campaign API and preferences page and document it in the campaigns, segments, forms, automations, API reference and realtime docs 2026-09-06 02:50:16 -07:00
Matthew Meszaros 436e564750 feat: ignore the /.worktrees/ directory in .gitignore so git worktrees created inside the repo root never show up as untracked files 2026-09-06 02:09:58 -07:00
Matthew Meszaros c3f7bc7b00 Merge pull request #335 from warmbly/feat/lead-intake-automations
feat: lead intake through automations (contact.created and form.submitted triggers, create-or-update contact and add-to-campaign actions) for lead-form sources like Facebook Lead Ads
2026-09-06 00:58:34 -07:00
Matthew Meszaros 41413afa2a feat: keep a failed contact lookup distinguishable from a missing contact in the native automation actions by wrapping the repository error instead of folding it into the no-contact-matched message, as CodeRabbit's follow-up on the lead-intake PR pointed out 2026-09-06 00:54:05 -07:00
Matthew Meszaros 04a2ed59c9 feat: address the CodeRabbit review on the lead-intake PR by giving the webhook sink its own copy of every map payload so the automation goroutine never writes into the map still being marshalled for endpoint delivery, propagating contact lookup errors in the upsert action's skip mode and in the native actions adapter so a failed lookup is never mistaken for a missing contact, adding the contacts source CHECK as NOT VALID in migration 000129 to skip the table scan under the migration lock, defaulting a new create-or-update-contact node's email template to the address key the chosen trigger actually carries, correcting the n8n guide on which lead-form sources have a trigger node, naming API batches over 100 contacts among the silent bulk arrivals in the contacts guide, and updating the marketing automations page from nine to twelve triggers 2026-09-06 00:47:33 -07:00
Matthew Meszaros a3989f9d9c feat: turn automations into a lead-intake path so a Facebook, Instagram, LinkedIn or TikTok lead form pushed by Zapier, Make, n8n or any webhook becomes a tagged, campaign-enrolled contact without leaving Warmbly: add the warmbly.upsert_contact and warmbly.add_to_campaign built-in actions with templated field mapping, custom fields, tags, campaign and an if-exists policy, fire a rich contact.created event from the one contact write path (silent for file imports, sheet syncs and API batches over 100) and expose contact.created and form.submitted as automation triggers with condition fields, variables and sample data, carry an automation depth through events raised by an action so a flow that creates a contact cannot re-trigger itself past five hops, stamp automation-created contacts with the new automation source (migration 000129), share the campaign picker between the sheet sync wizard and the automation builder, document lead intake in the automations, Zapier, Make, contacts, forms, integrations, expressions and webhook pages plus a new n8n guide, mirror the new triggers and actions on the marketing automations page, and drop the 34 MB cli binary that was committed by mistake 2026-09-06 00:36:05 -07:00
Matthew Meszaros fb9e8bcf93 Merge pull request #333 from joaoppa/fix/salvage-expired-batch
Ingest what an expired or cancelled warmup generation batch did finish
2026-09-05 22:00:12 -07:00
Matthew Meszaros 508ee741cf Merge remote-tracking branch 'origin/main' into fix/salvage-expired-batch 2026-09-05 21:33:20 -07:00
Matthew Meszaros c88118661f feat: record an admin cancel on a warmup generation batch job through a conditional MarkBatchCancelling update that only touches a job still running with a non-terminal batch status, so a cancel racing the poller can no longer overwrite the completed state and counts the poller just wrote or send the job back into the active set to be ingested twice 2026-09-05 21:24:11 -07:00
Matthew Meszaros b0c090e521 feat: keep an admin-cancelled warmup generation batch job running with batch_status cancelling instead of marking it failed on the spot, so PollBatches reaches the cancelled branch that PR 333 added and ingests the conversations OpenAI finished before the cancel landed while the admin's reason survives the ingest, refuse a second cancel on a batch already cancelling, hide the admin Cancel button while a batch is cancelling, and refresh the PollBatches, CancelBatch and WarmupBatchPoller doc comments that still said expired and cancelled batches are marked failed 2026-09-05 21:14:46 -07:00
joao-crm 9b44f84fb2 feat: ingest what an expired or cancelled warmup generation batch did finish instead of discarding it, since OpenAI still hands back the requests it completed before the window closed and the poller was marking the job failed with zero generated while a paid output file sat unread, with the terminal-batch decision lifted into endedBatchOutcome so the reason is recorded alongside the salvaged rows 2026-09-05 15:43:07 +00:00
Matthew Meszaros 96aff16824 Merge pull request #332 from warmbly/fix/grpc-ghsa-vp52-heap-exhaustion
fix: bump google.golang.org/grpc to v1.83.1 for GHSA-vp52-pcj8-j9qc
v0.4.0
2026-09-05 02:37:55 -07:00
Matthew Meszaros a338b2442a feat: bump google.golang.org/grpc from v1.82.1 to v1.83.1 to clear GHSA-vp52-pcj8-j9qc (heap exhaustion via HTTP/2 DATA frame fragmentation), pulling the required google.golang.org/api v0.264.0, cloud.google.com/go/auth v0.18.2, gax-go v2.17.0, otel v1.44.0 and genproto bumps that grpc v1.83.1 needs, since pinning api back to v0.260.0 resolves grpc to the still-affected v1.83.0-dev 2026-09-05 02:31:18 -07:00
Matthew Meszaros a026beb074 Merge pull request #330 from warmbly/feature/mailbox-fair-use-allowance
Unlimited mailboxes under fair use, bulk CSV connect, and atomic attachment quota (#326)
2026-09-04 21:34:45 -07:00
Matthew Meszaros 3bf8d4bdfa Merge remote-tracking branch 'origin/main' into feature/mailbox-fair-use-allowance 2026-09-04 21:25:53 -07:00
Matthew Meszaros aac8fd3f6b Merge pull request #329 from warmbly/feature/warmbly-cli
A warmbly CLI: sign in from your terminal and drive the product, installable in one command
2026-09-04 21:20:23 -07:00
Matthew Meszaros 70baaac636 feat: describe a paid plan's mailbox capacity in the free-workspace panel of the allowance dialog as one mailbox for every daily send it includes rather than promising unlimited, and name the upgrade feature More mailboxes to match 2026-09-04 21:17:58 -07:00
Matthew Meszaros c7da25d126 fix: never leave a per-IP rate-limit counter without a TTL, which would block that address forever once it passed the limit, by dropping the key and failing open when EXPIRE fails and repairing a missing expiry on the reject path, and assert the exit status as well as the message when the CLI installer rejects a flag 2026-09-04 21:12:27 -07:00
Matthew Meszaros bf46839fb7 feat: address the CodeRabbit review on the mailbox allowance work by reserving the mailbox slot inside the account insert transaction under a per-organization lock so concurrent connects cannot both take the last one, resolving the storage limit under the quota lock so a plan change cannot be raced past, deleting refused attachment objects on a context that outlives the request, settling already-connected rows before the bulk batch spends allowance, leaving password columns out of the failed-rows CSV, stopping click propagation from the portalled allowance dialog, and counting bulk progress once 2026-09-04 21:11:25 -07:00
Matthew Meszaros b35e111705 fix: address the review on the CLI PR: give the sign-in handshake its own per-IP budget so a 200-poll login cannot lock the address out of the browser login, keep https for a remote host that names a port instead of sending a bearer token in cleartext, report truncation when a paginated walk stops at max-pages, accept a piped secret with no trailing newline, normalise WEBSOCKET_URL on its suffix so a bare /socket becomes a real endpoint, destroy a minted secret the moment its code expires, gate cli-installer-ci on the required status check, print a rejected flag instead of an unbound-variable error, and use a portable sha256 so the packaging runs on macOS 2026-09-04 21:03:58 -07:00
Matthew Meszaros 3596a9770f feat: make mailboxes truly unlimited under a fair-use allowance of one mailbox per daily send the plan includes (15,000 on Business), drop the 200 hard cap and the 5-per-day connect throttle, open a request-more dialog with inline limit requests and plan upgrade when the allowance is reached, add bulk CSV mailbox import with live progress and a downloadable failed-rows file, reserve attachment storage atomically under a per-org lock so concurrent uploads cannot pass the quota (#326), report storage and the allowance on the billing usage meters, and honor approved daily-send overrides in the sender 2026-09-04 21:00:26 -07:00