Salim Sadman

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
IncidentKit landing page: the headline When production breaks, everyone looks at one page, a short description, and three principle cards.

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

IncidentKit landing page with the headline When production breaks, everyone looks at one page and a call to action.

01

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

Lower landing page with three principle cards about the public boundary, the timeline as the record, and the postmortem written from it.

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.

Sign in page with email and password fields and a note about demo accounts.

03

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

Organisation dashboard for Acme listing services such as Website, Authentication, Payments and Database with their health states, tabs for incidents, postmortems, actions and members.

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.

UIAPI · ORG SCOPERBACPOSTGRESEVENTS + AUDITPUBLIC STATUS
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.