Skip to content

v38.0 - Web Analytics

Latest

Choose a tag to compare

@pierre-b pierre-b released this 17 Aug 08:37
web_analytics_dashboard
  • Feature: Web Analytics — the Staminads feature set, rebuilt on PostgreSQL. A ~21 KB gzipped cookieless browser SDK (/na.js) streams sessions, pageviews and goals into monthly-partitioned tables per workspace, with engaged-time metrics (TimeScore, bounce rate), editable channel-attribution rules (40 defaults, with historical backfill), goals carrying a value and a declared type — purchase, subscription, lead, signup, booking, trial or other, because only your site knows which it is — 10 custom dimensions, geo resolution from a bundled MaxMind GeoLite2 City database, and a console section with Dashboard, Explore, Goals, Filters, Annotations and Settings tabs plus a live view. Visitors can be tied to a contact: NotifuseAnalytics.identify(email, hmac) attaches a verified address to a session — the signature is checked against the workspace secret, so only your own server can mint one — and an address it vouches for becomes a contact if it is not one already, while visitors arriving from a tracked email link are identified automatically with no code. An identified visitor's sessions and pageviews then appear on the contact timeline, any page openable in a side panel, and both navigation and goals are usable in segment conditions, with goals also able to trigger automations (navigation never does). Calling identify() is itself the opt-in: a workspace that wants anonymous reporting simply never calls it, and obtaining consent to store browsing history against a contact remains yours to do. Charts can be annotated — a launch, a price change, an outage — each pinned to the timezone it was entered in, and broadcasts annotate themselves the moment a send starts. An AI assistant answers questions about your own traffic from the same reports the dashboard draws, so its answers and the charts beside them can never disagree; it can also drive the screen — period, comparison, filters, Explore reports — needs an LLM integration configured under Settings → Integrations, and never groups or filters by visitor email address. The v38 migration adds the tables to existing workspaces and grants the new web analytics permission to existing members and pending invitations; the feature is off until enabled in a workspace's Web Analytics settings. For high-traffic installs, compose.alloydb.yaml runs the same schema on AlloyDB Omni's columnar engine.

  • Improvement: A sent broadcast links straight to its web analytics reports. Two icons in the broadcast's header open the Explore and Goals tabs filtered to that broadcast's UTM campaign over its send window plus the following week, and every variation row carries the same pair narrowed to its own UTM content — so an A/B test's two creatives can be compared by what they did on the site, not only by opens and clicks. The links need a UTM campaign on the broadcast: without one they stay disabled, because filtering on an empty campaign matches every untagged visit rather than none. A broadcast that sets its own UTM content disables the per-variation links for the same reason, since every variation then ships that one value instead of its template's.

  • Improvement: The General and Blog workspace settings now use the same floating save bar as Web Analytics. It appears only once something has changed, offering Discard and Save Changes plus a Cmd/Ctrl+S shortcut, and leaving the section with unsaved edits asks for confirmation instead of dropping them silently — so the save control is no longer stranded below the fold of a long form. Pressing Enter in a field no longer submits these forms; save from the bar or with Cmd/Ctrl+S.

  • Improvement: Saving blog settings no longer erases the SEO canonical URL. The console never loaded the stored value into the form, so every save sent it back empty and the blog's <link rel="canonical"> tag disappeared. The field is now populated from the saved settings.

  • Improvement: Tracked email links are no longer altered or missed. Appending UTM parameters rewrote the entire query string — dropping pairs Go's URL parser rejects (?sid=1;2, ?discount=50%off) and re-escaping the rest — while uppercase <a HREF="…"> was skipped altogether, leaving those links with no click tracking, UTM parameters or identity token. Links are now matched case-insensitively and left byte-for-byte as authored apart from what Notifuse appends; per-link click reporting groups by the rewritten URL, so an affected link starts a new row from this release.

  • Improvement: analytics.query no longer answers an empty breakdown with one invented row. A grouped query that matched nothing returned a single row with an empty dimension and zero measures, which reads as real data and hides a table's own empty state. Ungrouped totals still answer zero, because a KPI has to render a number.

  • Improvement: A sign-in link that carries an email (/console/signin?email=…) no longer loses it on the first click. A stale token in the browser makes the console's opening user.me call fail, and the 401 handler redirected to a bare /console/signin — discarding the query string that page needs — so the visitor got an empty form and only the second click worked, once the token had been cleared. The redirect is now skipped when the browser is already on the sign-in page.

  • Improvement: The console renders Ant Design's own strings in Japanese, Italian and Brazilian Portuguese. All three were offered in the language picker and their translations shipped, but the map that hands a locale to Ant Design covered only five languages, so date pickers, table filter and sort menus, pagination and empty states silently stayed English for them. Notifuse's own strings were translated throughout, which is why this read as a half-translated screen rather than a missing language.

  • Improvement: Listing, editing and deleting transactional notifications now require the Transactional permission. The three endpoints checked workspace membership only, so a member whose transactional access had been revoked could still read the notifications, repoint one at a different template and delete it — the console offered the permission and the API ignored it.

  • Improvement: analytics.query now covers the whole of a date range's last day. A timeDimensions range ending on a bare date stopped at that day's midnight, so a report ending today showed nothing for today and a single-day range came back empty — the console compensated by asking for tomorrow instead. Bounds given as an explicit timestamp are still used exactly as sent.

  • Improvement: Integration credentials are no longer sent back to the browser. Editing an integration and leaving a credential field blank now keeps the stored one, so only a value you actually type replaces it.

  • Improvement: A send that fails no longer stores its template data in clear. Recording the failure rewrote the whole message history row from the copy held in memory, which still carried the plaintext variables, overwriting the blob that was encrypted when the row was created — magic links, confirmation tokens and API keys included. Transactional and double opt-in sends were affected; broadcasts and automations record failures differently and were not. Rows already written that way are left as they are.

  • Improvement: Deleting a contact now erases every copy of their address. Message history and inbound webhook events had their identifying column replaced with DELETED_EMAIL while the address remained inside the stored payload on the same row — both of which are returned by their list endpoints — and custom events, segment memberships and segment-queue entries were not cleaned at all.

  • Improvement: Deleting a contact now cancels email already queued to them. Queued messages carry the address independently of the contact record, so a broadcast or automation send that had not yet drained would still arrive after the contact was erased.

  • Improvement: A List Status node now renders in an automation's Flow Stats. It appeared there with no icon and no title, and all three of its branches were dropped, so everything downstream of it floated on the canvas disconnected from the flow it belongs to.

  • Improvement: Text that had gone missing from three screens is back, in every language. Broadcast status badges and their tooltips rendered blank, the automation Add node menu listed raw keys like list_status_branch instead of node names, and the toast shown when single sign-on refuses a login carried no message at all. Each passed its translation function into a helper, which silently disabled it and left those strings resolving to nothing — translations most of them already had.

  • Feature: Automation triggers can filter who enters. A trigger now takes entry conditions — the same builder segments use, over contact properties, list memberships, past activity and goals — so an event enrolls only the contacts that match. Set them in the trigger panel under Entry conditions. A contact who does not match is never enrolled, which differs from a Filter node after the trigger: that enrolls first and then exits, and with Once per contact it uses up the contact's single entry. A count of past activity includes the event that fired the trigger, so "opened at least 3 emails" enrolls on the third open.

  • Improvement: Every automation node takes an optional description, not just the Filter node. It appears under the node's title on the canvas and in Flow Stats, so a flow of Email and Delay cards explains itself without opening each node. Removing a condition tree now asks for confirmation first.

  • Improvement: Undo in the automation editor now works in whole edits rather than single characters. Typing in a text field — a webhook URL, a custom event name, a node description — recorded a step per keystroke, so Cmd+Z crawled back one letter at a time and a long edit could push earlier node and connection changes out of the 200-step history. A run of typing in one field is now a single step.

  • Improvement: An automation's status and the database trigger that enrolls contacts for it are now kept in agreement. They were saved separately, so an automation could keep enrolling after being paused or deleted, or show Live and enroll nobody; enrollment now checks that the automation is live, and two people acting on the same automation at once no longer silently undo each other.

  • Fix: In the Blog and Email AI assistants, a reply that only used a tool and wrote no text left the conversation in a state the AI provider rejected, so the next message failed. The conversation is now repaired before it is sent.

  • Fix: user.me answers 500 instead of 401 when it cannot verify a session at all. A transient database error was reported as an expired session, and the console deletes the stored token on any 401 — signing people out over a blip.

  • Fix: Duplicate rows are recognised on a PostgreSQL server running a non-English locale. They were detected by matching the driver's English error text, which PostgreSQL translates according to lc_messages — so on a French or Japanese server single sign-on reported an account already linked to another identity as a generic “sign-in failed, please try again”, re-running the setup wizard failed instead of accepting the root user it had already created, and creating a template whose id was already taken answered a bare 500 rather than saying which id clashed. Detection now keys off the SQLSTATE, which no locale changes.

  • Fix: The transactional notification endpoints now answer 403 when a member lacks permission, instead of a 500 — or, when the denial came from the template check, a 400 blaming the template (invalid template for channel email: Insufficient permissions…). transactional.testTemplate went further and answered 200 with the denial buried in the response body; it also answers 404 again for a template that genuinely does not exist.

  • Fix: The blog feed no longer compresses its response for a client that refused compression. Accept-Encoding: gzip;q=0 is an explicit refusal, and the feed read it as consent.