Features¶
Features are the "what" and "why." Each page here describes a capability the way a person experiences it — what someone is trying to do, what they see, and what happens behind the scenes — before handing off to the services and models that make it real.
This is the top of the depth ladder: start here when you want to understand a thing Beacon does, then follow the links down into the services that own the behaviour and the data models they read and write. If you instead want the system map, start at the Overview; if you want a single service as a black box, jump to Services.
The features, at a glance¶
Each row is one user-facing capability. "Who" is the person triggering it; the services and models columns are where you go for depth.
| Feature | Who triggers it | Domain | Touches services | Touches models |
|---|---|---|---|---|
| Plan upgrade | Organization admin | Billing | billing-service, accounts-service, messaging-service | Subscription, Organization |
| Suspend an organization | Internal admin | Accounts | accounts-service, messaging-service | Organization |
Two features today, and they were chosen deliberately as a reference set: one spans every domain and both kinds of inter-service call (synchronous HTTP and asynchronous events); the other is a small, near-self-contained admin action. Read both and you've seen the two shapes most Beacon features take.
Plan upgrade — the full-stack example¶
Plan upgrade is the feature to read first. An organization admin opens Settings → Billing, picks a higher Plan, and confirms the prorated charge. The new plan is active immediately and a confirmation email lands shortly after.
It's the example we point people at because it exercises the whole system in one go:
- The web UI calls billing-service (
POST /subscriptions/{id}/upgrade), which reads org state from accounts-service (GET /orgs/{id}) before it commits. - billing-service applies the change to the Subscription,
computes proration in application code, and emits a
subscription.upgradedevent onto RabbitMQ. - messaging-service consumes that event, renders a confirmation, sends it, and records a delivery event.
So a single click fans out across a synchronous HTTP path (the upgrade itself, which must succeed before the admin sees a result) and an asynchronous event path (the confirmation, which happens eventually and independently). That split is the single most important idea to take from this feature: the upgrade is transactional and blocking; the message is eventual and decoupled. If RabbitMQ or messaging-service is down, the upgrade still succeeds — the admin just gets their email late.
sequenceDiagram
participant UI as Web UI
participant Billing as billing-service
participant Accounts as accounts-service
participant MQ as RabbitMQ
participant Messaging as messaging-service
UI->>Billing: POST /subscriptions/{id}/upgrade {plan_id}
Billing->>Accounts: GET /orgs/{id} (seats, status)
Accounts-->>Billing: org details
Billing->>Billing: validate + prorate, update Subscription
Billing-->>UI: 200 {subscription}
Note over Billing,MQ: synchronous path ends here
Billing-)MQ: publish subscription.upgraded
MQ-)Messaging: subscription.upgraded
Messaging->>Messaging: render + send confirmation, log delivery_event
The page itself goes deeper still — into the proration-in-app-code nuance, a
Discrepancy where the help center disagrees with the
code, and a suspected-dead coupon_code field the UI still sends. Those callouts
are good models for how we record gotchas in place rather than dropping them.
Suspend an organization — the small admin example¶
Suspend an organization is the counterweight: a tight,
mostly self-contained action with no event fan-out. An internal admin flips an org
to suspended (for non-payment or abuse); the org can still sign in, but
messaging-service refuses new sends until it's reinstated.
It's worth reading precisely because it's not glamorous. It shows a feature that lives almost entirely in one service (accounts-service, the sole writer of org state) plus one downstream consumer that simply reads that state at the right moment. There's no shared database and no event — messaging-service asks accounts-service "is this org still active?" right before each send. That read-the- authority-don't-cache-it pattern recurs all over Beacon, and this is the smallest place to see it.
How a feature is shaped¶
Every feature page on this site follows the same business → technical arc, so you can skim the top of any of them and stop when you have what you need:
- A one-line summary (the blockquote) — what the feature does, for whom.
- User journey — the numbered steps a person actually takes.
- How it works — the prose-plus-
sequenceDiagramexplanation of what happens behind the click. - Services & data involved — the links down into services and models.
- Notes & nuances — collapsible detail, discrepancies with secondary sources, and "suspected dead" flags for paths that look unreachable.
If you're adding a feature, copy an existing page and keep that arc; lead with the human story, then earn trust with the specific service calls, event names, and status values further down.
How features map to the rest of Beacon¶
Features don't own services or data — they use them. The same service shows up in several features, and that's the point: the feature pages tell the journeys, the service pages own the contracts, and the model pages own the data shapes.
flowchart TD
upgrade[Plan upgrade] --> billing[billing-service]
upgrade --> accounts[accounts-service]
upgrade --> messaging[messaging-service]
suspend[Suspend an organization] --> accounts
suspend --> messaging
billing --> subModel[Subscription]
accounts --> orgModel[Organization]
messaging --> deModel[Delivery event]
- billing-service — Ruby/Rails on MySQL. The authority on plans, subscriptions, invoices, and proration. Owns the upgrade.
- accounts-service — the tenant/identity layer; sole writer of Organization state. Both features above read it; only the suspend feature writes it. (Stub — see below.)
- messaging-service — Elixir/Phoenix on PostgreSQL + MongoDB + RabbitMQ. Renders and sends messages and records delivery events. It reacts to both features rather than driving either.
Reading order, if you're new: skim this page, read Plan upgrade top to bottom, then dip into billing-service and Subscription when the feature page links you there.
What's not here yet
These are the two reference features we've deepened so far — they aren't Beacon's whole surface area. Sending a campaign, dunning a past-due subscription, and provisioning a new org are all real journeys that don't have feature pages yet. When they're added, they belong here with the same shape.
accounts-service is a stub
Both features above lean on accounts-service,
but its service page is still a stub (known
shape only, low confidence). Treat its HTTP surface — GET /orgs/{id} and the
suspend endpoint — as confirmed-by-use here but not yet fully documented there.
accounts-service language — a structured-field gap, not a conflict
Every source agrees the service is Java/Spring on MySQL: the
accounts-service page prose, the
Organization model, and the
Accounts domain page all say so. The service's
frontmatter, however, records only language: java — consistent on the
language, but it drops the framework the prose carries. Nothing here
contradicts; the structured field is just coarser than the prose. Worth
normalising in a convergence pass rather than treating as drift.