# Build brief — a focused alternative to Instatus

> **Verdict:** Partly, if you narrow it · **Buildability:** 49/100 · **Category:** Uptime
> **Source:** https://www.canitbevibecoded.com/instatus
> Independent editorial assessment from Can It Be Vibe Coded? Not affiliated with, endorsed by, or derived from Instatus. Verify current pricing and capabilities before acting.

## Context

**Instatus** — Hosted status pages with incident updates and subscriber notifications for your users.

The page itself is the easy part: components, colored dots, an incident timeline, a 90 day uptime bar; an agent will hand you that with a focused implementation. The notification fan-out is where it gets annoying: email is a Resend account and a loop, but you inherit double opt-in, unsubscribes, bounce handling, and explaining why the incident email landed in spam during the outage. Hosting is the other half: a status page on the same infra as the thing it reports on is decoration, so you need a separate host and domain and the discipline to keep it boring. Add the workflow Instatus quietly gives you, writing an update from your phone at 2am, per-component subscriber preferences, Slack and RSS and webhooks. Buildable in a focused implementation for a personal project; not the thing to hand-roll if a real customer contract mentions the words 'status page'.

This brief describes a focused, single-operator replacement for the part of Instatus that is genuinely reproducible. It is deliberately narrower than the product it replaces, and it says so in writing. Build the useful core; do not pretend to have rebuilt the rest.

## What you are building

A separately hosted status page that renders component health and incident updates from a small database, with an admin form to post updates and an email blast to confirmed subscribers.

- Ping endpoints, store status history, and notify when checks fail.
- A responsive interface with real empty, loading, success, and error states.

## Requirements

### Functional

- A host that is not the infrastructure you are reporting on, ideally a different provider entirely.
- A separate domain or subdomain on separate DNS.
- A transactional email provider with a verified sending domain, e.g. Resend or Postmark.
- Somewhere to store subscribers that survives a reboot.

### Non-functional

- Accessibility: semantic markup, labelled controls, visible focus, and reduced-motion support.
- Security: server-side secrets, validated input, and no credentials in the client bundle.
- Reliability: retries with backoff on external calls, and a clear failure state when a provider is down.
- Portability: the operator can export their data and leave without losing it.

## Implementation brief

Build a self-hosted status page app in an empty folder. Stack, no substitutions: Node 20, Fastify, better-sqlite3, server-rendered HTML via Fastify's reply.type('text/html') and plain template literal functions, vanilla CSS in one file. No React, no build step, no ORM.

What it does:
1. Public page at / showing a list of components (name, description, status: operational, degraded, partial_outage, major_outage) with an overall banner computed from the worst component status.
2. Under that, incidents newest first. An incident has a title, impact, current status (investigating, identified, monitoring, resolved), and an append-only list of timestamped updates.
3. A 90 day uptime strip per component rendered from a daily_status table (one row per component per day, worst status seen that day). Fill it from the poller below.
4. GET /api/status returning the same data as JSON, and GET /feed.rss as a valid RSS 2.0 feed of incident updates.
5. Admin at /admin behind a single bearer token from ADMIN_TOKEN in .env, checked with a constant-time compare. Forms to: set component status, open an incident, append an update, resolve it. HTML forms, no JS framework.
6. Subscribers: POST /subscribe takes an email, stores it unconfirmed with a random token, emails a confirm link. GET /confirm/:token confirms. Every email includes a working GET /unsubscribe/:token link. When an admin appends an incident update, queue one email per confirmed subscriber and send them sequentially with a small delay, logging failures to a sends table. Use Resend via fetch with RESEND_API_KEY from .env; if the key is absent, write the emails to ./outbox/ as .txt files instead so it runs offline.
7. Optional poller: a setInterval that HTTP GETs each component's check_url if set, marks degraded on slow responses over 2s and major_outage on non-2xx or timeout, and writes daily_status.

In scope: SQLite schema created on boot with migrations as plain SQL strings, seed script with three components and one resolved incident, a README that says in one sentence to deploy this somewhere other than the infrastructure it monitors.

Out of scope, do not build: multi-tenancy, SMS, Slack or Discord integrations, OAuth, custom domain automation, an analytics or telemetry call of any kind, Docker, a JS bundler.

Secrets only in .env, with a .env.example committed and .env gitignored. Include npm scripts: dev, seed, poll. It must run with npm install and npm run dev on a clean machine.

## Delivery standard

- Inspect the repository first, then write a short implementation plan before writing code.
- Deliver the smallest complete end-to-end workflow first; every primary control must work against persisted data.
- Use real validation and storage; never substitute fake dashboards, decorative controls, hard-coded success states, or mock integrations.
- Include responsive layouts plus genuine empty, loading, success, validation, and failure states.
- Keep secrets server-side in environment variables, provide .env.example, and never commit credentials or user data.
- Add structured logs around every external call and return actionable errors without leaking sensitive details.
- Write unit tests for the core logic and one automated test of the main user journey.
- Finish with a README covering setup, architecture, data location, backups, tests, deployment, and known limitations.

## Acceptance criteria

- [ ] A clean install starts the app using only the README and .env.example.
- [ ] The primary journey works from first visit through saved result, reload, edit, export, and deletion where applicable.
- [ ] Invalid input, missing configuration, provider failure, and an empty database each have a usable state.
- [ ] The interface works at 390px and 1440px, is keyboard navigable, and shows visible focus on every control.
- [ ] Tests, type checking, linting, and a production build all pass with no ignored failures.
- [ ] No part of the interface implies a live integration, security guarantee, or scale capability that was not actually built and verified.

## Non-goals

Do not build these, and do not claim to have replaced them:

- Deliverability someone else warms up and monitors: your first incident email is also your first send reputation test.
- Notification channels beyond email: SMS, Slack app, Teams, Discord, native webhooks, RSS, all of which Instatus ships out of the box.
- Per-component subscriptions, unsubscribe handling, and double opt-in that you do not have to think about.
- The polish: scheduled maintenance windows, incident templates, timezone handling, embeddable widgets, public API.
- Real independence, unless you actually do the work to host the page away from your own stack.

## What you still own after launch

- Run migrations, backups, restores, and dependency updates.
- Test the critical journey after every model, API, or hosting change.
- Monitor failures and fix the edge cases a first prompt will miss.

## Risk

**Operational risk.** The code is achievable; dependable data, integrations, and ongoing operations are the real cost.

Editorial confidence in this assessment: medium. No reviewed project implementation is linked yet.

---

Generated by [Can It Be Vibe Coded?](https://www.canitbevibecoded.com) · Full report: https://www.canitbevibecoded.com/instatus
