Notify.software · Notification decisions for schools and districts

The decision layer is built and test-pinned. The send transport is unprovisioned, and this page is going to keep saying so.

Every notification a school platform sends has a decision in front of it: may we contact this family about this child at all, on which channel, at what hour, exactly once, and can we prove afterwards why. That decision layer is built here — persisted, fail-closed, and pinned by tests that assert the refusals rather than the successes. The transport that would put bytes on the wire is not connected. A planned channel reads provider-not-provisioned with zero counts and a null provider reference, and a test asserts that exact string. There is no queue holding messages for a later flip, no fabricated acknowledgement, and no delivery figure anywhere on this site, because nothing has been delivered.

Fail-closedan unresolvable consent context yields zero recipients — suppression, never assumed permission
One chokepointthe notification path calls the same canonical consent gate as the roster and record pipelines — no second copy
Zero countsevery planned channel reads provider-not-provisioned — asserted by a test, not by a comment
Opaque refsthe delivery surface is keyed on an opaque recipient reference — no student id column, counts-only roll-ups

What it does today

Seven capabilities, each with the symbol that holds it true

A page that says “built” is asking to be believed. Each card below carries the file or constant a reader with the repository can check the sentence against, and the four status words are kept distinct on purpose: built means the engine is persisted and pinned by a test; substrate means the store exists and the surface reading it does not; dark means the decision exists and always resolves to a refusal today, by construction; unprovisioned means no engine is connected and the seam answers with a service-unavailable response rather than fabricating one. Collapsing those into a single word would hide the difference between a transport we have not connected and a family who said no.

Recipient resolution from the enrolled roster only — never a self-asserted address

A guardian becomes a notify recipient only by being an enrolled contact on this school’s roster, carrying an address on file. An address typed into a form by whoever is holding the phone is not a recipient. This is why the platform cannot be turned into an unaudited mailing list by a well-meaning staff member: the recipient set is a projection of the roster and the consent record, and there is no writable path that adds an address to a send without adding it to the roster first, where it is visible and auditable. Every resolved recipient carries the source contact id for audit and de-duplication.

Built · roster-derived recipients only

record-notify.ts · ConsentedRecipient

Broadcast composition and lifecycle — a closed transition table, honest timestamps

A broadcast is composed from a reusable template that carries a subject, a body, a category and a severity. Categories are a closed set: emergency, weather, attendance, health, transportation, general. Severities are a closed set: emergency, urgent, informational. The lifecycle is a closed transition table — draft, scheduled, sending, sent, with cancellation available from draft, scheduled and sending — and the timestamps are written when the transition happens, not backfilled to make a report look tidy. The closed vocabularies are not a convention in the application code; they mirror database CHECK constraints, so a value outside the set cannot be persisted even by a caller that skipped the engine.

Built · closed vocabularies, DB-enforced

mass-notification.ts · migration 0492

Targeting — two selectors built, four deliberately unbuilt

Two audience selectors are built and run end to end: an explicit, bounded list of student references, and whole-school, which server-enumerates the roster through the existing read-only roster primitive and feeds every student through the same per-student consent chokepoint. Four selectors — role group, grade level, bus route, custom group — are shapes the store can hold and the engine will not expand. That is deliberate. An unverifiable mass fan-out is the single easiest thing in this domain to fake convincingly: it looks like it worked, it reports a plausible number, and nobody discovers the truth until the day it matters. Those four resolve to a refusal rather than to a guess, and they will stay that way until each has a bounded resolver that can prove which humans it selected.

Two selectors built · four refuse rather than guess

mass-notification.ts · AUDIENCE_KINDS

The delivery board — counts only, opaque refs, zero everywhere today

