07 · RESPOND WEB APPLICATION · SAAS · MULTI TENANT
IncidentKit Website
An incident management platform for engineering teams: declare, coordinate, keep a timeline, publish a status page and write the postmortem from the record.
- Role
- Product, data model, RBAC, frontend and backend, end to end
- Platform
- Web · Next.js · PostgreSQL · organisations, roles and a public status page

The problem
During an outage the facts scatter across chat, documents and tickets. Afterwards someone has to reconstruct when it started, who noticed, when the cause was found, what was deployed and when service recovered.
My contribution
The whole product: organisations with owner, admin, responder and viewer roles; services with health states; incidents with severity and a lifecycle from declared to postmortem; a timeline of typed events with occurred at and recorded at kept apart; responders and incident roles; internal notes separated from customer updates by design; a public status page built from its own query; action items; postmortems generated from the record; and an audit log of the changes that matter.
Core stack
- Next.js
- TypeScript
- PostgreSQL
- RBAC
- Server rendered public status page
- Draft only AI postmortems
Proof it works
Every organisation's data is scoped at the query level, internal notes and customer updates are separate records, and the public status page reads public fields only, so an internal note cannot reach it by accident.
What it is
Context
IncidentKit gives a team one authoritative incident record: INC number, severity, status, duration, responders, affected services, timeline, updates, action items, and later the postmortem.
The rule that shaped it: internal incident information must never accidentally become public. The line between collaboration and customer communication is treated as a security boundary, not a display setting.
What made it hard
The challenge
Multi tenancy has to be enforced in every backend query, never by hiding things in the interface. A user from one organisation requesting another organisation's incident gets a not found, every time.
Timelines are only useful if their order can be trusted. Responders' clocks disagree, and people record events after the fact, so occurred at and recorded at are separate and the server is authoritative for creation time.
The experience
A walk through the product

01
One page for the whole incident: the promise, and the reason the product exists.

02
The principles on the landing page are the ones enforced in code: the public boundary, the timeline as the record, the postmortem from the record.

03
Sign in; organisations, roles and memberships decide what you can see and do from here on.

04
The organisation: services and their health, incidents, postmortems, action items and members, all scoped to one tenant.
What I built
The work, lane by lane
Tenancy and access
- Organisations with memberships and roles: owner, admin, responder, viewer
- Every query bounded by organisation
- Permission matrix enforced on the server
- Audit log for severity, status, responder, communication and role changes
Incidents
- Declare in seconds: title, severity, services, description, commander
- Lifecycle: declared, investigating, identified, mitigating, monitoring, resolved, postmortem
- Typed timeline events with occurred at and recorded at
- Responders with incident roles: commander, technical lead, communications, scribe
- Services with operational, degraded, partial and major outage states
Communication
- Internal notes and customer updates as separate models
- Public status page from a dedicated public query
- Published updates only ever appear publicly
- In app and email notifications
Learning
- Action items with owners, due dates and status across incidents
- Postmortem editor: summary, impact, detection, timeline, cause, resolution, lessons, actions
- AI drafted postmortem from the structured record, marked as a draft
- Metrics: time to acknowledge, identify and resolve
Engineering decisions
Decisions and their trade-offs
Real decisions cost something. Each of these names its price.
01Two models, not one flag
Internal updates and customer updates are different records. The public status page is built from its own query over public fields, never by serialising an incident and stripping properties.
Trade-offMore tables and DTOs, in exchange for a boundary that cannot be crossed by forgetting a filter.
02Occurred at versus recorded at
An event can be logged at 14:42 for something that happened at 14:26. Both times are kept; the timeline orders by occurred at and the server sets recorded at.
Trade-offSlightly more to explain in the interface; a much more honest record for the postmortem.
03Server side permissions, not hidden buttons
The role matrix is enforced on every mutation on the server. Hiding a button in the interface is a courtesy, never the control.
Trade-offEvery action needs a permission check even when the interface would never show it; that is the cost of being able to trust the boundary.
Under the hood
How the system holds together
A modular monolith: a Next.js interface and API over PostgreSQL, with an event service writing timeline and audit entries in the same transaction as the mutation they describe, a notification service, and a separate public read path for the status page.
Deeper technical notes
- Audit entries are written with important mutations rather than after them, so the log cannot drift from the data.
- Timeline events are idempotent to create, so retries do not produce duplicates.
- The postmortem assistant receives only structured fields and notes approved for it, and its output is a draft the author must edit.
Gallery
Every screen, up close
Real screenshots from the shipped build, open any of them for a closer look.
Quality, privacy, accessibility
Evidence of care
- Tenant isolation enforced in every query; cross organisation requests return not found.
- Role based access checked on the server for every mutation; viewers cannot change incidents.
- The public status endpoint is built from public fields only, never by serialising an incident and stripping properties.
- Audit log entries are not editable by ordinary users and are written in the same transaction as the change.
Where it stands
Outcome and current proof
- Built with organisations, services and their health states, incidents, postmortems, action items and members, as the dashboard shows.
- The AI postmortem assistant only drafts from the structured record and is marked as a draft.
Technologies
Chosen for reasons
- Next.js + TypeScript
- Server rendered interface and API with typed data contracts.
- PostgreSQL
- Organisations, memberships, services, incidents, events, updates, actions, postmortems and audit entries as relations with real constraints.
- RBAC
- A permission matrix enforced on the server, not in the interface.
- Separate public read path
- The status page can only ever see what was published.
For your project
What this experience enables
- SaaS products with organisations, roles and audit requirements.
- Operational tools where a trustworthy record matters more than chat.
- Public views over private data without leaks.