Analytics and notifications

11 min read

The dashboard, the per-feature numbers, and the push notifications a staff browser can receive.

Analytics

Social analytics is separate from Dashboard → Analytics. Administrators and publishers with the connected account's view_results permission can view post results. The analyst role does not grant access to Social. Accepting a publisher invitation allows access to Social; an administrator must still assign the publisher's actions for each connected account.

Dashboard → Analytics reports site traffic, acquisition, audience, content, commerce, and events. Choose a date range of 7, 30, 90, or 365 days; figures compare that period with the preceding period. The audience filter applies to Analytics and Content analytics. When another site reports to this analytics instance, use Source to select it; see the analytics script. Select a row to add a filter chip for its channel, referrer, campaign, entry page, device, browser, operating system, country, or language. Chips combine, appear in the URL, and can be removed individually or cleared together. The On the site now count includes browsers with an event in the last five minutes. Analytics has eight tabs; the selected tab is remembered in that browser:

  • Overview — visitors (distinct browsers), page views, sessions, views per visit, bounce rate and session length, each against the window before; the goals — sign-ups, and with their modules purchases and memberships, and every event the site registers as a goal in its config (analytics.events) — each as a count, its rate per 100 visitors and the change (and, where a conversion was made with no session on record, how many were); visitors, views and sessions a day; pages per session, signed-in people, content opened, new and returning.
  • Acquisition — the channels: Direct · Search · Social · Email · AI · Referral (told once, in the database, from the referrer and the UTM tags — a tracked link in a funnel email lands with the funnel's tags, so it is Email, not a referral from the functions' host), each with visitors, sessions, bounce, length, sign-ups, the sign-up rate per 100 visitors, revenue and a column per registered goal; a channel opens to the referrers under it. Then Campaigns (visits by their UTM source, medium and campaign, with the sign-ups they began), Where they land (the first page of a session, with its bounce and sign-ups) and Where they leave (the last page, and its share of all exits).
  • Audience — who they are, as their browsers say it and nothing more: new against returning visitors as one bar; devices, browsers, systems; countries — the country the host's geo header stamps on a session's first event (Cloudflare's, Vercel's, CloudFront's or an x-country a front sets; unknown where there is none, as locally), never from the address itself — beside time zones and languages; guests, signed in and members; when they come, sessions by hour of the day and day of the week in your own zone; and retention — cohorts by the week of a visitor's first visit, each with how many came back in each of the weeks after, as a shaded grid.
  • Content (with the instrument on, Content & the diagnostic) — the most-viewed pages, the most-engaging pages (the average of a read's measured seconds), topic demand, and the diagnostic.
  • Commerce — the store card (quiet while nothing sold); the membership as a business: members now, monthly recurring revenue (a weekly plan normalised to a month), joined and lapsed in the window against the window before, free accounts, ending at period end, members a day, what they read before joining (last touch, as the content page credits) and the channel they joined by, by plan; the ads. Each only with its module on.
  • Events — every event name seen in the window, the site's own and the registered ones (by their labels, a GOAL pill on a goal), with its count, the people who fired it and its share of visitors; a name opens to its count a day and its top properties — an event a script sends under a name the site did not register is kept as sent and listed by that name.
  • Experiments and Explore (any metric on one chart — a registered event among them — by day or week, cumulative, against the previous window, split by channel or by source, a CSV).

Anybody who may see the figures — an administrator or an analyst; the data is refused server-side to everybody else (may_see_figures() in the database, which is what every admin_* read asks). Every series over time, on every analytics page, is drawn as bars unless the person switches it to a line (Bars · Line beside the range control; one choice for all the pages, remembered per browser); a reference series — the window before — stays a dashed line either way.

Analytics sessions store the entry path, referrer and UTM tags, device type, browser and operating-system families, time zone, language, and country when the hosting platform supplies it. They do not store IP addresses or screen dimensions. A browser identifier is derived in the browser where fingerprinting is available; if a visitor opts out or no fingerprint can be computed, the app uses a random identifier stored in that browser. Events from an opted-out browser are still counted, but the random identifier does not identify that visitor across browsers. Events are sent to the site's /api/events endpoint. Other sites can report to the same instance through the site's analytics script or a PostHog SDK configured with that endpoint.

Dashboard → Content analytics is what gets read, how long it holds people and how far they go — the page for whoever runs the content, read the way a publisher's tool reads it, in the dashboard's own idiom (the same range control, audience filter and chips, every figure against the window before), one tab at a time (the tab is remembered per browser):

  • Overview — eight numbers: views, readers (distinct browsers), engaged minutes (the sum of every read's measured seconds), time on a piece (the average), read to the end (the share of reads that reached 90% of the page), went on reading (recirculation: the share of views followed in the same session by another piece), pieces read, signed in; Reading now, the pieces with somebody in the last five minutes; reading over time, views and people a day with the same days before them dashed in grey; Rising and Fading, the pieces gaining and losing most on the window before; Evergreen, pieces published more than a month before the window and still in the top third by views (a piece's publish date comes from its record — a book has none, so it is never listed here); By kind.
  • Conversions — what the content led to: sign-ups, subscriptions, purchases and revenue in the window, each as content-led of all (a conversion is credited to the last piece the person opened in the seven days before it — last touch; one with no piece before it is not content's and counts only in the total), and the pieces that led to them with people, each kind of conversion, revenue, conversions in all and per hundred people, sorted by any column.
  • Pieces — every piece read in the window: title (which opens the piece's own page), kind, author where the record has one, views (a bar against the best-read piece), people, engaged minutes, average time, depth as a small bar, went on reading, the change on the window before — filtered by kind (only the kinds present), searched by title, sorted by any column, the first twelve shown and the rest behind Show all; Export CSV gives every piece with every column.
  • Authors — only where pieces carry one: every author with their pieces, views, people, engaged minutes, conversions and revenue, added up.
  • Topics & tags — views by the topics the pieces are filed under and by the tags they carry.
  • People — How far people read: where a read ended (left early, a quarter in, half way, to the end) as one bar and four rows; Where the reading came from: the channels of the sessions that opened a piece; Where visitors came from: the referrers, host by host, direct first.
  • Find your book (a site with the library's finder) — started, finished, completion, the share that went on to a book, the books by how often given / given first / opened, what people say they look for, the five words used most in each answer — never a table of every answer; a line saying so while the window holds nothing.
  • This site — the panels a site registers through #site/analytics, when it has any.

Every piece opens its own page (from the table, from Reading now, or from another piece's what they read next): views, visitors, engaged minutes, time on the piece, read to the end and went on reading, each against the window before; views and people a day since it was published (the record's date, else the first read on record); where they came from — the channels of the sessions that opened it, then the referrers; devices; new against returning people; where reads ended, in tenths of the page; what they read next (the piece opened after it in the same session, each a link to its own page); and what it led to — the sign-ups, memberships and purchases credited to it (last touch) with their revenue, by the channel the person arrived by. A piece nobody read in the window says so in one line.

The measurement is the app's: opening a piece writes a content_view event (kind, id, title, tags, the topic slugs), and leaving it — the next piece opened, a page view elsewhere, the tab hidden, the page gone — writes a content_read with the seconds it held the person — while the tab was visible and the person was there: a pointer move, a key, a scroll, a touch or a wheel within the last minute; a minute with none of them stops the clock at that minute's end and the next signal starts it again, so a page left open counts the reading and not the lunch, and a long read is as long as it was — and the furthest the viewport's foot reached as a share of the page. Reads are one per session and piece (the segments summed, the deepest kept); admin_content(days, audience, published) in the database does the arithmetic (the publish dates for Evergreen come from the collections, since the events do not carry them), admin_piece(kind, id, days, audience, tz) a piece's page, admin_kpis(days, audience, tz, filters, goals), admin_series(days, audience, filters, goals), admin_members(days), admin_events(days, audience, filters, event), admin_retention(weeks, audience, filters) and admin_now() the dashboard's — every one answering whoever may see the figures (may_see_figures()); filters is the chips and the source as one object (session_ok in the database applies it to a session's facts, session_facts(since) gathers them once per session), and with no source named a read is this site's own rows. The channel a session belongs to is decided once, by channel_of(ref, utm) in the database, from the referrer's host and the UTM tags: Email for a tagged email or a webmail host, Social, Search, AI, Paid (a paid medium), Direct for no referrer, Referral for the rest.

Forms → Analytics (the forms module) reports one form at a time, as Typeform does. The index is a card per form — started, finished, completion, the last response — and a form opens to its own results: the numbers with their sparks (started, finished, completion, time to complete, passed for a scored form), started and finished a day, where people drop off (every step of the form: how many reached it, how many left at it) and every question in the form's order — a choice's spread as bars with counts and shares, a scale's or rating's distribution with its average, a written answer counted with a link to Responses (never listed here). The responses themselves are Forms → Responses.

Community holds the pages that are not analytics at all — Support, the inbox; Comments, the queue and the rules it is judged by; and Community, the rooms and who may do what in them — and each appears only while its own module is on.

Marketing → Funnels → Performance (the funnels module) is what the funnels and the blasts did, built the same way: an index, then one funnel or blast at a time. The index: emails sent, the open rate, the click rate, unsubscribed (the count and its share of sent), conversions and revenue in the window against the window before; sent, opened and clicked a day; a card per funnel (its steps, in it now, completed, the window's numbers, the last send — a chat step's shown · threads begun on the line) and per blast; how many people are reachable and how many have opted out, in one line. A funnel opens to its own page: its tiles (entered, in it now, completed of everyone who ever entered, sent, open rate, click rate, unsubscribed, goal reached when it names a goal, conversions, revenue), entered · sent · opened · clicked a day, and the journey — the funnel drawn as a funnel. The journey follows the people who entered in the window, however far its emails have carried them since (so a later step never outnumbers the one before): every step in order, an email step with how many were sent it, opened it, clicked it, reached the goal and unsubscribed on it, each as a share of the step before; and between two steps, as things stand now, who is waiting for the next (its after not yet run out), held (due but not sent — the cooldown, or no longer in the audience) and who left the audience (no longer matching the rules, or opted out); the completed at the end. A chat step shows shown · picked · threads begun · people and every choice by how often it was picked, in the prompt's own words (a funnel of prompts alone shows only that). Open a step for its links by destination (clicks and people), when it is opened — a grid of hour by day of the week, in your own time zone — the median time from the send to the first open, the share read on a phone, and, when the step was edited while it was going out, each version's own sent · opened · clicked. A blast is one step on the same page.

Conversions are credited last touch, by email. A paid purchase, a subscription, or a page view of the funnel's or blast's goal — a path set on the funnel's settings and the blast's editor, the page somebody reaching means the email worked — in the seven days after a send goes to the last email of that funnel the person clicked before it, else the last they opened, else nowhere; on the index a conversion is credited once, to the last email of any funnel or blast. An unsubscribe is credited to the email it came from (the link at the foot carries the send). Opens are overcounted by mail clients that fetch images ahead — the page says so once and leads with clicks; delivery and bounces are the relay's and are not shown. Nothing on the page lists a person. The reads are admin_funnels(days, goals) and admin_funnel(kind, name, days, steps, rules, goal, tz), for whoever may see the figures; the funnels' shape (steps, goals, audiences) comes from the compiled .velite/funnels-index.json, since the database holds the log and knows nothing of a funnel's steps.

Every number comes from the site's own events — a page view, a content view and read, a checkout, a quiz step, an ad shown, a prompt picked — written by the app as they happen. Experiments read the same events for their conversions.

Notifications (web push)

Settings → Notifications lets staff opt a browser in to push alerts, even when the site is closed. The push module must be enabled. Set up the VAPID keys before expecting alerts:

  1. Generate a key pair with node build/vapid.mjs.
  2. Set VITE_VAPID_PUBLIC_KEY in the site's build environment.
  3. Set VAPID_PRIVATE_KEY and VAPID_SUBJECT as Edge Function secrets, then deploy the functions and rebuild the site.
  4. Open Settings → Notifications in each browser, allow notifications when prompted, choose topics, and use Test to check delivery.

Each browser has its own permission and topic selection. The settings page lists the account's other registered browsers and can remove a subscription. A support alert is suppressed while a focused tab is already showing the admin inbox. On iPhone and iPad, add the site to the Home Screen to receive push notifications. When the required keys are missing, the page reports that push is unavailable.

Topics: a new support conversation; a new line in one (the two are distinct); a new account; a purchase; a membership; a session booked. The support topics go to anyone on the desk; the rest to administrators. A member is told when they are named in a room they are in — and, from an end-to-end encrypted room, told that there is a message without being told what it says, because nothing here can read it.

Each browser opts in on its own — the permission, the service worker, the subscription — and keeps its own list of topics; the page lists an account's other browsers and can drop one. Test sends one to your own browsers. A support notification is held back when a focused tab is already on the admin (the inbox is telling you itself). On an iPhone or iPad, push arrives only once the site is on the Home Screen.

The words of each notification are UI strings → emails → ## push, a record per topic (title, body, url) with the facts filled in.

The deploy requires these keys when the push module is enabled.