Consent, and STOP

The channel this product runs on is somebody else's phone. Two screens capture the permission to use it, one screen takes it away, and one tells the sender it is gone. Everything else in the account-less tier rests on these four.

1 · Inviting your builder
Ruth pays and Curtis has no account, which is the one tier combination where a homeowner types a builder's number. Consent is captured where the number is, and nowhere else.
An invite is solicited, so it sends without a consent row

And it has to, or the product cannot start. Requiring a granted row before every send makes it impossible to ever send the message that captures consent — the rule as first written would have bricked this. Two classes: solicited is an invite the sender asked us to send, a reply on a thread they started, a reminder about their own project, and it goes. Cold is texting somebody who never signed up, and it needs a row. A revoked row blocks both.

The tick is an assertion, not a preference

Which is why it is a tick rather than the switch used on 15-account. A switch is a thing somebody sets and resets; this is a statement Ruth makes once, on a date, that she is held to. Drawing it as a toggle would quietly recast a legal attestation as a setting.

What is rendered is what is stored

consent_text takes the exact sentence shown at capture, alongside the channel, the normalized number and the source. A record that stored a summary, a version number or a link to whatever the wording is today would be worth nothing in the only conversation it exists for.

2 · Adding a sub, and what he is attesting
The same capture from the other side, and the harder one: Dave is putting in a number for somebody who has not asked for anything. This is the moment Luis's number enters the system, which is what makes his Thursday question on 31 possible at all.
This is the send that starts texting strangers

And it is the one the consent decision gates. The program's core motion is texting people who never installed anything, subs especially, and US A2P 10DLC requires keyword support and an auditable record of why we had the number. The carrier may absorb STOP at the platform level; that stops delivery and gives us nothing to show, so the application keeps its own record regardless.

He is attesting to a relationship, not ticking a box

The sentence names what Dave is actually claiming: that he works with this person and that this person expects to hear from him. It is deliberately not I have permission to contact them, which is a thing anybody would tick without reading. source records that it came from the sub-add flow rather than from an invite, because the two claims are different and a year later the difference is the whole question.

One consent, every job

Luis is added on The Hollis House and the row is keyed to his number, not to the job. Dave putting him on Cedar Court next month captures nothing new, and Luis replying STOP from Cedar Court stops the Hollis House too. A person is one person.

3 · STOP, and what it takes away
Our only screen here is a hundred and one characters long, and it is not our chrome. This is the native message app, which is the whole point: opting out cannot require opening anything of ours.
Dave Marsh (Dave Marsh Builders) is asking about Cedar Court. Reply here or open cedar.hschlk.co/h3nd
Tuesday 8:14am
STOP
Tuesday 8:15am
HouseChalk: you won't get any more texts from us. Reply START to turn them back on, or HELP for help.
Tuesday 8:15am
That was every job, not this one Hendricks is on Cedar Court and on Ridgeline. One word from either stops both, and stops any job any builder adds them to after today. There is no per-job version of this and there isn't going to be one.
The keyword is matched before anything is looked up

STOP, START and HELP are handled at the top of the inbound webhook, ahead of the query that works out which job and which person this is. That ordering is the fix rather than an optimization: an opt-out from a number we cannot place would otherwise fail to resolve and never be recorded, which is the same defect as an unroutable reply landing nowhere. Both are one line, and it is this line.

Global, and absolute

A revoked row blocks the cold class and the solicited class alike. There is no reminder that survives it, no invite that overrides it and no receipt that argues with it. Honoring an opt-out on one job while another keeps texting is both illegal and obviously wrong, and the record is keyed by number precisely so that it cannot happen by accident.

HELP is real and gets no frame

It returns a fixed sentence naming the product and how to reach a person, and it sits on the same line as the other two keywords. Drawing it would be drawing this screen a third time to show one different sentence.

4 · A blocked send, surfaced to the sender
The other half, and the half that gets skipped. Dave is not the one who opted out, so if nothing tells him he will watch an ask sit unanswered and conclude his framer is ignoring him.
A dropped send and a dropped reply are the same defect

Pointed opposite ways. The register on 22-send exists because a reply that cannot be routed must stay visible rather than land nowhere; this row exists for the identical reason on the way out. A channel that hides its failures in either direction is a channel that lies, and the lie is worse outbound because the sender goes on believing the message arrived.

Rung 2, and a clock is not an error

It interrupts, because he cannot proceed and needs to know now. It does not block the screen, because the rest of Send is still true and still useful. The tone is .say.clock and it is earned rather than borrowed: a clock is genuinely running, framing starts Monday, and the ask has been sitting since 3 June. It prints itself — a clock is running — rather than asking the reader to decode an amber panel, and it means time is passing rather than that something is broken — there is no red rung in this product, and a blocked send is a fact rather than a fault. Note the register too: this variant on a homeowner surface would be a mistake, because a timestamp on their screen is anxiety and on his it is coordination.

The action slot is not optional, and here it points out of the product

A .say with no action is a bug in the page rather than a variant. Call them instead is the honest one: the thing he can actually do about this is not something this product can do for him, and offering a retry that would fail is worse than the wait.

He cannot undo it, and the screen says so

Not grayed out, not hidden behind a permission error, and above all not offered as a button that fails. Only the person who sent STOP can send START. Drawing a re-enable control here would be drawing a lie, and the inert-capability treatment exists for exactly this: visible, quiet, and explaining itself where somebody reaches for it.