Matthew Meszaros
|
1e1bb3622e
|
feat: address the CodeRabbit review on the email editor PR by generating email image object keys from a uuid and the sniffed extension instead of the uploader's filename so a name carrying '..' cannot produce a key the public route refuses to serve and a private filename never travels inside a recipient's URL, deleting the stored object before its row and refusing the request when storage will not take it so a failed delete leaves nothing loadable that the quota has stopped counting, accepting only https image addresses, resynchronising the HTML source view when a template replaces the body underneath it, and rendering the campaign preview through the inbox's sandboxed frame so markup written in the source view can neither run nor restyle the dashboard for a teammate
|
2026-09-08 21:17:36 -07:00 |
|
Matthew Meszaros
|
9ce1e060dd
|
feat: fix the campaign step body editor for issue #380 by registering TipTap's UndoRedo and HardBreak so Ctrl+Z and Shift+Enter work at all, normalising pasted HTML from Gmail/Outlook/Word so their blank-line scaffolding stops rendering a second gap on top of our paragraph margins, adding a workspace email image library (migration 000139, public email-images/ objects sharing the attachment storage quota, keyset-paginated /email-images endpoints) with toolbar upload, URL and library insertion, drag-drop, clipboard paste and a size/alignment/alt bubble, and adding an HTML source view that names the tags the schema would drop before switching back
|
2026-09-08 21:00:09 -07:00 |
|
Matthew Meszaros
|
0e7525866a
|
feat: merge main into the select-all branch and rebuild the contact selection on top of its new keyset pagination, dropping this branch's own cursor repair now that Search carries the sort key and boundary value in a SortCursor, re-extracting the shared WHERE builder from that version, and pointing SearchIDs at the contactSorts spec and the conditional campaign-count lateral so a resolved selection orders and joins exactly the way the list does
|
2026-09-08 20:26:34 -07:00 |
|
Matthew Meszaros
|
c7a44ad909
|
feat: complete the CRM push 400 description in the OpenAPI spec with the shared resolver's own failures, a select-all carrying no filters and a filter matching more than fifty thousand contacts, so the published contract matches every way the endpoint can refuse a selection
|
2026-09-08 20:02:26 -07:00 |
|
Matthew Meszaros
|
1432e7c459
|
feat: publish the exclusion-list bound that ResolveSelection enforces by giving every OpenAPI exclude field maxItems 50000 and naming too_many_contacts in the bulk update, bulk delete and CRM push 400 descriptions, and note the cap on the selection table in the contacts API reference
|
2026-09-08 19:50:46 -07:00 |
|
Matthew Meszaros
|
c8042e2942
|
Merge remote-tracking branch 'origin/main' into feat/campaign-entry-delay
|
2026-09-08 09:58:38 -07:00 |
|
Matthew Meszaros
|
37ed967919
|
Merge remote-tracking branch 'origin/main' into fix/issue-382-user-experience
|
2026-09-08 08:32:31 -07:00 |
|
Matthew Meszaros
|
4e52e9476b
|
Merge remote-tracking branch 'origin/main' into feat/campaign-entry-delay
|
2026-09-08 07:31:31 -07:00 |
|
Matthew Meszaros
|
f8c0899be4
|
feat: address the CodeRabbit review by promoting a rounded wait into the next unit so a contact drawer never reads 60 minutes or 24 hours, describing the entry delay's anchor as the lead's own enrolment moment with the campaign's creation date only as the legacy fallback in both the campaigns and segments guides, replacing the launch dialog's 'sending begins immediately' line when a delay is set, following an outside value change with the picker's preset or custom mode, and chaining the trigger card's saves so two commits inside one round trip cannot land out of order
|
2026-09-08 07:30:24 -07:00 |
|
Matthew Meszaros
|
0f83329936
|
feat: answer the CodeRabbit re-review by guaranteeing the promise the failed-pin warning makes: an import-level note now goes to the front of the error list and is never dropped by the per-row cap, so a file full of bad addresses cannot bury or evict the one note explaining why the rows that did import are not in the segment they were imported into, with a single note however many segments were targeted and a live test that forces the write to fail behind a capped error list
|
2026-09-08 06:54:36 -07:00 |
|
Matthew Meszaros
|
177e36265d
|
Merge remote-tracking branch 'origin/main' into fix/issue-381-user-experience
|
2026-09-08 06:47:37 -07:00 |
|
Matthew Meszaros
|
a453573c7b
|
feat: finish the OpenAPI half of the contact selection change by rewriting the bulk delete, bulk update and CRM push operation and 400 descriptions for the filter form and its selection_too_large and empty-match failures, and encoding the two valid bodies as a oneOf so a generated client cannot send an empty object, a select-all with no filters, or an id list and a filter at once
|
2026-09-08 06:17:36 -07:00 |
|
Matthew Meszaros
|
f46728733e
|
feat: address the CodeRabbit review on the select-all PR by tying the header checkbox to the rows on screen and making it put unticked rows back before it clears a select-all, bounding the exclusion list before it sizes a map, skipping half-filled custom-field pills when a filter is saved as a segment so the segment matches the search, and naming the fifty-thousand selection cap on the campaigns and segments guides
|
2026-09-08 05:39:16 -07:00 |
|
Matthew Meszaros
|
c59446271b
|
Merge remote-tracking branch 'origin/main' into feature/bulk-select-all
|
2026-09-08 05:20:02 -07:00 |
|
Matthew Meszaros
|
a6862477b3
|
feat: add a 'select all matching' selection to every contact list so a bulk action can cover the whole filtered set instead of only the loaded pages: a ContactSelection body (all + filters + exclude) on bulk update, bulk delete, verification, batch research, segment members and CRM push resolved server-side through a new SearchIDs that shares the search's WHERE builder, a Gmail-style banner and shared selection module in the contacts table, campaign Leads and segment members views, an uncapped select-all in the From contacts picker, and fixes for the keyset cursor comparing a row against itself (contact pagination repeated and skipped rows past page one) and the dashboard sending custom-field filters under the wrong wire name
|
2026-09-08 05:19:59 -07:00 |
|
Matthew Meszaros
|
846d691768
|
feat: write the campaign's 'no step is due yet' activity line once a day instead of on every deferred wake-up, and name the entry delay in it when the campaign holds its first emails, so launching a delayed campaign explains itself instead of filling the feed with two hundred identical lines
|
2026-09-08 05:09:37 -07:00 |
|
Matthew Meszaros
|
ed50ad9f2f
|
feat: add a campaign entry delay so a contact's first email can wait a set time after they enter the campaign, with campaigns.entry_delay_minutes and a campaign_leads.added_at anchor (migration 000136), the delay applied in the router's per-lead due check and floored into the placer through ContactSequencePair.NotBefore, a distinct entry_delay constraint in the contact next-action preview, and the control surfaced on the Schedule tab, a new Trigger card at the top of the Steps canvas, the campaign wizard's Schedule step, the launch dialog, the update_campaign AI tool and the iOS schedule page, plus guides, API reference and live scheduler and repository tests
|
2026-09-08 05:06:04 -07:00 |
|
Matthew Meszaros
|
55894a0e9d
|
feat: fix the campaign Leads tab and contact export stopping partway through a large list by rebuilding the contacts keyset cursor: the boundary subquery named the outer row's alias so Postgres read it as a correlated self-reference and the whole comparison collapsed to c.id >= <cursor uuid>, serving each page the newest rows of a randomly shrinking id range until it ran out at a third of the leads, so the opaque token now carries the ordering it was taken under plus the boundary row's own sort value and id, rejects a token replayed under another sort, and pairs with a direction-following id tiebreak, an ORDER BY that names its NULL placement, a new (organization_id, created_at DESC, id DESC) index, EXISTS campaign and category filters and a lateral campaign_count the query only joins when a filter or sort asks for it, taking a page of a 50k-contact organization from 120ms to 3ms, and the dashboard now keeps the rows it already loaded when a later page fails and says how far through the list Load more is
|
2026-09-08 05:01:15 -07:00 |
|
Matthew Meszaros
|
b5fc5b2974
|
feat: document importing into a segment: the file wizard and a Google Sheets source both inherit the segment they were started from, a condition-less segment is the shortest way to turn a file into an audience, and the API reference gains segment_ids on lead-sync create and update, the ?segment_id= list filter and segments_pinned on the import result
|
2026-09-08 04:59:31 -07:00 |
|
Matthew Meszaros
|
800c4a062f
|
feat: frontend half of the admin panel upgrade: drop ten unused dependencies, the dead worker-load and plan requests, the stub pages and the retired permission names, fix the analytics client that sent the wrong query parameters and the mail test toasts that used an unmounted toaster, add a Cmd-K command palette with live user, organization, mailbox and worker search, a mobile nav drawer, document titles, a Sentry-wired route error boundary and permission gates on every route, replace polling with the realtime spine wherever an event exists, fold Analytics into Overview with a signups-by-channel card, merge System Status into Setup and health and Settings, Notifications and Effective limits into Configuration as tabs with redirects from every old path, add the Sync, Sends, Jobs, Fleet, Admins and Transfers pages plus API-key, webhook and transfer tabs on the organization page, mailbox reassignment on the worker page and abuse and action-history tabs on Warmup, and add the admin-panel docs page with every nav reference updated
|
2026-09-07 21:40:38 -07:00 |
|
Matthew Meszaros
|
156a90c39a
|
feat: default every PostHog capture host (site build, dashboard runtime, backend analytics client) to PostHog Cloud US instead of EU, and update the configuration docs table, since the customers are primarily US-based
|
2026-09-07 20:28:31 -07:00 |
|
Matthew Meszaros
|
de4b1e3aff
|
Merge remote-tracking branch 'origin/main' into fix/issue-371-public-images
# Conflicts:
# site/public/install.sh.sha256
|
2026-09-07 09:13:22 -07:00 |
|
Matthew Meszaros
|
6542047745
|
Merge remote-tracking branch 'origin/main' into fix/issue-371-public-images
|
2026-09-07 09:07:22 -07:00 |
|
Matthew Meszaros
|
25484f70ab
|
Merge remote-tracking branch 'origin/main' into feat/issue-357-loopback-mail-relays
|
2026-09-07 09:05:11 -07:00 |
|
Matthew Meszaros
|
c4bfbaf4a9
|
feat: address the review on the image publicity gate by passing build-push only the seven services that workflow actually publishes, since web, admin and cli have no :dev tag and would have warned falsely on every push to main, by taking the release tag through the step env instead of interpolating github.ref_name into the shell, and by no longer asserting the tag is fine when a pull is refused, because GHCR denies an unknown namespace exactly as it denies a private one, so a mistyped --registry now gets its own reading in the installer message, the troubleshooting table and the fork note, which also splits the personal and organization paths to the visibility setting
|
2026-09-07 09:02:23 -07:00 |
|
Matthew Meszaros
|
719a81866c
|
feat: gate the release on every published image being pullable with no credentials, because GHCR creates each package private and does not inherit the repository's visibility, so the authenticated imagetools inspect in create-release passed for four releases while ghcr.io/warmbly/warmbly/* returned unauthorized to everyone outside the org and no curl | sh self-host install could pull a byte (#371); adds scripts/check-images-public.sh as the anonymous pull test, makes it the gate before create-release and the source of the digests in images.json, warns from build-push on main where a new service image first appears, and teaches install.sh to report a registry refusal as one instead of as a missing tag
|
2026-09-07 08:51:57 -07:00 |
|
Matthew Meszaros
|
63070fb833
|
feat: address the review on the loopback mailbox mode by building every mail address with net.JoinHostPort, so an IPv6 literal keeps the brackets a host:port string needs and "::1" on 1143 stops dialling a host called "::1:1143" with no port, by moving the self-hosted half of the rule into the dialers as well as the connect form, because an organization archive exported from a self-hosted instance carries its mailboxes and an import must not hand a hosted worker one that dials its own loopback in the clear, by refusing an unencrypted CSV row on a hosted instance where the API would only reject it a moment later, and by saying in the docs that the port never selects the mode and that the whole 127.0.0.0/8 range counts
|
2026-09-07 08:27:54 -07:00 |
|
Matthew Meszaros
|
769a05aa90
|
feat: identify an IMAP folder by its name rather than by its UIDVALIDITY, which RFC 3501 never promised was unique across folders, so a mailbox on a server that stamps that number with the folder's creation time no longer loses the entire sync of every folder in a tree created in the same second, with the folder row keyed on (email_id, mailbox), each stored message stamped with its folder's name alongside the UIDVALIDITY generation its uid belongs to, a rename followed as a move that carries the mail and the cursor instead of orphaning both, and a changed UIDVALIDITY treated as what it is, the cursor going void
|
2026-09-07 08:21:17 -07:00 |
|
Matthew Meszaros
|
766bd3ae6a
|
feat: let a self-hosted instance connect a mail server on its own machine by adding a third mailbox security mode, "none", accepted only for a loopback literal and only where the worker shares a host with the relay, so Proton Bridge on 127.0.0.1:1143/1025 and a local Dovecot or Mailpit can be connected at all, with the rule enforced in onboarding and reauth validation, again by the worker against the peer it actually dialled rather than the name it was given, and hidden from the connect form on the hosted product where the worker is never the customer's machine
|
2026-09-07 08:07:40 -07:00 |
|
Matthew Meszaros
|
b25897fe67
|
feat: render a hand-placed {{.UnsubscribeLink}} as a real anchor labelled with the workspace's unsubscribe link text instead of shipping the bare signed API URL in the body, let the composer turn a text selection into that link so the wording stays the author's, and warn in the step editor and at preflight when a plain-text-only campaign carries a body opt-out link that has nowhere to hide its address
|
2026-09-07 06:34:45 -07:00 |
|
Matthew Meszaros
|
ab62f24009
|
Merge main into the PostHog branch after the Sentry work landed
|
2026-09-07 05:25:47 -07:00 |
|
Matthew Meszaros
|
103fa00b55
|
feat: state in the configuration reference and the troubleshooting guide that a hosted form page takes its error-reporting environment from the forms service's APP_ENV, stamped into the page, rather than from WARMBLY_SENTRY_ENVIRONMENT like the dashboard and the admin panel do
|
2026-09-07 05:19:24 -07:00 |
|
Matthew Meszaros
|
b204737a05
|
Merge the Sentry branch (with main) into the PostHog branch
|
2026-09-07 05:01:38 -07:00 |
|
Matthew Meszaros
|
1239f1d191
|
Merge main into the Sentry branch, routing the new smtp reporting through the errs wrapper instead of the SDK import main added
|
2026-09-07 05:00:43 -07:00 |
|
Matthew Meszaros
|
04dc83da38
|
Merge the Sentry review fixes into the PostHog branch, keeping the new JavaScript escaping for the PostHog runtime-config values too
|
2026-09-07 04:48:30 -07:00 |
|
Matthew Meszaros
|
f043bfc761
|
feat: address the CodeRabbit review on the Sentry PR by escaping every runtime-config value the web and admin entrypoints write into a JavaScript string literal so a quote or newline in a DSN cannot inject or discard the whole config, reporting only the count and types of a failed query's parameters instead of their values now that the db scope actually applies, cloning the fallback hub in errs.Hub so scope cannot leak between requests, flushing the tracking service before every fatal exit since process::exit skips the guard's drop, treating a blank WARMBLY_RELEASE or APP_ENV as unset in realtime, stamping the environment and a service tag into the forms page, and correcting the docs claim that an unset DSN means the SDK is never initialised
|
2026-09-07 04:47:51 -07:00 |
|
Matthew Meszaros
|
4582896f54
|
merge
|
2026-09-07 04:43:45 -07:00 |
|
Matthew Meszaros
|
cebcd2cc83
|
merge
|
2026-09-07 04:37:07 -07:00 |
|
Matthew Meszaros
|
6bfba363d3
|
feat: address the CodeRabbit review on the SMTP compatibility PR by carrying the server's own refusal in a rejected-recipient error so an address that no longer exists is distinguishable from one a policy blocked, classifying our own refusal to authenticate over an unencrypted link as the configuration problem it is rather than retrying it four times as an outage against a server that is answering fine, and fixing the fake server's canned refusal at construction so the session goroutine and the test no longer race on it under go test -race
|
2026-09-07 04:30:04 -07:00 |
|
Matthew Meszaros
|
d00960ad59
|
Merge feat/sentry-everywhere (with main) into the PostHog branch
|
2026-09-07 04:26:17 -07:00 |
|
Matthew Meszaros
|
be16f77f5d
|
Merge main into the Sentry branch
|
2026-09-07 04:25:41 -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
|
1af5ac1ea6
|
feat: let a fixed mailbox problem stop showing as a permanent error (issues #362, #363): resolve a mailbox's connection errors on the first sync pass that reaches the server again, because nothing but a credential reconnect ever resolved an error row, so a five-minute outage left a red needs-attention on the mailbox for good and held its health at warning; carry what the folder listing had to skip as sync state shown in the drawer's Sync card instead of raising an error row nobody can withdraw, so the note disappears once the mailbox is back under the folder limit or the duplicate folder id is renamed; and keep only a fingerprint per message in the IMAP flag scan rather than the whole flag set and Message-ID, which held 74 MB per mailbox at the window and folder limits against 14 MB for the digests, on a worker whose base capacity is 16 mailboxes
|
2026-09-07 04:24:09 -07:00 |
|
Matthew Meszaros
|
211650af8d
|
feat: send through every kind of SMTP server (issues #359-#361): negotiate the sign-in method from what the server advertises, preferring CRAM-MD5 then LOGIN then PLAIN, because sending AUTH PLAIN blind was refused by every server that offers only LOGIN, which is Microsoft 365 relays and most appliance relays, and that refusal was reported to the mailbox's owner as a wrong password and deactivated the account; put a deadline on the whole SMTP conversation so a peer that stops answering without closing the connection can no longer park a send goroutine forever, which only the dial was protected against; classify a refusal by its reply code so a permanent 5xx on the sender, the recipient or the message is reported as the rejection it is and not retried four times as though the server were offline, while a 4xx still retries; announce the sender's own domain in EHLO rather than net/smtp's localhost, which relays read as a spam signal; and share one AUTH LOGIN implementation with the notification mailer instead of keeping two copies of the code that handles credentials
|
2026-09-07 04:19:42 -07:00 |
|
Matthew Meszaros
|
8e9f67f46b
|
feat: add cookieless PostHog analytics for the hosted marketing site and dashboard with server-side signup and subscription events, a first-party acquisition record written once at signup on a new organization_acquisition table registered in the org-transfer spec, an acquisition column and channel filter in the admin org list, and never a single request from a self-host because every key is unset by default
|
2026-09-07 04:18:33 -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
|
51dedc90ee
|
feat: put every runtime behind one optional error-reporting story: a single internal/observability/errs wrapper that is now the only package importing sentry-go, InitSentry for cmd/forms, release and environment tags on every service from the existing build stamp, optional Sentry in the admin panel and the public forms app, the sentry crate in the Rust tracking service, release tagging in realtime, CI source-map upload that only runs when a Sentry token is configured, and docs covering the DSN for each service
|
2026-09-07 03:51:06 -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
|
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
|
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 |
|