Every planned channel on every broadcast starts, and today ends, at provider-not-provisioned with zero counts and a null provider reference. That is not a display convention or an empty state waiting to be populated: it is what the engine returns, and it is pinned by a test that asserts exactly that string, exactly those zero counts. The board rolls up counts and nothing else. The per-recipient delivery surface is keyed on an opaque recipient reference — never a student id column — so no row on this surface is in the student-PII census, and the database adds a restrictive wall on top so that a report reader sees zero rows regardless. A number on this board can only ever come from a provider acknowledgement, and there is no provider.

Dark · provider-not-provisioned, zero counts, test-pinned

DISPATCH_PROVISIONED = false · mass-notification.test.ts:152

The send transport — unprovisioned, and the seam answers 503 rather than pretending

There is no outbound transport connected on this rail today. The master enablement flag is unset, which holds every lane honest-off regardless of what else is configured; and a lane whose transport is not connected stays honest-off even when every credential it needs is present, because presence of a key is not evidence of a working path. The SMS and voice lanes are additionally dark for a concrete, checkable reason: there is no per-school verified sending origin on file, so the planner suppresses each member rather than borrowing another school’s number. The dispatch route’s external processors answer 503. With no transport key the dispatch service resolves to a no-op implementation that logs and returns — it does not queue for later, it does not report a success, and it does not write a delivery row.

Unprovisioned · no transport, no queue, no fabricated ack

COMMS_LIVE unset · NoopNotificationService · no_sender_identity

What it refuses today

The refusal list, in the present tense, with the reason each one holds

This is the section most product pages do not have. It is written in the present tense because that is the only tense that stays true without someone re-reading it: a sentence that describes a future arrival becomes a promise the moment the calendar moves, and nobody re-reads a marketing page to keep it honest. Every line below is what happens if you use this rail this afternoon.

  • No message leaves the platform on this rail. The master enablement flag is unset, which holds every lane off regardless of what else is configured. With no transport key the dispatch service resolves to a no-op implementation that logs and returns. It does not queue, it does not retry later, and it does not write a delivery row.
  • No mailbox is provisioned. There is no inbox on this product, nothing to sign in to, and no address that receives replies. The owned mail transport is modelled as a lane and that lane is off.
  • Every planned channel reports provider-not-provisioned with zero counts and a null provider reference. A delivery number can only come from a provider acknowledgement, and there is no provider. The engine has no code path that writes a success without one.
  • Role-group, grade-level, bus-route and custom-group targeting refuse rather than expand. Those shapes persist, and the engine will not resolve them to a recipient set until each has a bounded resolver that can prove which humans it selected. An unverifiable mass fan-out that reports a plausible number is the single most dangerous thing this domain can ship.
  • SMS and voice suppress on a named reason before reaching a transport. No per-school verified sending origin is on file, so each planned member is suppressed with that reason code rather than sent from a number belonging to another school.
  • An emergency broadcast does not override a family’s opt-out or quiet-hours window. The emergency kind is modelled as deferrable. Whether a genuine life-safety message should bypass both is a district and counsel decision with a regulatory question behind it, and it is recorded as an open decision rather than quietly resolved in code.
  • No AI drafts, ranks or times a message. No engine is provisioned, so there is nothing to disclose to a family and nothing to defend to a board.
  • No money moves and no price is set. There is no checkout on this site and no plan to select.

Lane states

Eight lanes, all off, each off for its own reason

The enablement surface models the rail as eight independent lanes rather than one switch, because “messaging is on” is not a fact anyone can act on. A lane is ready only when the master flag is set, its own inputs are present, and its transport is actually connected — the last of which is a separate bit precisely so that a full set of credentials cannot make a dark lane read as ready. Every lane is off today. Listing all eight is a more honest picture of the work remaining than naming one.

Off

Transactional email

The decision layer routes to it; the transport is not connected. The api and worker halves of the rail disagree on one from-address variable name, and the enablement surface reconciles that mismatch rather than letting a half-configured lane read as ready.

Off

Transactional SMS

Dark for a specific reason rather than a general one: no per-school verified sending origin is on file, so every planned member suppresses with a named reason code instead of a faked send.

