# SMS for the Owner and for a customer

It behaves the same, because it is the same code. Both audiences reach SMS through one implementation with two doors: the Owner over the operator routes, a customer over the tenant facade that rewrites the path. Inbound handling, deduplication, attachment and **STOP** work for both; outbound works for neither on the live carrier. The only genuine difference is commercial — a customer's use is bounded by their plan.

- **Status:** Foundation
- **Audience:** both
- **Channels:** sms
- **In the app:** #/calls, #/dlt, #/billing
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/sms/sms-owner-tenant/

## One body, two doors

A customer session does not call the same URLs the Owner does. `tenantAdapt` rewrites an Owner path into its `/api/customer/ui/…` equivalent in the browser, and `customer_safe` in the backend is an allowlist that refuses any `/api/*` a customer session is not permitted to reach. Behind both doors sits the same module doing the same work.

This is why an SMS question is almost never an audience question. If a text is filed against the right person for the Owner, it is filed the same way for a tenant, because there is no second implementation to drift. When a screen genuinely does behave differently for the two, the cause is usually one of those two gates rather than the feature — an unrecognised path becomes `/api/customer/ui/blocked` and a 403.

> **Note** There is no Owner-only SMS capability and no customer-only one. The two things that are correctly Owner-only across the whole product — verifying customer payments and JBRH's own pricing — are about running the platform, not about using a channel.

## What is identical

| Behaviour | Owner | Customer workspace |
|---|---|---|
| Inbound webhook, verification, workspace resolution | Yes | Yes |
| Deduplication on the provider identifier | Yes | Yes |
| Attachment to a Person and the timeline | Yes | Yes |
| **STOP**, suppression and `block:sms` directives | Yes | Yes |
| Outbound SMS on the live carrier | No | No |
| DLT record keeping at `#/dlt` | Yes | Yes |
| Plan allowances applied to messaging work | No plan, no gate | Bounded by the plan |

Every row above the last is a statement about one implementation, not about two that happen to agree. The parity rule in this codebase is enumerated rather than asserted: a suite reads the route table out of the running application and compares each Owner capability with its customer counterpart, and the list of known gaps may shrink and must never grow.

## The difference that is real

Commercial, and only commercial. A customer workspace has a plan, and its messaging work is counted against that plan's allowances in the same ledger every other channel uses. The Owner has no plan and no gate. That is a difference in what may be spent, not in what exists.

Because outbound SMS is unavailable on the live carrier for both audiences, the allowance has almost nothing to bite on for this channel today. It is worth knowing about anyway, because it is exactly the difference that would appear the moment a provider carrying SMS was configured — the Owner would send freely, a customer would send until the day's allowance was spent, and the refusal would be recorded rather than silent.

## How to check a suspected asymmetry

1. Reproduce the same action as both audiences before concluding anything.
   - Result: Most reported asymmetries are one audience only in the report, not in the product.
2. If a customer session gets a 403, look at the path rather than the feature.
   - Result: An unrecognised route rewritten to `blocked`, or a path missing from the allowlist, produces exactly that symptom for a capability that works perfectly.
3. If both audiences fail the same way, treat it as a channel limit rather than an isolation problem.
   - Result: For SMS the usual answer is the provider: no carrier for text means no send for anybody.

## Questions

### Does a customer workspace get a different SMS provider?

Provider credentials are held per workspace, so two workspaces can be configured against different providers. What they cannot have is different handling code: the adapter normalises whatever arrives into the same canonical record, which is the point of keeping provider-shaped rows separate from the engine's.

### Can the Owner send SMS even though a customer cannot?

No. The carrier on the live account carries no SMS for anyone, and the Owner has no privileged path around a provider limit. The Owner's exemption is from plan allowances, not from physics.

### Where do I see the plan side of this?

Plan & Usage shows a customer workspace's allowances and what has been spent against them. The Owner's equivalent shows usage without a limit to compare it against, because there is no plan to enforce.

## Related

- [SMS in Connect](https://connectbyjbrh.com/docs/sms/)
- [Sending SMS: what is available today](https://connectbyjbrh.com/docs/sms/sms-outbound-status/)
- [WhatsApp for the Owner and for a customer](https://connectbyjbrh.com/docs/whatsapp/whatsapp-owner-tenant/)
- [One implementation, two audiences](https://connectbyjbrh.com/research/two-audiences-one-implementation/)
- [Security and isolation](https://connectbyjbrh.com/docs/security/)
- [Configuring a channel, end to end](https://connectbyjbrh.com/docs/workflows/channel-configuration/)

## What this page is based on

- `docs-source/sources/GENERAL.md` §1 and §2 — two audiences, one implementation
- `docs-source/sources/CHANNELS.md` §3 — the SMS channel
- Connect capability registry (docs-source/facts.py) — `owner_tenant_routing`, `sms_outbound`
