mesej

Reply-only: what it forbids, and where the constraint actually lives

Reply-only means the system never messages anyone first. What that forbids, where the constraint lives in code rather than in a policy page, how much of that code exists today, and why both platforms already work this way.

Reply-only means one thing: mesej never messages anyone first. Everything else follows, including the capabilities listed below that the product does not have. This page lists what the constraint forbids, shows where it is enforced in code rather than in a policy page, says how much of that code exists today, and quotes the platform rules that already work the same way.

What reply-only forbids

Each is a route the code does not have rather than a setting it declines to use. How much of that code exists is the third section down:

  • No first message. Nothing in the product takes a phone number, a chat id or a list from you and writes to somebody. The one function that mints a send destination takes a conversation’s own identity and is named for the single caller allowed to use it, so an originating send would have to be written on purpose rather than switched on.
  • No broadcast, no campaign, no reactivation of a cold customer list.
  • No follow-up once a conversation has gone quiet past its permission window, including one you type yourself. On WhatsApp a closed window takes away your ability to write freely exactly as it takes away the agent’s.
  • No review request, and no appointment reminder, to anyone outside an open window. Reminder timing is computed by a pure scheduler with no ability to send anything.

A build gate here enforces the wording too: the claim is that it never messages anyone first, not that it merely answers. A review ask is not an answer, so the second version would stop being true the moment one shipped.

A policy and an architecture are different claims

A tool can describe itself as inbound-only and mean it sincerely. A policy is a sentence in documentation, held in place by whoever ships the next release, and it lasts as long as the incentive behind it. So the question worth putting to any vendor is what would have to be rewritten to break it. If the answer is a toggle in an admin panel, the promise is a preference.

Where the constraint lives in this codebase

Two mechanisms, both checkable by reading the repository.

No send function takes a raw destination. The type a send accepts is a branded ChannelTarget carrying a unique symbol, so no plain object satisfies it structurally, and it has one constructor: target_from_conversation, which is meant to be called with a conversation’s own persisted identity, meaning the channel a message arrived on and the contact who wrote it. Be exact about how strong that is, because the overstated version was here until this pass: the constructor takes plain strings and brands them, so a phone number typed into a form could be handed to it by a caller determined to do so. What the brand buys is that no forged object satisfies the type, and that the constructor is named for its one legal caller, so a wrong call site shows up in a diff. The enforcement test named for this is specified as: “No exported function accepts a phone number, WhatsApp ID or Telegram chat ID as a send destination.” What tests/send_guard.test.ts checks today is narrower than that sentence: it scans the guard function’s own source text for the words phone_number, chat_id and recipient, which a parameter rename would pass.

Permission is the first check in the guard, before anything becomes visible outside the system. The guard reads a permission clock and refuses outright once it has passed. Two details carry weight. The permission clock is a separate field from the billing clock and separate functions read them, so a free 72-hour billing window cannot quietly grant 72 hours of permission to write. And an owner reply typed in the console runs through the same check as an agent reply, so the window applies to a human exactly as it applies to the model. Tests drive the guard through a closed window, an owner reply into a closed window, an over-plan owner reply that must still go, and a live billing window against a dead permission window.

How much of this exists, stated exactly

Claiming an architectural guarantee for code that does not exist would be worse than writing nothing, so:

Part Status on 19 September 2026
The branded target and its one constructor Written, with tests
The send guard, permission checked first Written as a pure function over injected effects, with tests
The WhatsApp and Telegram senders Written, called by nothing but a test
The conversation object that would own the send Deployed, storing state and running its alarm. It does not send
Webhook route, queues, the path from an inbound message to a reply Absent

So the mechanism exists and the product around it does not. No channel is connected, so nothing here answers a customer yet. Our architecture document names 38 enforcement tests. Searching the test tree for those names on 2026-09-19 finds 11 of them and not the other 27, and most of the missing ones cannot be written yet because they assert properties of a send path that is unfinished. This page said roughly sixteen and seventeen until that count was actually run, which did not even add up. Publishing the mechanism now is not a claim that it is done. It is checkable now, and a constraint retrofitted later is a different thing from one the send path was designed around.

Telegram already enforces it, and charges nothing

On Telegram the constraint costs us nothing, because the platform does it for us. From Telegram’s introduction for developers:

Bots can’t start conversations with users. A user must either add them to a group or send them a message first. People can search for your bot’s username or start a chat via its unique t.me/bot_username link.

The discovery route named there is the only outbound shape Telegram allows: a link somebody else chooses to click. Telegram removes the unwanted message while leaving the wanted ones free, and says so in its FAQ: “bots are able to message their users at no cost”, subject to published throughput limits such as “In a single chat, avoid sending more than one message per second.” Its paid tier buys throughput to users who already started a chat, never the first message.

On WhatsApp, breaking it costs money and needs approval

Meta does not prohibit the first message. It prices it and reviews it, which comes to the same thing for a product that does not want to send one. The Service messages page defines the permission:

When a WhatsApp user messages you or calls you, a 24-hour timer called a customer service window starts. If the user messages or calls you again before the timer expires, the timer resets to 24 hours.

Inside it a business writes freely, and Meta notes on the same page that “service messages do not require pre-approval”. Outside it, the Business Messaging Policy is blunt: “Outside the 24-hour customer service window, you may only send messages via approved Message Templates”. The pricing update page puts the consequence in one line: “Thus non-template messages can only be used to respond - not reach out - to users”.

Two costs sit on that template route, and they are why we do not build it. Approval is one: Meta’s Template fundamentals page says “Templates are automatically reviewed upon creation or after editing”, and a template that fails review “cannot be sent in template messages”. Money is the other: on Meta’s pricing page, “You are only charged when a template message is delivered”, and “Template messages are the only message type that can be sent outside of a customer service window, to reach out to users.” Those are Meta’s rules and Meta’s prices on Meta’s platform, quoted as platform mechanics and not as anything we offer.

So reaching out buys approval and pays per message, and replying needs neither. That is a poor reason to adopt the constraint and a fair reason to believe somebody who has.

What we have not verified

We read documentation, not behaviour. We have not run a send at a stranger against either provider to see what error comes back, and we are not going to describe an error message we have not seen. Nothing above has been exercised by a real customer message, because no channel is connected to exercise it.

Reply-only also says nothing about reply quality, and the two are easy to conflate. A system that never initiates can still answer badly, which is why the reply text is gated separately, and why Google moderates the replies businesses publish and exposes a reason in its API when it rejects one. If you are comparing tools that write for you, what the category can and cannot do is more useful than who claims restraint, and answering an unhappy customer is a craft problem either way.

The checkable version of reply-only is short. No send takes a raw destination, one constructor mints a target and is named for the only caller allowed to use it, and permission is the first thing the guard reads. Today that mechanism has nothing wired to it, and saying so is part of the claim.

Sources