Architecture overview
By Rafael Duarte · created 2 Mar 2020, 10:00 UTC · last updated 22 Oct 2026, 15:30 UTC · version 25
architectureidentity-serviceportalnotifierregions
Three services, one stack per region. This page is the map; each system has its own page.
The shape of it
customer's IdP (Entra ID / Okta)
│ SAML
people at depots ──► identity-service ──► PostgreSQL (per region)
administrators ──► portal (React) ──┘ │
│ outbox_events (same transaction)
▼
relay job ──► SQS queue ──► notifier ──► Mailcrest (email)
│ └──► Textbridge (SMS codes)
└──► in-app notices (portal bell)
- identity-service is the source of truth: people, depots, roles, access requests, approvals, the audit log and the sign-in log. Laravel API, PHP. It signs people in with a password, a phone code or their company's single sign-on, switched on one depot at a time. identity-service.
- portal is the customer's admin portal, a React and TypeScript single-page app. It holds no data of its own; everything goes through identity-service's API. portal.
- notifier sends what people are told: email through Mailcrest, SMS codes through Textbridge, and in-app notices in the portal. It listens to identity-service's events. notifier.
Events
identity-service never calls notifier for a notice. It writes an event to its outbox_events table in the same transaction as the change, and a relay job puts it on the region's queue. Delivery is at least once; notifier drops events it has already handled by event id. The one exception is phone codes: identity-service asks notifier for an SMS directly, because a code that waits in a queue is a code a driver gives up on. Details: Events and the outbox.
Regions
Each client (our code's word for a customer) is placed in one region at onboarding, and its data and backups stay there:
| Region | Where | Since |
|---|---|---|
| EU | Frankfurt (eu-central-1) | the start; the default |
| UK | London (eu-west-2) | 2025 |
| US | Virginia (us-east-1) | January 2026 |
Each region runs its own identity-service, notifier, PostgreSQL, Redis, queue and backup bucket. Nothing is copied between regions. The portal's static files are the same everywhere; after sign-in it talks to its client's region. Regions and environments, ADR-006.
Sign-in
| Method | Who | Since |
|---|---|---|
| Password | anyone | the start |
| Phone code by SMS | depot workers without a company email | 26 May |
| Single sign-on over SAML (Entra ID, Okta) | a client's depots, one depot at a time | Entra ID since early 2023, Okta 22 June |
Administrators must use MFA in the portal since 11 June. Failed sign-ins are rate-limited at the edge and per account since 10 September (ADR-014). A session lasts eight hours unless the client's administrators set another length (1 to 12 hours, Settings › Sign-in); nothing ends it on idle.
Approvals
People ask for access to a depot's doors, systems and vehicles. identity-service routes each request once, when it comes in, to whoever holds the approver's role at the person's home depot; administrators see every request under All requests. Since 1 October the approver role can be held for one depot (ACM-247); since 22 October waiting requests move to the depot's new approver when a role changes (ACM-320). The product rules are on Roles and approvals.
Outside dependencies
| Dependency | Used for | If it is down |
|---|---|---|
| AWS (three regions) | everything we run | see the Disaster recovery plan |
| Mailcrest | notice emails; a US account and, since 16 October, an EU account | notices queue and retry; nobody is locked out |
| Textbridge | SMS codes | phone-code sign-in stops; runbook |
| Customers' IdPs | single sign-on | that depot can't sign in; runbook |
What is changing
- One switch for every tested depot (ACM-293) is in delivery since 20 October: a rehearsal list of each depot's test sign-in in the portal, one request that switches every depot that passed, and a way back for one depot. Until it ships, Kelderby's first seven depots switch one at a time.
- Portal in German and Dutch is in delivery.
Change history
| Version | Date | Who | What |
|---|---|---|---|
| 21 | 19 Jan 2026 | Rafael | US region |
| 22 | 14 Sep 2026 | Rafael | Rate limits and lockout after OPS-41 |
| 23 | 2 Oct 2026 | Marek | Approver role per depot (ACM-247) |
| 24 | 19 Oct 2026 | Rafael | Mailcrest EU account; ACM-293 approved for delivery |
| 25 | 22 Oct 2026 | Marek | Waiting requests follow their depot's approver (ACM-320) |