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
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
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
fb7cf397db
Merge remote-tracking branch 'origin/main' into fix/campaign-restart-keep-running
2026-09-07 03:05:06 -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
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
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
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
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
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
3bf8d4bdfa
Merge remote-tracking branch 'origin/main' into feature/mailbox-fair-use-allowance
2026-09-04 21:25:53 -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
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
Matthew Meszaros
f68918d7f6
Merge remote-tracking branch 'origin/main' into feature/warmbly-cli
2026-09-04 20:14:46 -07:00
Matthew Meszaros
028689fd2e
feat: device-code sign-in for the CLI, with a browser approval page at /cli that mints a scoped API key, self-revocation at DELETE /api-keys/self so a read-only credential can always end itself, and app_url plus websocket_url on /auth/config so a client can find the dashboard and the realtime gateway on a self-hosted layout
2026-09-04 20:14:27 -07:00
Matthew Meszaros
5fd9aedb80
Merge remote-tracking branch 'origin/main' into feature/plan-upgrade-paywall-modal
2026-09-04 09:11:51 -07:00
Matthew Meszaros
e5360713e7
Merge remote-tracking branch 'origin/main' into feature/install-script-setup-wizard
2026-09-04 06:43:12 -07:00
Matthew Meszaros
d78e4423e4
fix: address the review of the notifications work — bound detached alert delivery to a fixed pool that drops and logs rather than spawning a goroutine per event, never echo a transport error that embeds the webhook URL, treat an emptied channel target as cleared so switching transport cannot post Slack payloads to a Discord URL, emit user.registered for invited signups and the worker alert after a successful reassignment, format Stripe amounts by currency exponent, refuse an upgrade while the plan list is still loading instead of silently opening the portal, apply a fixed discount to the annual invoice rather than to each month, convert proration from minor units, contain focus in the nested enterprise dialog, hide the odometer wheels from assistive tech, and drop the last dedicated-IP row from the pricing matrix
2026-09-04 06:33:20 -07:00
Matthew Meszaros
77058a2cb4
feat: address the review on the installer branch by keeping the database password out of pg_dump's argv, excluding backup bundles from the blob root they are written into, tolerating blobs that change or vanish mid-archive, making the instance-settings bootstrap a single atomic insert, and validating the release tag before it is written into .env
2026-09-04 06:24:05 -07:00
Matthew Meszaros
fb868dfe40
feat: delete the storage objects of a step's attachments when the step is deleted, since the attachment rows cascade away with the sequence and left their bytes counted against the organization's storage quota with no row left to reach them
2026-09-04 06:20:26 -07:00
Matthew Meszaros
85f3dad215
feat: send the files a step actually carries by honouring campaign_attachments.sequence_id in the send, test-send and preview paths instead of attaching every campaign file to every step, count them per step in the preflight content score, and refuse an upload naming another campaign's step
2026-09-04 06:16:16 -07:00
Matthew Meszaros
81d6ba4520
Merge remote-tracking branch 'origin/main' into feature/plan-upgrade-paywall-modal
2026-09-04 06:09:14 -07:00
Matthew Meszaros
a0c9d5a5b0
feat: add operator notification channels an admin configures in the panel (Discord, Slack, HMAC-signed webhook, or email) with per-channel event subscriptions, a synchronous test probe, SSRF-guarded URLs and credentials redacted on read, wire nine instance events including the enterprise inquiry that previously wrote a row nobody was told about, and reword the plan-specific limits copy so it reads correctly without billing
2026-09-04 06:02:40 -07:00
Matthew Meszaros
d68bbcd2ab
feat: add a one-command self-host installer at warmbly.com/install.sh with an interactive data-control wizard, give docker-compose.yml image keys and per-store volume variables, add an image-mode updater, move engagement/form/audit retention into instance settings, and add warmblyctl backup/restore
2026-09-04 05:49:54 -07:00
Matthew Meszaros
483d6c2fe7
Merge remote-tracking branch 'origin/main' into fix/issue-307-test-send-preview-from-name
2026-09-04 03:34:35 -07:00
Matthew Meszaros
64e6e3a502
Merge remote-tracking branch 'origin/main' into fix/campaign-daily-budget-deferrals
2026-09-04 03:06:45 -07:00
Matthew Meszaros
2a19bf0519
Merge remote-tracking branch 'origin/main' into fix/campaign-daily-budget-deferrals
2026-09-04 02:59:01 -07:00
Matthew Meszaros
997aa2c748
Merge remote-tracking branch 'origin/main' into fix/issue-307-test-send-preview-from-name
2026-09-04 02:58:40 -07:00
Matthew Meszaros
8ce331a7ac
feat: fix the campaign test-email endpoint answering 404 for every caller by scoping its campaign lookup to the organization instead of the user, let it send from any mailbox of the organization and render a real contact through a new contact_id, attach the campaign's files, and extend the template preview with contact_id, campaign_id and account_id so it applies the signature, opt-out footer and plain-text rule the send path does
2026-09-04 02:58:36 -07:00
Matthew Meszaros
f844c14804
feat: carry the mailbox display name in the emsg body blob of every send so a renamed sender reaches the worker on its next message instead of waiting for an inactive-active toggle, read it into the From header of the Gmail, Graph and SMTP clients with the name cached at load time as the fallback, and say so in the mailboxes guide
2026-09-04 02:58:36 -07:00
Matthew Meszaros
eb85224cc8
Merge remote-tracking branch 'origin/main' into fix/contact-timeline-opaque-cursor
2026-09-04 02:57:59 -07:00
Matthew Meszaros
850ff2b6a2
feat: lock the campaign row for update while replacing its linked segments so two concurrent replacements cannot commit the union of their sets, and return the status read under that lock from ReplaceForCampaign so the wake or restart reaction no longer depends on a post-commit link lookup that a concurrent detach could empty
2026-09-03 20:46:27 -07:00
Matthew Meszaros
ab0c756aa2
feat: show a campaign's linked segments on the Leads tab as chips with live enrolled and held-out counts that filter the list, explain an empty list caused by an empty segment or hand-removed members and add them back in one click, add a scope-aware Export to campaign leads and segment members with a campaign-ready preset that carries lead status and engagement, write the links and the enrolment of PUT /campaigns/:id/segments in one transaction so a failed enrolment changes nothing instead of answering added 0, restart a completed campaign when a one-shot enrol adds leads, clear the manual-removal record when a contact is added by hand, count every link's members in one scan, audit sweep enrolments so open Leads tabs refresh, and document it in the campaigns and segments guides and the API reference
2026-09-03 20:38:27 -07:00
Matthew Meszaros
88d8a2ba8a
feat: page every contact timeline source on a row comparison against the (at, source, id) cursor, unnest campaign progress stamps to one row per event so the limit and the logged-open and logged-click suppression apply per event, fetch one row past the page from every source, return all matching suppression entries, and encode the next cursor from the last event's key
2026-09-03 20:19:16 -07:00
Matthew Meszaros
6bc6bf4345
feat: reword the no-eligible-mailbox pause on the start endpoint, the task auto-pause, and its test to name the only cause left (a sending behaviour profile with no working days) now that budget and window gates defer
2026-09-03 20:18:43 -07:00
Matthew Meszaros
701dc404a2
Merge remote-tracking branch 'origin/main' into fix/click-without-open-tracking
2026-09-03 05:22:05 -07:00
Matthew Meszaros
d9527ad98a
feat: reject an overflowing commit distance in the update version parser instead of clamping it, with a regression case, so a malformed build string can never suppress an available update
2026-09-03 05:04:30 -07:00
Matthew Meszaros
3d5eb36ae5
feat: second review pass on self-hosted updates: git describe suffixes after a prerelease (rc.1-2-gabc1234) now parse as prerelease plus commit distance, Apply validates the updater for every target including explicit tags, a DNS failure only reads as the compose profile being off when UPDATER_URL names the updater service and stays unreachable for custom hosts, the admin dialog re-checks that the update can still start before launching it and drops the confirmation when it cannot, the bare-metal installer refuses symlinks anywhere inside a build directory, restores config.js with --remove-destination and bounds every health probe, and the bare-metal docs add the single sudoers rule before the upgrade command
2026-09-03 05:04:30 -07:00
Matthew Meszaros
bb35de0455
feat: address review on self-hosted updates: the updater no longer re-locks its mutex when a job finishes (every job used to deadlock at completion and freeze the status API), the backend caches the updater view so the member version pill, the health checks and the admin poll share one read and an absent updater is reported as not running rather than broken, the bare-metal upgrade builds unprivileged and hands off to a root-owned fixed-path installer that refuses symlinks so sudoers allows one command instead of install/cp/rm/chown/chmod/systemctl/ln, the installer fails when the backend does not come back, the seed image gets the version build args, the dashboard gates the update action on manage_settings and stops polling a backend that answers 404, and revived timestamps are typed as Date
2026-09-03 05:04:30 -07:00
Matthew Meszaros
b2ea1f1961
feat: add self-hosted update awareness and one-click updates: every binary is stamped with its version and commit, the backend polls GitHub Releases and a new host-side updater (cmd/updater, compose profile or systemd unit) reports the checkout's commit distance, the admin panel's top bar shows a version pill that turns into an update indicator and opens a dialog with confirmation, live step progress and log, restart tracking and result, the dashboard header shows the same pill to every member of a self-hosted instance with the full update flow for platform admins, Setup and health gains update_available and updater_unreachable checks, warmblyctl status prints the version, make upgrade and scripts/upgrade-bare-metal.sh cover the by-hand paths, and docs gain an Updates page plus configuration, health, deployment and API reference updates
2026-09-03 05:04:30 -07:00