Off

Web push

The in-app notification store exists and is persisted; the browser push transport is not connected.

Off

Family email on the owned mail transport

The lane the estate intends to own outright rather than rent. It is modelled and it is dark. No mailbox is provisioned and no mail is sent from this product today.

Off

Family SMS and voice

Blocked on the same verified-origin store as transactional SMS. The planner reaches sender-isolation and stops there.

Off

Enrollment messaging

A separate lane on purpose: consent for an operational notification is not consent for an enrollment campaign, and the rail refuses to treat them as one basis.

Off

Voice

Modelled as its own lane with its own origin requirement. No transport connected.

Off

Scheduling notifications

The booking substrate exists on a sibling surface; the confirmation send rides this rail and is off with the rest of it.

Who it is for · and the first week

For the person who has to explain, afterwards, why a message went out

The reader this is built for is the district or school operations owner who is accountable for family contact, and the engineer or administrator sitting beside them who has to answer the follow-up question. It is not built for a procurement committee comparing feature grids, which is why there is no grid on this page. The first week produces a real deliverable and sends nothing, and that is not a limitation of a trial — it is the shape of the product today.

1 · Day one · the consent picture

You point the rail at a roster and it tells you, per student, whether a notification about that child would resolve to a recipient at all, and if not, which of two named reasons applies: the student is suppressed, or no enrolled contact carries an address. That is a report you can act on immediately, and most operations owners have never had it. It is produced entirely from records you already hold, and producing it sends nothing.

2 · Day two to three · the quiet-hours and channel picture

Per-recipient channel preferences and quiet-hours windows are loaded and validated. A malformed timezone is rejected at the boundary with an error naming the field, not accepted and silently coerced. You finish with a defensible answer to the question a board member eventually asks: at what hour would this system have contacted my family, and who decided that.

3 · Day four to five · a broadcast composed and planned, nothing sent

You compose a real template, target it with an explicit student list or whole-school, and run the plan. It returns an audience reach estimate and a per-channel plan in which every channel reads provider-not-provisioned with zero counts. This is the deliverable: a rehearsal that is honest about being a rehearsal. Nothing leaves the building, and the record of the rehearsal is a real persisted broadcast in draft state that you can cancel.

4 · The end of the week · the written refusal list

You leave with a list of exactly what this rail will refuse to do for you today and the symbol that holds each refusal in place. That list is the honest basis for deciding whether to build on it, and it is the same list on this page.

How it fits

The machine-send member of a family of human-correspondence products

This is a deliberate outlier inside its own brand family. Its siblings are correspondence products — a person holds an address, writes to another person, and reads a reply. This one is machine-send: a system decides that a family should be told something and the message is generated, never typed. That is why the identity here is a signal and a pulse rather than an envelope, why the palette is a status light on graphite rather than the institutional navy the procurement-facing products wear, and why the page reads like an operations console instead of a brochure. The reader is not being persuaded to feel something about their school; they are being told, precisely, what a system will and will not do on their behalf.

It shares a substrate with the operational products it serves rather than duplicating them. The scheduling surface’s confirmations, the program surfaces’ family updates and the safety surfaces’ broadcasts all resolve their recipients through the same chokepoint described above. There is one consent decision in this estate, not one per product, and that is the property that makes the family coherent rather than merely similar-looking.

Pricing

No price is set, and there is no checkout on this site

There is no price for notify.software, no plan tier, and nothing to purchase here. That is the whole present-tense answer, and inventing a number to fill the section would be the exact failure this page is built to avoid. Two facts that are settled and worth stating: the underlying mass-notification module is classified as safety-tier, enabled on every base plan, and marked must-stay-free, on the reasoning that a life-safety surface must not go dark for a billing reason; and what the outbound transport costs is an unanswered question, because the transport is unprovisioned. Any per-message figure written here today would be a guess wearing the costume of a quote.

Objections

The questions a sceptical operations owner actually asks

