Commit Graph
733 Commits
Author SHA1 Message Date
Matthew Meszaros 3591d64404 Merge remote-tracking branch 'origin/main' into fix/issue-401 2026-09-09 08:58:08 -07:00
Matthew Meszaros 16261de4cb Merge remote-tracking branch 'origin/main' into fix/issue-401 2026-09-09 08:51:02 -07:00
Matthew Meszaros 68b5d8f358 feat: bind a campaign lead to the mailbox that sends its first email so every follow-up leaves from the same address, holding a lead back while its mailbox is merely out of budget or outside its hours and moving it to another mailbox only when that one can no longer send for the campaign at all 2026-09-09 08:51:00 -07:00
Matthew Meszaros 3153f9ff23 Merge remote-tracking branch 'origin/main' into fix/self-hosted-unsubscribe-domain 2026-09-09 08:44:13 -07:00
Matthew Meszaros cc50f1d026 feat: contain the signature editing surface so a stored inline position:fixed covers its own box instead of a teammate's dashboard, since the visual editor mounts that markup with innerHTML and forcing the source view for every inline style would take the visual editor away from almost every real signature, and stop the editor's table rule overriding a template's own width attribute, which edited a 600px design at full container width and discarded its column widths 2026-09-09 08:33:46 -07:00
Matthew Meszaros 035865e5c5 Merge remote-tracking branch 'origin/main' into feat/issue-393-html-css-email 2026-09-09 08:30:52 -07:00
Matthew Meszaros f5eeac7b2a feat: address the CodeRabbit review by carrying every stretch of stylesheet the CSS parser cannot read through as a verbatim item, since the sheet is rewritten from parsed items the moment any rule inlines and an unrelated match was deleting the rest, keeping a link's destination out of the content score now that the text renderer emits it so a CTA pointing at a free-trial page stops costing eight points, promising inlining in the editor only for a stylesheet that is actually eligible for it, skipping the client notes entirely for a plain-text campaign that ships no HTML part, switching a step into HTML mode when a template replaces its body with document markup rather than waiting for the next visual edit to gut it, accepting a pasted background shorthand only when it is a single colour so Word's "yellow none repeat scroll" stops becoming an invalid longhand, listing in SCHEMA_TAGS only the tags the mounted schema actually keeps so the warning fires for h1, font, center, thead and caption instead of staying silent while they are dropped, and correcting the guide's byte-for-byte claim and its unconditional plain-text claim 2026-09-09 08:30:49 -07:00
Matthew Meszaros 6ebf9cfdcf Merge remote-tracking branch 'origin/main' into fix/self-hosted-unsubscribe-domain 2026-09-09 08:24:04 -07:00
Matthew Meszaros 538dc29575 feat: stop the CSS inliner deleting the half of a selector list it cannot take over, so a rule like ".btn, .other:hover" keeps its declaration for the hover half after the first inlines, leave a rule whose selector list cannot be split on commas exactly as the author wrote it because a comma inside :is() or an attribute value belongs to the selector and rejoining the pieces writes a different one, keep a rule that matches nothing today rather than dropping it, and declare the paste-adoption ref above the editor that closes over it as the other paste-time refs already are 2026-09-09 08:20:23 -07:00
Matthew Meszaros cdbdad09ee feat: stop the unibox conversation list resetting to the top when an email is opened, by keying the app shell's route boundary and Suspense on route identity rather than the raw pathname (the open thread is a path segment, so every click was remounting the whole page), remembering each list's scroll offset across remounts and the mobile display:none pane, patching mark-as-read into the query cache instead of refetching every loaded page, and auto-loading the next page at the end of the list (issue #396) 2026-09-09 06:52:57 -07:00
Matthew Meszaros 7f2bfa252e feat: hold the campaign preview request until writing settles, since HTML mode runs it from the Edit tab and a designed body is tens of kilobytes to ship on every 250ms pause, and treat a document sitting behind a leading comment as one when deciding whether to adopt a paste as HTML or to open an A/B arm as markup, because a saved email opens with the tool that wrote it before its doctype 2026-09-09 06:39:23 -07:00
Matthew Meszaros 426ea45013 feat: full HTML and CSS support for campaign bodies and mailbox signatures, adding a send-time CSS inliner and a structure-aware plain-text renderer in internal/pkg/mailhtml, persisting HTML mode on a step through the previously unused sequences.body_code so a designed email is no longer gutted by the editor schema on reopen, widening the TipTap schema to hold tables, div containers, colours, fonts, sizes and alignment with toolbar controls for each, folding alignment into a single style producer because TipTap merges two style attributes by splitting on the first colon and truncated background-image url(https://...) to url("https"), replacing the regex plain-text stripper that put a whole stylesheet into the text/plain part and cost a content score eight points for a .free-trial-banner class, locating </body> past Outlook conditional comments so the signature and opt-out footer stop landing inside one, forcing the signature source view for markup a contentEditable cannot host safely, and reporting per-client compatibility findings from the preview endpoint 2026-09-09 06:34:47 -07:00
Matthew Meszaros 7d58b874b8 feat: keep every recipient-facing and self-host-facing address on the deployment's own domain: mint unsubscribe links on a workspace's verified tracking domain (served by the tracking service, proxied to the backend that owns the pages), attach RFC 8058 one-click only over https, resolve all branding through config.Brand() gated on SelfHosted() so a self-host's email footer, sign-in links, stats card, API example and public form badge name nobody else, drop the app.warmbly.com fallback from AppBaseURL, blank TRACKING_DOMAIN and FORMS_DOMAIN on core-only installs, and have install.sh offer to configure a fresh interactive install instead of silently defaulting to localhost 2026-09-09 06:34:43 -07:00
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 c8042e2942 Merge remote-tracking branch 'origin/main' into feat/campaign-entry-delay 2026-09-08 09:58:38 -07:00
Matthew Meszaros c35495ae5c feat: await the trigger card's recovery refetch inside the same promise chain as its saves, so a refused entry-delay commit's invalidation cannot resolve after the next commit's optimistic write and put the stale delay back on the card 2026-09-08 08:34:20 -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 78dd032a06 feat: address the CodeRabbit review on the contacts pagination PR by refusing a cursor boundary the SQL cast would choke on so a hand-made token is a 400 instead of a database error surfacing as a 500, writing timestamp boundaries with an explicit to_char pattern the Go validator mirrors so a token never depends on the server DateStyle, basing the full-screen error guard on rows loaded rather than rows left after the client-side subscription filter, keeping the failure visible when that filter hides every loaded row, and pointing Try again at the request that actually failed by reading isFetchNextPageError instead of guessing from hasNextPage 2026-09-08 06:53:57 -07:00
Matthew Meszaros 03b6188364 feat: tighten the import result's failed-pin warning so it contrasts the two outcomes instead of joining them with an and that reads as if both succeeded 2026-09-08 06:36:34 -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 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 99c4ec9e21 feat: answer the CodeRabbit review on #386 by making ContactImportResult.SegmentsPinned a *bool so omitempty stops collapsing a failed membership write into the same absent value as an import that asked for none, warning in the result step when a pin did not land instead of leaving it to a note under a green Import complete, holding the pinned-contacts truncation notice until the overrides listing has actually arrived so it cannot read the newest 0 of 5,000 while it loads, and pointing that notice at the contact drawer, since a pinned-out contact can never be reached from the member list it used to name 2026-09-08 05:19:03 -07:00
Matthew Meszaros 4e5a6018fc feat: keep the entry-delay field from re-normalising its own keystrokes, so typing 60 in the minutes box no longer flips to 1 hours under the cursor, and bound the typed amount per unit so switching 45 minutes to days shows the 90 days it is actually clamped to 2026-09-08 05:17:00 -07:00
Matthew Meszaros 8692b1cb52 feat: give the entry-delay picker its own draft state with a separate onCommit callback, so the flow canvas's trigger card saves a typed custom amount on blur, Enter or a stepper click instead of firing one PATCH per keystroke, while the Schedule tab and wizard keep batching every edit into their Save button 2026-09-08 05:11:14 -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 16349852cc feat: fix issue #381 by scoping the import and sheet-sync entry points on a segment's member list to that segment, so a CSV uploaded from inside a segment pins its rows in instead of creating contacts that are nowhere in it, and round the page out with an empty state offering Add contacts, Import file and New contact plus a pinned-contacts panel that says when it is showing only the newest slice of a large import 2026-09-08 04:59:25 -07:00
Matthew Meszaros 5f665a3208 feat: teach the contact import wizard a lockedSegment that always travels with the upload, named in the header and as a fixed always-applied row above the segment picker, confirmed on the result step from the API's segments_pinned flag, and refresh the segments query alongside contacts so the page behind the wizard is right before the realtime spine event lands 2026-09-08 04:59:25 -07:00
Matthew Meszaros c9781a1c57 feat: surface the sheet-sync segment targets in the dashboard: an Add to segments picker on the sync wizard's options step and in the source edit drawer, a lockedSegment the wizard applies on every run and shows as a fixed row, and a SyncSourcesPanel that scopes its list and its New sync to the segment it was opened from 2026-09-08 04:59:18 -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 25484f70ab Merge remote-tracking branch 'origin/main' into feat/issue-357-loopback-mail-relays 2026-09-07 09:05:11 -07:00
Matthew Meszaros d48d57464b feat: hand the SMTP auth negotiation the normalized host, because net/smtp records the name NewClient was given as the server name and PlainAuth refuses to authenticate when its own host does not match it, so a bracketed IPv6 literal failed on an address it was correct about, and stop the bulk CSV calling an unencrypted row invalid while the deployment config is still loading, where a self-hosted instance would have accepted it 2026-09-07 08:44:32 -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 a5802b53cf feat: address the review on the unsubscribe-link change by closing the tag scanner over quoted attributes in both the send path and the editor, so a > inside an attribute can no longer split a tag and rewrite the href that follows it into a dead link, and by scanning only the copy a plain-text step actually ships, resolving the campaign's inherited opt-out mode before warning, checking the subject too, and reporting an unreadable step list as a failed check rather than an empty scan 2026-09-07 06:34:45 -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 26c88d2426 feat: address the CodeRabbit review on the PostHog PR by dropping a referrer that is not a hostname instead of storing its query string or fragment, redacting email-shaped values out of every acquisition field before they reach the database or an analytics property, counting an invited signup which returned before the count was taken, hanging subscription_started off the persisted trial-to-paid transition so a redelivered webhook cannot report a second start, capturing the validated provider in both OAuth mailbox paths including Warmbly Cloud, making the two acquisition toggles mutually exclusive and naming them after what they actually select, and replacing the unsupported CNIL consent-exemption claim with what the guidance says and an explicit note that qualifying is a deployment-specific assessment this document does not make 2026-09-07 05:45:11 -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 8f604921ab Merge branch 'main' into fix/issue-343-behaviour 2026-09-07 04:49:48 -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 f1b6345337 feat: address the CodeRabbit review on the mailbox error lifecycle PR by bounding the transient-error resolution to failures raised before the sync pass ran, because JetStream is configured with MaxDeliver and no MaxAckPending so a redelivered older success could otherwise clear a newer outage and leave the mailbox looking healthy until the next distinct one, naming the archive folder in the drawer's folder note alongside the others the cap always keeps, and giving the duplicate-folder note a singular verb 2026-09-07 04:36:50 -07:00
Matthew Meszaros d51501bef1 Merge branch 'main' into fix/issue-343-behaviour 2026-09-07 04:36:42 -07:00