Blog Behaviour

The nudge we did not send: notification timing as a defect

Most engagement systems treat notification timing as a delivery problem and optimise for the message being opened. We hold roughly a third of everything the engine wants to send, and that number is on the dashboard where everyone can see it.

The defect nobody logs

Notification timing is not a delivery detail. A reminder to stand up, delivered while you are driving, is not a neutral event. It is a small tax on attention and a slightly larger one on trust, and after enough of them the person turns notifications off — at which point the product is finished, however good the recommendation engine is.

So the context agent runs before every send, and returns one of three verdicts: deliver, defer with a retry window, or block with a named reason. A block is recorded as a successful decision, not a failure to deliver.

What blocks a send

Driving, detected from motion state rather than location. An active call. Do-not-disturb. The daily cap, which is per person and per mode. A minimum gap since the last one. Quiet hours. A calendar block marked as focused, which we read structurally — the titles never leave the device.

When a send is blocked, the API returns it as a success with `held: true`, the blocking reasons, and a retry window. It is not an error, and treating it as one is how you end up with a client that retries into the same wall.

The awkward growth conversation

Held rate as a headline metric makes every growth review slightly uncomfortable, because the obvious lever for weekly active users is to send more. We have the number in front of us specifically so that lever is visible when somebody reaches for it.

What we measure instead is completion rate per delivered message. Sending fewer and better moves that number in the direction that actually correlates with people still using the product in month six.

Timing is a bandit, not a language model

Notification timing is decided by a contextual bandit with a sub-second budget. It is not an LLM and the architecture notes say so explicitly, because "upgrade it to a model" is a suggestion that arrives about twice a year and is wrong every time.

How a calendar is read without reading it

The most useful signal for when somebody can move is their calendar, and the calendar is also the single most sensitive thing on a work device. Titles contain client names, medical appointments, disciplinary meetings and resignations.

So the calendar is read structurally. Start time, end time, busy or free, accepted or tentative, number of attendees, whether it recurs. The title is never transmitted, never logged and never sent to a model — it is in the redaction list the AI Gateway enforces before any external call, alongside names, clinical notes and free-text responses.

This costs accuracy. A meeting called "1:1 with Sam" and one called "quarterly board review" look identical to us, and the second is a much worse moment to interrupt. We accept the worse recommendation, and the notification a person receives says "calendar titles never left your device" so the trade is visible rather than assumed.

What a held nudge actually returns

The response is a success with `held: true`, an array of blocking reasons, and a retry window in seconds. Not a 4xx, not an empty body, not a silent no-op.

That shape matters more than it looks. A client that receives an error retries, logs an exception, and eventually shows the user something is broken. A client that receives an explicit hold with a retry window waits, and can tell the person why if they ask.

The metric we watch instead

Completion rate per delivered message, split by mode. It is the number that moves when the timing model improves and the number that falls when somebody quietly raises a daily cap to hit a weekly target.

Held rate sits next to it, unexplained and deliberately prominent. If it drops sharply without a corresponding change to the context model, something has been loosened and somebody should have to say what.

Questions this article answers

What stops Jess Move sending a notification?

Driving, detected from motion state rather than location. An active call. Do-not-disturb. The daily cap, which is per person and per mode. A minimum gap since the last one. Quiet hours. And a calendar block marked as focused, which is read structurally.

Does Jess Move read my calendar titles?

No. It reads start time, end time, busy or free, accepted or tentative, attendee count and whether it recurs. The title is never transmitted, never logged and never sent to a model — it is in the redaction list the AI Gateway enforces before any external call.

What happens when a nudge is held rather than sent?

The API returns a success with `held: true`, an array of blocking reasons and a retry window in seconds. Not a 4xx, not an empty body, not a silent no-op — a client that receives an error retries into the same wall.

Part of: What micro-movement actually is

informational

This article sits in the micro-movement cluster and links up to /micro-movement. Clusters are how the editorial agent decides what to write next — the thinnest one wins.

  • how to be more active with a desk job
  • how long a movement break needs to be
  • exercise without changing clothes or going to a gym
  • what to do when you have no time to exercise
  • movement snacks versus one long workout

Read next

More of the same argument.

This is written about a product you can use today.

Everything described here is running. An account is free, takes about two minutes, and the writing stays free whether or not you make one.

Free to create. 150 AI credits a month for 2 months, then AI features need a plan — everything that is not AI carries on either way. Premium is £5.99 a month and cancels in one click. Under 18 a guardian confirms before the account opens.