Yet Another Status Page
A modern, self-hosted status page built with Payload CMS and Next.js.
Features
- π¨ Incident Management - Track and communicate service disruptions
- π§ Scheduled Maintenance - Plan and notify users about upcoming maintenance
- π§ Email & SMS Notifications - Automatic subscriber notifications via SMTP and Twilio
- π Service Groups - Organize services into logical groups
- π¨ Beautiful UI - Modern, responsive status page with dark mode support
- π Self-Hosted - Full control over your data and infrastructure
- π³ Docker Ready - Easy deployment with Docker and Docker Compose
Quick Start
# Clone the repository
git clone https://github.com/Hostzero-GmbH/yet-another-status-page.git
cd yet-another-status-page
# Start with Docker Compose
docker compose up -d
Visit http://localhost:3000 to see your status page, and http://localhost:3000/admin to access the admin panel.
Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Yet Another Status Page β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Frontend (Next.js) β Admin Panel (Payload CMS) β
β - Status Page β - Manage Services β
β - Incident History β - Create Incidents β
β - Subscribe Form β - Schedule Maintenances β
β β - Send Notifications β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β PostgreSQL Database β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Documentation
- Installation Guide - Get started with Yet Another Status Page
- Docker Compose Setup - Deploy with Docker
- Admin Guide - Learn how to manage your status page
- Notification Workflow - Understand the notification system
Installation
Yet Another Status Page can be deployed in several ways. Choose the method that best fits your infrastructure.
Prerequisites
- Docker and Docker Compose (recommended)
- OR Node.js 20+ and PostgreSQL 15+
Deployment Options
Option 1: Vercel (One-Click)
Deploy instantly to Vercel with a managed PostgreSQL database:
This will:
- Create a new Vercel project
- Provision a Vercel Postgres database
- Prompt you to set
PAYLOAD_SECRET(generate a random 32+ character string)
All configuration (site name, logos, services, notifications) is done through the admin panel β no code changes required.
Option 2: Docker Compose (Recommended for Self-Hosting)
The easiest way to self-host. See the Docker Compose guide for detailed instructions.
# Clone the repository
git clone https://github.com/Hostzero-GmbH/yet-another-status-page.git
cd yet-another-status-page
# Copy the example environment file
cp .env.example .env
# Edit the environment variables
nano .env
# Start the services
docker compose up -d
Option 3: Pre-built Docker Image
Pull the latest image from GitHub Container Registry:
docker pull ghcr.io/hostzero-gmbh/status-page:latest
Run with your own PostgreSQL:
docker run -d \
--name status-page \
-p 3000:3000 \
-e DATABASE_URI=postgres://user:pass@host:5432/db \
-e PAYLOAD_SECRET=your-secret-key \
-e SERVER_URL=https://status.example.com \
ghcr.io/hostzero-gmbh/status-page:latest
Option 4: Build from Source
# Clone the repository
git clone https://github.com/Hostzero-GmbH/yet-another-status-page.git
cd yet-another-status-page
# Install dependencies
npm install
# Build the application
npm run build
# Start the production server
npm start
First-Time Setup
-
Access the Admin Panel
Navigate to
http://your-server:3000/admin -
Create Admin User
On first access, youβll be prompted to create an admin account.
-
Configure Settings
Configure your status page in the admin panel:
- Configuration β Site Settings: Site name, description, favicon, logos
- Configuration β Email Settings: SMTP settings for email notifications
- Configuration β SMS Settings: Twilio settings for SMS notifications
-
Add Services
Create service groups and services that represent your infrastructure.
-
Go Live
Your status page is now accessible at
http://your-server:3000
Docker Compose Setup
This guide explains how to deploy Yet Another Status Page using Docker Compose.
Quick Start
1. Clone the Repository
git clone https://github.com/Hostzero-GmbH/yet-another-status-page.git
cd yet-another-status-page
2. Create Environment File
cp .env.example .env
Edit .env with your configuration:
# Database
DATABASE_URI=postgres://hostzero:your-secure-password@db:5432/hostzero_status
POSTGRES_PASSWORD=your-secure-password
# Security
PAYLOAD_SECRET=your-32-character-secret-key-here
# URLs
SERVER_URL=https://status.yourdomain.com
Note: Email (SMTP) and SMS (Twilio) settings are configured via the admin panel under Configuration β Email Settings and Configuration β SMS Settings, not through environment variables.
3. Start the Services
docker compose up -d
4. Access the Application
- Status Page: http://localhost:3000
- Admin Panel: http://localhost:3000/admin
Docker Compose File
Create a docker-compose.yml in your project root:
version: '3.8'
services:
app:
image: ghcr.io/hostzero-gmbh/status-page:latest
# Or build from source:
# build:
# context: ./cms
# dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- DATABASE_URI=${DATABASE_URI}
- PAYLOAD_SECRET=${PAYLOAD_SECRET}
- SERVER_URL=${SERVER_URL}
depends_on:
db:
condition: service_healthy
restart: unless-stopped
volumes:
- uploads:/app/public/media
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=hostzero
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=hostzero_status
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U hostzero -d hostzero_status"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
postgres_data:
uploads:
Production Deployment
With Traefik (Recommended)
version: '3.8'
services:
app:
image: ghcr.io/hostzero-gmbh/status-page:latest
environment:
- DATABASE_URI=${DATABASE_URI}
- PAYLOAD_SECRET=${PAYLOAD_SECRET}
- SERVER_URL=https://status.yourdomain.com
depends_on:
db:
condition: service_healthy
restart: unless-stopped
volumes:
- uploads:/app/public/media
labels:
- "traefik.enable=true"
- "traefik.http.routers.status.rule=Host(`status.yourdomain.com`)"
- "traefik.http.routers.status.entrypoints=websecure"
- "traefik.http.routers.status.tls.certresolver=letsencrypt"
- "traefik.http.services.status.loadbalancer.server.port=3000"
networks:
- traefik
- internal
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=hostzero
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=hostzero_status
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U hostzero -d hostzero_status"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- internal
volumes:
postgres_data:
uploads:
networks:
traefik:
external: true
internal:
With nginx
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- app
restart: unless-stopped
app:
image: ghcr.io/hostzero-gmbh/status-page:latest
environment:
- DATABASE_URI=${DATABASE_URI}
- PAYLOAD_SECRET=${PAYLOAD_SECRET}
- SERVER_URL=https://status.yourdomain.com
depends_on:
db:
condition: service_healthy
restart: unless-stopped
volumes:
- uploads:/app/public/media
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=hostzero
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=hostzero_status
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U hostzero -d hostzero_status"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
postgres_data:
uploads:
Updating
To update to the latest version:
# Pull the latest image
docker compose pull
# Restart with the new image
docker compose up -d
Backup & Restore
Backup Database
docker compose exec db pg_dump -U hostzero hostzero_status > backup.sql
Restore Database
cat backup.sql | docker compose exec -T db psql -U hostzero hostzero_status
Backup Uploads
docker compose cp app:/app/public/media ./media-backup
Configuration
Yet Another Status Page is configured through environment variables and the admin panel.
Environment Variables
Required
| Variable | Description | Example |
|---|---|---|
DATABASE_URI | PostgreSQL connection string | postgres://user:pass@host:5432/db |
PAYLOAD_SECRET | Secret key for encryption (min 32 chars) - Generate one | your-super-secret-key-here-32ch |
SERVER_URL | Public URL of your status page | https://status.example.com |
Note: On Vercel, both
POSTGRES_URLandSERVER_URLare automatically detected:
POSTGRES_URLis set when you add a Vercel Postgres databaseSERVER_URLfalls back toVERCEL_PROJECT_PRODUCTION_URLorVERCEL_URLif not explicitly setThe app supports both
DATABASE_URIandPOSTGRES_URLfor database connections.
Optional
| Variable | Description | Default |
|---|---|---|
PORT | Server port | 3000 |
NODE_ENV | Environment mode | production |
SSO/OIDC Authentication (Optional)
Enable Single Sign-On with any OIDC-compliant identity provider (Keycloak, Okta, Auth0, Azure AD, Google).
| Variable | Description | Default |
|---|---|---|
OIDC_CLIENT_ID | OAuth2 client ID | - |
OIDC_CLIENT_SECRET | OAuth2 client secret | - |
OIDC_AUTH_URL | Authorization endpoint | - |
OIDC_TOKEN_URL | Token endpoint | - |
OIDC_USERINFO_URL | User info endpoint | - |
OIDC_SCOPES | OAuth scopes | openid profile email |
OIDC_AUTO_CREATE | Create users on first login | true |
OIDC_ALLOWED_GROUPS | Comma-separated list of allowed groups | (allow all) |
OIDC_GROUP_CLAIM | Claim name containing groups | groups |
OIDC_DISABLE_LOCAL_LOGIN | Disable password login (SSO-only) | false |
Provider-Specific URLs
Keycloak:
OIDC_AUTH_URL=https://keycloak.example.com/realms/{realm}/protocol/openid-connect/auth
OIDC_TOKEN_URL=https://keycloak.example.com/realms/{realm}/protocol/openid-connect/token
OIDC_USERINFO_URL=https://keycloak.example.com/realms/{realm}/protocol/openid-connect/userinfo
Okta:
OIDC_AUTH_URL=https://{domain}.okta.com/oauth2/default/v1/authorize
OIDC_TOKEN_URL=https://{domain}.okta.com/oauth2/default/v1/token
OIDC_USERINFO_URL=https://{domain}.okta.com/oauth2/default/v1/userinfo
Auth0:
OIDC_AUTH_URL=https://{tenant}.auth0.com/authorize
OIDC_TOKEN_URL=https://{tenant}.auth0.com/oauth/token
OIDC_USERINFO_URL=https://{tenant}.auth0.com/userinfo
Azure AD:
OIDC_AUTH_URL=https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
OIDC_TOKEN_URL=https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
OIDC_USERINFO_URL=https://graph.microsoft.com/oidc/userinfo
Google:
OIDC_AUTH_URL=https://accounts.google.com/o/oauth2/v2/auth
OIDC_TOKEN_URL=https://oauth2.googleapis.com/token
OIDC_USERINFO_URL=https://openidconnect.googleapis.com/v1/userinfo
Callback URL
When configuring your identity provider, set the callback/redirect URL to:
https://your-status-page.com/api/users/oauth/callback
Group-Based Access Control
To restrict access to specific groups from your identity provider:
- Configure your IdP to include group claims in the userinfo response
- Set
OIDC_ALLOWED_GROUPSto a comma-separated list of allowed groups - If your IdP uses a different claim name, set
OIDC_GROUP_CLAIM
Example Keycloak Setup:
- Create a client scope named βgroupsβ with a Group Membership mapper:
- Token Claim Name:
groups - Add to userinfo: On
- Token Claim Name:
- Add the scope to your client
- Configure the status page:
OIDC_SCOPES=openid profile email groups
OIDC_ALLOWED_GROUPS=status-page-admins,status-page-editors
SSO-Only Mode
To disable password login and require SSO for all users:
OIDC_DISABLE_LOCAL_LOGIN=true
Warning: Ensure SSO is working correctly before enabling this option, or you may lock yourself out!
Admin Panel Settings
The admin panel has three configuration sections under Configuration:
Site Settings
Access Configuration β Site Settings to configure:
- Site Name: Displayed in the header and emails
- Site Description: Meta description for SEO
- Favicon: Custom favicon for your status page
- Logos: Light and dark theme logos
- SEO: Meta titles and descriptions
- Status Override: Maintenance mode and custom messages
Email Settings
Access Configuration β Email Settings to configure email notifications:
| Setting | Description |
|---|---|
| Enable Email Subscriptions | Allow users to subscribe via email |
| SMTP Host | Your mail server hostname |
| SMTP Port | Usually 587 (TLS) or 465 (SSL) |
| SMTP Security | None, TLS, or SSL |
| SMTP Username | Authentication username |
| SMTP Password | Authentication password |
| From Address | Sender email address |
| From Name | Sender display name |
| Reply-To | Reply-to address (optional) |
SMS Settings
Access Configuration β SMS Settings to configure SMS notifications:
| Setting | Description |
|---|---|
| Enable SMS Subscriptions | Allow users to subscribe via SMS |
| Account SID | Your Twilio Account SID |
| Auth Token | Your Twilio Auth Token |
| From Number | Your Twilio phone number (required if not using Messaging Service) |
| Messaging Service SID | Alternative to From Number for better deliverability |
SMS Templates
You can customize the SMS message templates with these placeholders:
| Placeholder | Description |
|---|---|
{{siteName}} | Your site name from Site Settings |
{{title}} | Incident or maintenance title |
{{status}} | Current status (e.g., Investigating, Resolved) |
{{message}} | Update message content |
{{schedule}} | Maintenance schedule (maintenance only) |
{{url}} | Link to the incident/maintenance page |
Available templates:
- New Incident Template - For initial incident notifications
- Incident Update Template - For incident status updates
- New Maintenance Template - For scheduled maintenance announcements
- Maintenance Update Template - For maintenance status updates
You can also configure Title Max Length and Message Max Length to control truncation.
Testing Notifications
After configuring SMTP or Twilio:
- Create a test subscriber in Notifications β Subscribers
- Create a test incident in Status β Incidents
- Check the Notifications collection for the auto-generated draft
- Click Send Notification Now to test
Security Recommendations
- Use strong secrets: Generate a random 32+ character string for
PAYLOAD_SECRET - Use HTTPS: Always deploy behind HTTPS in production
- Secure database: Use strong passwords and restrict database access
- Regular backups: Schedule regular database backups
Admin Overview
The Yet Another Status Page admin panel is powered by Payload CMS and provides a comprehensive interface for managing your status page.
Dashboard
The dashboard shows at-a-glance metrics:
- Active Incidents - Current unresolved incidents
- Upcoming Maintenances - Scheduled or in-progress maintenance windows
- Draft Notifications - Notifications waiting to be sent
- Scheduled Notifications - Notifications being processed
- Email Subscribers - Active email subscribers
- SMS Subscribers - Active SMS subscribers
Navigation
The admin panel is organized into sections:
Status
- Service Groups - Logical groupings of services
- Services - Individual services to monitor
- Incidents - Service disruptions and issues
- Maintenances - Scheduled maintenance windows
Notifications
- Notifications - Manage and send notifications
- Subscribers - Manage subscriber list
Admin
- Users - Admin user accounts
- Media - Uploaded files and images
Configuration
- Site Settings - Site name, branding, SEO, status overrides
- Email Settings - SMTP configuration and email subscriptions
- SMS Settings - Twilio configuration, SMS subscriptions, and message templates
Workflow Overview
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. Create Services β
β Define your infrastructure components β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. Incident Occurs β
β Create incident β Notification draft auto-created β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. Review & Send β
β Go to Notifications β Review β Send β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. Add Updates β
β Post updates β New notification drafts auto-created β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 5. Resolve β
β Mark resolved β Final notification β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Quick Actions
Creating an Incident
- Go to Status β Incidents
- Click Create New
- Fill in the title, affected services, status, and impact
- Click Save
- A notification draft is automatically created
Scheduling Maintenance
- Go to Status β Maintenances
- Click Create New
- Set the title, affected services, and schedule
- Click Save
- A notification draft is automatically created
Sending Notifications
- Go to Notifications β Notifications
- Find the draft notification
- Review and edit the content if needed
- Click Send Notification Now
Managing Services
Services represent the components of your infrastructure that you want to display on the status page.
Service Groups
Service groups organize related services together.
Creating a Service Group
- Go to Status β Service Groups
- Click Create New
- Enter a name (e.g., βCore Infrastructureβ, βAPI Servicesβ)
- Optionally add a description
- Click Save
Ordering Groups
Drag and drop service groups to reorder them on the status page.
Services
Services are individual components within a group.
Creating a Service
- Go to Status β Services
- Click Create New
- Fill in:
- Name - Display name (e.g., βAPI Gatewayβ)
- Description - Brief description
- Service Group - Which group it belongs to
- Status - Current operational status
- Click Save
Service Statuses
| Status | Color | Description |
|---|---|---|
| Operational | π’ Green | Service is working normally |
| Degraded Performance | π‘ Yellow | Service is slow or partially impaired |
| Partial Outage | π Orange | Some functionality unavailable |
| Major Outage | π΄ Red | Service is completely unavailable |
| Under Maintenance | π΅ Blue | Service is undergoing planned maintenance |
Automatic Status Updates
Service status is automatically updated when:
- An incident is created affecting the service
- An incident is resolved
- A maintenance window starts or ends
You can also manually update the status at any time.
Best Practices
Naming
- Use clear, user-facing names
- Avoid internal jargon
- Be consistent with naming conventions
Grouping
- Group by function (e.g., βCoreβ, βAPIsβ, βIntegrationsβ)
- Keep groups manageable (5-10 services each)
- Consider your usersβ perspective
Granularity
- Not too broad (users need to know whatβs affected)
- Not too narrow (too many services is overwhelming)
- Aim for 10-30 total services for most deployments
Managing Incidents
Incidents represent unplanned service disruptions or issues affecting your infrastructure.
Creating an Incident
- Go to Status β Incidents
- Click Create New
- Fill in the incident details:
- Title - Brief description (e.g., βAPI Gateway Latency Issuesβ)
- Affected Services - Select impacted services
- Status - Current investigation status
- Impact - Severity level
- Click Save
A notification draft is automatically created when you save.
Incident Statuses
| Status | Description |
|---|---|
| Investigating | Issue detected, investigating cause |
| Identified | Root cause identified, working on fix |
| Monitoring | Fix applied, monitoring for stability |
| Resolved | Issue fully resolved |
Status Flow
Investigating β Identified β Monitoring β Resolved
You can skip statuses if appropriate (e.g., go directly to Resolved for quick fixes).
Impact Levels
| Impact | Description | Display |
|---|---|---|
| Operational | No user impact (informational) | π’ Green |
| Degraded Performance | Slower than normal | π‘ Yellow |
| Partial Outage | Some functionality unavailable | π Orange |
| Major Outage | Service completely unavailable | π΄ Red |
Adding Updates
As the incident progresses, add updates to the timeline:
- Open the incident
- Scroll to Updates
- Click Add Update
- Fill in:
- Status - Current status
- Message - Update details
- Created At - When this update occurred
- Click Save
A new notification draft is automatically created for each update.
Resolving an Incident
- Open the incident
- Change Status to βResolvedβ
- The Resolved At timestamp is automatically set
- Click Save
- Review and send the final notification
Incident Permalinks
Each incident gets a unique short ID (e.g., abc123) that creates a permanent link:
https://status.example.com/i/abc123
This link is included in notifications and remains valid even if the title changes.
Best Practices
Titles
- Be specific but concise
- Include the affected component
- Avoid blame or technical jargon
Good: βPayment Processing Delaysβ Bad: βDatabase server crashed due to OOM killerβ
Updates
- Post updates every 30-60 minutes during active incidents
- Be honest about what you know and donβt know
- Set expectations for next update
Resolution
- Confirm the issue is fully resolved before closing
- Include a brief summary of what happened
- Thank users for their patience
Example Timeline
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β API Gateway Latency Issues β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β π‘ Investigating - 10:00 AM β
β We are investigating reports of slow API responses. β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β π‘ Identified - 10:30 AM β
β Root cause identified as a misconfigured load balancer. β
β Our team is implementing a fix. β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β π’ Monitoring - 11:00 AM β
β Fix deployed. We are monitoring for stability. β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β π’ Resolved - 11:30 AM β
β This incident has been resolved. API response times β
β have returned to normal. β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Managing Maintenances
Maintenances represent planned service interruptions or maintenance windows.
Creating a Maintenance
- Go to Status β Maintenances
- Click Create New
- Fill in the maintenance details:
- Title - Brief description (e.g., βDatabase Migrationβ)
- Description - Detailed explanation (optional)
- Affected Services - Select impacted services
- Scheduled Start - When maintenance begins
- Scheduled End - When maintenance ends (optional)
- Duration - Human-readable duration (e.g., β~2 hoursβ)
- Click Save
A notification draft is automatically created when you save.
Maintenance Statuses
| Status | Description |
|---|---|
| Upcoming | Scheduled but not yet started |
| In Progress | Currently underway |
| Completed | Successfully finished |
| Cancelled | Maintenance was cancelled |
Auto-Status Updates
Enable automatic status transitions:
- Auto-start on schedule - Automatically changes to βIn Progressβ when the scheduled start time is reached
- Auto-complete on schedule - Automatically changes to βCompletedβ when the scheduled end time is reached
These can be enabled/disabled per maintenance.
Adding Updates
During maintenance, add updates to keep users informed:
- Open the maintenance
- Scroll to Updates
- Click Add Update
- Fill in:
- Status - Current status
- Message - Progress update
- Created At - When this update occurred
- Click Save
A new notification draft is automatically created for each update.
Maintenance Permalinks
Each maintenance gets a unique short ID (e.g., xyz789) that creates a permanent link:
https://status.example.com/m/xyz789
Notification Content
Initial Notification (Email)
A maintenance window has been scheduled.
Scheduled Start: Sat, Jan 11 at 2:00 AM
Scheduled End: Sat, Jan 11 at 4:00 AM
Expected Duration: ~2 hours
We will notify you when the maintenance begins and completes.
View full details: https://status.example.com/m/xyz789
Initial Notification (SMS)
π§ MAINTENANCE: Database Migration
π
Sat, Jan 11 at 2:00 AM - Sat, Jan 11 at 4:00 AM
We will notify you when maintenance begins and completes.
Details: https://status.example.com/m/xyz789
Best Practices
Scheduling
- Schedule during low-traffic periods
- Give users at least 24-48 hours notice
- Avoid scheduling during holidays or major events
Communication
- Be clear about what will be affected
- Provide estimated duration
- Notify at key milestones (start, 50%, complete)
Timing
- Send initial notification 24-48 hours before
- Send reminder 1-2 hours before
- Send βstartedβ notification when beginning
- Send βcompletedβ notification when done
Example Timeline
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Database Migration β
β Scheduled: Jan 11, 2:00 AM - 4:00 AM EST β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β π
Scheduled - Jan 9, 10:00 AM β
β Scheduled maintenance for database migration. β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β π§ In Progress - Jan 11, 2:00 AM β
β Maintenance has begun. Services may be unavailable. β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β π§ In Progress - Jan 11, 3:00 AM β
β Migration 75% complete. On track for scheduled end. β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
Completed - Jan 11, 3:45 AM β
β Maintenance completed successfully. All services β
β have been restored. β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Notification Workflow
Yet Another Status Page includes a powerful notification system that automatically creates notification drafts and allows you to review before sending.
How It Works
ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
β Create/Update β βββΆ β Draft Created β βββΆ β Review & Send β
β Incident or β β Automatically β β from Admin β
β Maintenance β β β β β
ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
Automatic Draft Creation
Notification drafts are automatically created when:
- New Incident Created - A draft with incident details is created
- Incident Updated - When you add an update to the timeline, a new draft is created
- New Maintenance Scheduled - A draft with schedule details is created
- Maintenance Updated - When you add an update, a new draft is created
Manual Review & Send
Notifications are never sent automatically. You must:
- Go to Notifications β Notifications
- Review the draft content
- Edit if needed
- Click Send Notification Now
This gives you full control over what gets sent to subscribers.
Notification Statuses
| Status | Description |
|---|---|
| Draft | Created but not sent. Can be edited. |
| Scheduled | Being processed for sending. |
| Sent | Successfully delivered to subscribers. |
| Failed | Sending failed. Can retry. |
Notification Channels
Each notification can be sent via:
- Email - Sends to email subscribers only
- SMS - Sends to SMS subscribers only
- Both - Sends to all subscribers
Email Notifications
Content
Email notifications include:
- Subject line
- Formatted HTML body
- Call-to-action button linking to the status page
- Unsubscribe link (required for compliance)
Headers
Emails automatically include:
List-Unsubscribeheader for one-click unsubscribeList-Unsubscribe-Postheader for RFC 8058 compliance
Configuration
Configure SMTP in Configuration β Email Settings:
- Enable Email Subscriptions toggle
- SMTP Host, Port, Security
- Authentication credentials
- From address and name
SMS Notifications
Content
SMS messages are generated from customizable templates and include:
- Site name prefix
- Emoji indicator (π¨ incident, π§ maintenance)
- Title and status
- Scheduled times (for maintenance)
- Link to status page
Configuration
Configure Twilio in Configuration β SMS Settings:
- Enable SMS Subscriptions toggle
- Account SID
- Auth Token
- From phone number OR Messaging Service SID
SMS Templates
You can customize SMS message templates in Configuration β SMS Settings under the βSMS Templatesβ section. Available placeholders:
{{siteName}}- Your site name{{title}}- Incident/maintenance title{{status}}- Current status{{message}}- Update message{{schedule}}- Maintenance schedule{{url}}- Link to the page
Configure Title Max Length and Message Max Length to control how content is truncated to fit SMS limits.
Recipient Count
The notification form shows the estimated recipient count based on:
- Selected channel (Email/SMS/Both)
- Active subscribers matching that channel
After sending, it shows the actual number of recipients.
Retrying Failed Notifications
If a notification fails:
- The error message is displayed in the notification form
- The Retry Send button allows you to attempt again
- Fix any configuration issues before retrying
Common failure reasons:
- SMTP not configured
- Twilio not configured
- Invalid credentials
- Network issues
Best Practices
Writing Notifications
- Be concise - Get to the point quickly
- Include impact - What services are affected?
- Set expectations - When will it be resolved?
- Provide updates - Keep subscribers informed
Timing
- Send promptly - Notify as soon as youβre aware
- Update regularly - Post updates at least hourly during incidents
- Confirm resolution - Always send a final βresolvedβ notification
Testing
- Create a test subscriber (your email/phone)
- Create a test incident
- Send the notification to verify delivery
- Delete test data when done
Subscribers
Managing Subscribers
Go to Notifications β Subscribers to:
- View all subscribers
- Add subscribers manually
- Deactivate subscribers
- See subscription type (email/SMS)
Subscription Types
- Email - Requires valid email address
- SMS - Requires phone number with country code
Active vs Inactive
- Active - Will receive notifications
- Inactive - Opted out or deactivated
Subscribers can unsubscribe via the link in emails, which sets them to inactive.
Automation & Jobs Queue
Notifications are sent via a background jobs queue:
- Prevents timeouts for large subscriber lists
- Automatic retries on failure (up to 3 attempts)
- Progress tracking in the notification status
The queue processes immediately in development and can be scaled with workers in production.
Managing Subscribers
Subscribers receive notifications about incidents and maintenance windows.
Subscription Types
| Type | Description |
|---|---|
| Receives notifications via email | |
| SMS | Receives notifications via text message |
Each subscriber has one type. Users who want both should create two subscriptions.
Adding Subscribers
Manual Addition
- Go to Notifications β Subscribers
- Click Create New
- Fill in:
- Type - Email or SMS
- Email - Email address (for email type)
- Phone - Phone number with country code (for SMS type)
- Verified - Whether the subscription is verified
- Active - Whether to send notifications
- Click Save
Public Subscription
Users can subscribe via the public status page:
- Click the βSubscribeβ button on the status page
- Enter their email or phone number
- They appear in the Subscribers list
Subscriber Fields
| Field | Description |
|---|---|
| Type | Email or SMS |
| Email address (for email subscribers) | |
| Phone | Phone number with country code (for SMS) |
| Verified | Whether the subscription is verified |
| Active | Whether to receive notifications |
| Verification Token | Auto-generated token for verification |
| Unsubscribe Token | Auto-generated token for unsubscribe links |
Active vs Inactive
- Active - Subscriber will receive notifications
- Inactive - Subscriber will NOT receive notifications
Subscribers become inactive when:
- They click the unsubscribe link in an email
- An admin manually deactivates them
Unsubscribe Flow
Each email includes an unsubscribe link:
https://status.example.com/unsubscribe/{token}
When clicked:
- User sees a confirmation message
- Subscription is set to inactive
- They no longer receive notifications
The unsubscribe link is unique per subscriber and doesnβt expire.
Phone Number Format
SMS phone numbers must include the country code:
- β
+14155551234(US) - β
+442071234567(UK) - β
+33123456789(France) - β
415-555-1234(missing country code) - β
(415) 555-1234(missing country code)
Verification
The Verified field indicates whether the email/phone has been confirmed.
For manually added subscribers, you can set this to true if youβve verified the contact information.
Bulk Operations
To deactivate multiple subscribers:
- Select subscribers in the list view
- Use bulk actions to update
Privacy Considerations
- Store only necessary contact information
- Provide easy unsubscribe options
- Respect unsubscribe requests immediately
- Consider data retention policies
Local Development Setup
This guide explains how to set up Yet Another Status Page for local development.
Prerequisites
- Node.js 20+
- PostgreSQL 15+ (or Docker)
- npm or pnpm
Quick Start with Docker
The easiest way to develop locally is using the included Docker Compose configuration.
# Clone the repository
git clone https://github.com/Hostzero-GmbH/yet-another-status-page.git
cd yet-another-status-page
# Start the development environment
docker compose -f docker-compose.dev.yml up -d postgres # Start only the database
# Install dependencies
npm install
# Run database migrations
npm run payload migrate
# Start the development server
npm run dev
Visit:
- Status page: http://localhost:3333
- Admin panel: http://localhost:3333/admin
Note: The dev compose file uses port 3333 to avoid conflicts with production on port 3000.
Manual Setup
1. Install PostgreSQL
# macOS with Homebrew
brew install postgresql@16
brew services start postgresql@16
# Create database
createdb hostzero_status
2. Clone and Install
git clone https://github.com/Hostzero-GmbH/yet-another-status-page.git
cd yet-another-status-page
npm install
3. Configure Environment
cp .env.example .env
Edit .env:
DATABASE_URI=postgres://localhost:5432/hostzero_status
PAYLOAD_SECRET=your-development-secret-key
SERVER_URL=http://localhost:3000
4. Run Migrations
npm run payload migrate
5. Start Development Server
npm run dev
Development Scripts
| Script | Description |
|---|---|
npm run dev | Start development server with hot reload |
npm run build | Build for production |
npm run start | Start production server |
npm run payload migrate | Run database migrations |
npm run payload generate:types | Generate TypeScript types |
npm run payload generate:importmap | Generate import map for custom components |
Project Structure
status-page/
βββ src/
β βββ app/ # Next.js App Router
β β βββ (frontend)/ # Public status pages
β β βββ (payload)/ # Admin panel
β β βββ api/ # API routes
β βββ collections/ # Payload CMS collections
β βββ components/ # React components
β β βββ admin/ # Admin panel components
β β βββ status/ # Status page components
β βββ globals/ # Payload CMS globals
β βββ lib/ # Utility functions
β βββ tasks/ # Background job handlers
βββ public/ # Static assets
βββ payload.config.ts # Payload CMS configuration
βββ tailwind.config.ts # Tailwind CSS configuration
Making Changes
Adding a Collection
- Create a new file in
src/collections/ - Export the collection config
- Import and add to
payload.config.ts - Run
npm run payload generate:types - Run migrations if needed
Adding a Custom Admin Component
- Create component in
src/components/admin/ - Reference it in the collection config
- Run
npm run payload generate:importmap
Adding an API Endpoint
- Create a route file in
src/app/api/ - Export GET, POST, etc. handlers
Testing
# Type checking
npm run typecheck
# Build test
npm run build
Debugging
Database Issues
# Connect to database
psql $DATABASE_URI
# Reset database
dropdb hostzero_status && createdb hostzero_status
npm run payload migrate
Clear Cache
rm -rf .next
npm run dev
Architecture
Yet Another Status Page is built with modern technologies for reliability and developer experience.
Tech Stack
| Component | Technology |
|---|---|
| Framework | Next.js 15 (App Router) |
| CMS | Payload CMS 3.x |
| Database | PostgreSQL |
| Styling | Tailwind CSS |
| Rich Text | Lexical Editor |
System Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Clients β
β (Browsers, API Consumers, Email Clients, SMS) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Next.js Application β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββ β
β β Status Pages β β Admin Panel β β REST API β β
β β (Frontend) β β (Payload CMS) β β Endpoints β β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Payload CMS Core β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β Collections β β Globals β β Jobs Queue β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PostgreSQL β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β External Services β
β βββββββββββββββββββ βββββββββββββββββββ β
β β SMTP Server β β Twilio β β
β β (Email) β β (SMS) β β
β βββββββββββββββββββ βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Data Model
Collections
βββββββββββββββββββ βββββββββββββββββββ
β ServiceGroups ββββββΆβ Services β
β - name β β - name β
β - description β β - status β
β - order β β - group β
βββββββββββββββββββ βββββββββββββββββββ
β
βββββββββββ΄ββββββββββ
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ
β Incidents β β Maintenances β
β - title β β - title β
β - status β β - status β
β - impact β β - schedule β
β - updates[] β β - updates[] β
βββββββββββββββββββ βββββββββββββββββββ
β β
βββββββββββ¬ββββββββββ
βΌ
βββββββββββββββββββ
β Notifications β
β - title β
β - channel β
β - status β
β - content β
βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Subscribers β
β - type β
β - email/phone β
β - active β
βββββββββββββββββββ
Globals
- Settings - Site configuration, SMTP, Twilio credentials
Request Flow
Status Page Request
Browser β Next.js β Server Component β Payload API β PostgreSQL
β
Rendered HTML
Admin Panel Request
Browser β Next.js β Payload Admin β Payload API β PostgreSQL
β
React SPA
Notification Flow
Save Incident β afterChange Hook β Create Notification Draft
β
Admin Reviews Draft
β
Click "Send Now"
β
API Queues Job
β
Jobs Queue Processes
β
SMTP/Twilio Sends
β
Update Notification Status
Key Design Decisions
Why Payload CMS?
- Modern, TypeScript-first CMS
- Excellent admin UI out of the box
- Flexible data modeling
- Built-in authentication
- Jobs queue for background tasks
Why Next.js App Router?
- Server components for performance
- Streaming and suspense support
- Built-in API routes
- Excellent developer experience
Why PostgreSQL?
- Robust and reliable
- Excellent JSON support
- Widely supported
- Easy to backup and scale
Why Separate Notifications Collection?
- Audit trail of all notifications
- Review before sending
- Retry failed notifications
- Clear status tracking
Scaling Considerations
Horizontal Scaling
- Application is stateless
- Can run multiple instances behind load balancer
- Shared PostgreSQL database
Database
- Connection pooling (PgBouncer)
- Read replicas for high traffic
- Regular backups
Jobs Queue
- Can add dedicated worker processes
- Automatic retries on failure
- Scales with subscriber count
REST API
Yet Another Status Page provides a REST API for programmatic access to status data.
Base URL
https://your-status-page.com/api
Authentication
Most read endpoints are public. Admin endpoints require authentication via:
- Session cookie (from admin login)
- API key header:
Authorization: Bearer <api-key>
Endpoints
Incidents
List Incidents
GET /api/incidents
Query parameters:
| Parameter | Description |
|---|---|
limit | Number of results (default: 10) |
page | Page number (default: 1) |
where[status][equals] | Filter by status |
Get Incident
GET /api/incidents/:id
Maintenances
List Maintenances
GET /api/maintenances
Query parameters same as incidents.
Get Maintenance
GET /api/maintenances/:id
Services
List Services
GET /api/services
Get Service
GET /api/services/:id
Service Groups
List Service Groups
GET /api/service-groups
Get Service Group
GET /api/service-groups/:id
Subscribers
Subscribe
POST /api/subscribe
Content-Type: application/json
{
"type": "email",
"email": "user@example.com"
}
Response:
{
"success": true,
"message": "Subscription successful"
}
Unsubscribe
POST /api/unsubscribe
Content-Type: application/json
{
"token": "unsubscribe-token"
}
Payload REST API
The full Payload REST API is available at /api. See the Payload documentation for complete details.
Common Patterns
Filtering
GET /api/incidents?where[status][equals]=investigating
Sorting
GET /api/incidents?sort=-createdAt
Pagination
GET /api/incidents?limit=10&page=2
Field Selection
GET /api/incidents?select[title]=true&select[status]=true
GraphQL
A GraphQL endpoint is available at:
POST /api/graphql
GraphQL Playground (development only):
GET /api/graphql-playground
Rate Limiting
Public endpoints are rate limited to prevent abuse:
- 100 requests per minute per IP for read endpoints
- 10 requests per minute per IP for subscribe endpoint
Error Responses
All errors follow this format:
{
"errors": [
{
"message": "Error description"
}
]
}
Common HTTP status codes:
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad request |
| 401 | Unauthorized |
| 404 | Not found |
| 429 | Rate limited |
| 500 | Server error |