Schedulr for Craft CMS

Reports & analytics

The ledger

Every send writes one row per recipient per channel: who, which channel, which variant, the outcome, the push service's status code and its error text. Schedulr → Reports and each notification's Full delivery log read from it. Both editions have it.

StatusMeaning
queuedOn-site only: waiting for the visitor to load a page
deliveredPush: the push service accepted it. Email: the mailer accepted it. On-site: the visitor's browser fetched it
failedNot sent. The status code and error say why
gonePush: the push service says the subscription no longer exists. The device has been dropped
skippedNot reachable on that channel

"Delivered" for push means the push service took it. Whether it appeared is what the displayed event is for.

The ledger outlives what it describes. Deleting a notification or forgetting a subscriber leaves their delivery rows in place, so "what went out last March" stays answerable. It is pruned on a rolling window instead: see ledgerRetentionDays in Configuration.

Reports

Schedulr → Reports shows the known browsers on your list (how many accept push, how many have an address), a daily chart of delivered and failed sends over 7, 30, 90 or 365 days, and, in Pro, the best-performing notifications by click rate (once they have at least 20 deliveries).

Each notification's delivery log shows the summary by channel and status, failure reasons grouped by status code with an example error for each, the recent sends of a recurring schedule, the A/B variants, and every ledger row, filterable to failures only.

From a shell, php craft schedulr/notifications/report <id> prints the same funnel and failure summary.

Click tracking (Pro)

In Pro, a notification's link is replaced by a tracked link on Schedulr's own domain:

https://example.com/schedulr/go?sr_n=42&sr_c=push&sr_v=7&sr_s=1234&sr_k=3f9a0c51b2e4

It records the click and redirects to the notification's link. Three properties are deliberate:

  • It carries only IDs, never the destination. A redirect that takes its target from a query parameter is an open redirect, and so a phishing tool on your own domain. A URL carrying another URL is also what pushes a payload over the roughly 4KB limit push services allow after encryption.
  • It is signed. sr_k is an HMAC of the other parameters made with your site's security key, so somebody who received one notification cannot inflate every campaign's clicks. A link with a bad signature to a notification that has been sent still redirects (so a rotated security key does not strand readers); it just is not counted. An unsigned link to a notification that has never gone out is a 404, so the redirect cannot be used to preview a draft's destination.
  • Every parameter starts with sr_. Craft reserves token and p.

Because tracking rides on the link rather than on the service worker reporting anything, it works the same through Schedulr's worker, PWA's worker, an email client and the on-site banner.

A person clicking twice counts once towards the click rate, though every click is recorded.

Action buttons on push notifications are tracked the same way, through their own signed links.

In Lite, new sends link straight to their destination and are not counted. Links that went out while the site was Pro keep redirecting and counting.

Events

Besides clicks, Schedulr records:

EventReported by
displayedSchedulr's service worker when it shows a push notification; the on-site runtime when it shows the banner
dismissedSchedulr's service worker when a notification is closed; the on-site runtime when the close button is pressed (not when the banner times out)
unsubscribedThe email unsubscribe link, once the visitor confirms (or their mail client sends a one-click unsubscribe)
convertedYour own code, if you report it

Displayed and dismissed push events come from Schedulr's own worker. On a site where PWA's worker handles push, clicks are still tracked but displays and dismissals are not reported.

The event endpoint only accepts events it can attribute to somebody: either the visitor ID the runtime keeps in the browser, or a subscriber ID together with the signature (k) the push payload carried for it. Each type of event is written once per person per notification, so repeating a request adds nothing. Requests must be sent as JSON, and are rate-limited per IP.

To record a conversion, post the notification ID and the visitor's ID:

fetch('/actions/schedulr/track/event', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
  body: JSON.stringify({ type: 'converted', n: 42, s: Schedulr.visitorId() }),
});

Clicks are never accepted on this endpoint: they come only from the signed redirect.

The funnel

Each notification's results (Pro) show:

targetedPeople the send was addressed to
deliveredLedger rows that were delivered
failedLedger rows that failed
displayed, clicked, dismissedDistinct people with that event
clicksEvery click, including repeats
deliveryRateDelivered as a percentage of targeted
clickRateDistinct clickers as a percentage of delivered
dismissRateDismissals as a percentage of displayed, since only something shown can be dismissed

A rate with nothing to divide by is shown as a dash rather than 0%, so a notification that has not been sent yet does not look like one that failed.

A/B results (Pro)

Each variant shows its deliveries, clicks and click rate. The winner is marked once every arm has at least 100 deliveries, as the arm with the highest click rate. See Usage.

CSV export (Pro)

  • Subscribers — Schedulr → Subscribers → Export CSV, optionally filtered to push subscribers, people with an address, or unsubscribed. Columns: id, state, email, userId, language, timezone, platform, visits, notified, firstSeen, lastSeen, subscribed. Needs the Export subscribers permission.
  • Deliveries — Export CSV on a notification's delivery log. Columns: id, subscriberId, channel, status, statusCode, error, sentAt.