If it does not send anything, what am I actually looking at?

The half of a notification system that is hard to get right and impossible to check from the outside. Any transport can be connected in an afternoon. Deciding correctly, every time, whether a specific family may be contacted about a specific child, on a specific channel, at a specific hour, exactly once, and proving afterwards why — that is the part that takes months and the part that causes the incident when it is wrong. That part is built, persisted and test-pinned here. The transport is the cheap half and it is deliberately last, because a transport connected to a decision layer you have not audited is how a district ends up apologising for a message it cannot explain.

Is the transport being off just a flag someone can flip on a Friday?

No, and the distinction matters. Flipping the master flag alone changes nothing, because a lane whose transport is not connected stays off even with its full environment present. The engine constant that holds the send plan at provider-not-provisioned is a compile-time false in the domain module, and a test asserts the resulting plan byte for byte. SMS and voice have a second, independent block: no verified sending origin exists per school, so the planner suppresses on a named reason before it reaches a transport at all. Turning this rail on is provisioning work with an audit behind it, not a switch.

You describe four targeting selectors that refuse. Is that not just an unfinished feature?

It is an unfinished feature that refuses instead of guessing, and the difference is the entire product. The failure mode we are avoiding is the one where a grade-level selector quietly resolves to the students it can see, reports a confident number, and nobody discovers the gap until the day of an actual emergency. Each of those four selectors will ship when it has a bounded resolver that can enumerate exactly which humans it selected and prove the enumeration was complete. Until then the honest answer is a refusal, and we would rather write that on the page than discover it in an incident review.

What happens to a family who has not answered the consent question at all?

They are not contacted. An unresolvable consent context is treated as suppression, not as permission. This is the direction that costs us: it means the reach number is lower than a system that assumed consent, and it means an operations owner sometimes has to go collect a consent record before a message can go out. We think a platform that quietly widened its own permission when the record was missing would be the wrong thing to hand a school, and the fail-closed default is asserted by its own test rather than left to the caller.

Does an emergency override the quiet-hours window and the opt-out?

Not today. The emergency broadcast kind is modelled as deferrable, which means it respects the recipient’s per-channel opt-out and quiet-hours window like any other kind. There is a legitimate argument that a genuine life-safety message should override both, and it is a decision for a district and its counsel with the regulatory question answered properly — not a default we set for them in code. It is written down as an open decision rather than quietly resolved in either direction.

Does any child data leave our tenant?

No child name and no student reference crosses a service boundary. The delivery surface is keyed on an opaque recipient reference rather than a student id column, so no row on it is in the student-PII census, and the database carries a restrictive wall on that table on top of the application rule. Roll-ups are counts only. The one piece of personal data that rides a resolved notification is the guardian’s own address on the enrolled contact record, which is the minimum required to address a message to them.

Is any of this using AI to write, rank or time messages?

None of it. No drafting engine, no send-time model, no ranking. There is no AI engine provisioned on this rail, so there is nothing to disclose to a family and nothing to defend to a board. If that changes it will be an explicit, separately consented capability with its own disclosure, not a quiet improvement to an existing feature.

What does it cost?

No price is set for notify.software and there is no checkout on this site. The underlying mass-notification module is classified as a safety-tier module that is enabled on every base plan and marked must-stay-free, on the reasoning that a life-safety surface must not go dark for a billing reason. What the transport costs once it is provisioned is an unanswered question, because the transport is unprovisioned. Anything else written here would be a number we invented.

The next concrete step

A consent-picture report on your own roster, in one working session

The concrete next step is a single session against your roster that produces the day-one deliverable described above: per student, whether a notification about that child would resolve to a recipient at all, and which of two named reasons applies when it would not. It sends nothing, it needs no transport, and it uses records you already hold. Most operations owners have never seen that report, and it is the honest basis for deciding whether the rest of this rail is worth building on. Write to the address below with the number of schools and whether your contact records live in a student information system today.