Skip to main content

Status monitor (POC)

Run the monitor as a separate process from the application:

pnpm --filter @parcelis/jobs build
pnpm --filter @parcelis/email build
pnpm status

For local editing, use pnpm status:dev instead. It watches the monitor and its imported code, restarting automatically after saves. Refresh the browser to see layout or script changes. Service results update in place every 15 seconds. Each process restart resets the in-memory check history. Changes to compiled workspace packages still require rebuilding those packages.

Open http://localhost/status through the local web proxy, or http://127.0.0.1:40020 directly. /status/api/status returns the same sanitized results as JSON through either origin. The monitor must be running separately. The page uses the sign-in background with the Parcelis full logo and heading on the left and one shared status card on the right. The heading sits beneath the logo, aligned to its right edge; the description and monitoring badge are centered below. Narrow screens stack the columns. Compact service rows show colored badges, session uptime, and up to 90 recent check ticks. Gray slots indicate checks that have not been recorded. The monitor serves these existing brand assets itself, independently of the web app. Use the sun, moon, and monitor icons in the top-right corner for Light, Dark, or System, matching the sign-in page. The selection is saved in a browser cookie and survives automatic refreshes. System follows the device appearance; dark mode uses the matching Parcelis logo and background. Service results refresh without navigating away or closing expanded check details. If the monitor is temporarily unreachable, the page retains its last results and retries. The monitor reads the root .env and checks every 30 seconds after the previous cycle finishes. Restart the worker after updating it to enable heartbeats and test jobs.

Six component rows contain expandable check details:

ComponentChecks
Web applicationPage response, CSRF endpoint, sign-in, and session validation
APIHealth response, rejection of anonymous protected requests, and authenticated account lookup
PostgreSQLSELECT 1
RedisAuthenticated PING
Object storageBucket access plus signed upload/download and cleanup of a unique test object
BullMQ / workersHeartbeats, harmless jobs on both active queues, global SMTP fallback, scheduled reconciliation, outbox polling, and backlog

A failed core check (page/API response, bucket access, or job processing) makes the component unavailable after three failed observations since its last operational result. A failed secondary check makes it degraded. An unmeasured check makes it unknown unless another check has already established degradation or an outage. The overall headline follows unavailable → degraded → unknown → operational, so missing evidence never hides a measured failure. Open Check details to see which check needs attention. Both component and underlying check transitions are logged and included in optional webhook alerts. All component results are stored before webhooks are sent concurrently with a three-second timeout.

The API's public GET /api/v1/ready separately checks PostgreSQL, Redis, and storage. It returns 200 with status: "ok" or 503 with status: "unavailable", without component details. Concurrent requests share one probe run, and its result is cached inside each API process for two seconds after completion; HTTP responses use Cache-Control: no-store. The existing /api/v1/health remains a liveness check. Worker readiness does not affect API readiness.

Optional monitor environment variables:

VariableDefault
STATUS_HOST127.0.0.1
STATUS_PORT40020
STATUS_INTERNAL_URLWeb proxy upstream; http://localhost:40020 (uses STATUS_PORT when set)
STATUS_WEB_URLhttp://localhost:30000/login
STATUS_API_URLhttp://localhost:40010/api/v1/health
STATUS_WEB_EXPECTEDParcelis
STATUS_AUTH_EMAILUnset; dedicated active monitoring account
STATUS_AUTH_PASSWORDUnset; monitoring account password
STATUS_ALERT_WEBHOOK_URLDisabled; optional JSON POST on component status changes

Use actual selected dev ports or externally reachable deployment URLs. The monitor also needs the application's database, Redis, and S3 environment settings and bucket inspection permission, plus upload, download, and delete access under healthchecks/. Keep those credentials on the monitor server. The browser receives only component/check names, status, timing, static explanations, and recent samples. The standalone monitor defaults missing REDIS_HOST and REDIS_PORT to localhost and 63790; Redis credentials are still required.

Checks have a five-second response deadline. Worker heartbeats expire after 60 seconds; processing canaries must complete within 1.5 seconds per queue; the global SMTP fallback canary allows three seconds. Three failed observations since the last operational result confirm an outage; the first two show degraded. Unknown and degraded samples preserve the failure count. Success recovers immediately. Results older than 90 seconds become unknown, including their child checks.

Set STATUS_AUTH_EMAIL and STATUS_AUTH_PASSWORD in the monitor environment to an existing dedicated active account. The monitor signs in through the web app, validates its session, and uses that session for /trpc/auth.me on the API. Without these credentials, the sign-in and authenticated-request checks show unknown; page responses, CSRF availability, and anonymous-request enforcement are still checked. No account is created automatically. Use a monitoring account to avoid repeatedly attempting sign-in with a person's credentials.

The Global SMTP fallback check verifies the worker’s environment-configured SMTP connection and authentication using a separate transport with a two-second deadline. It sends no test messages. Missing, incomplete, or invalid global SMTP configuration shows unknown; connection or authentication failure degrades the worker component. This does not test per-organization SMTP settings or delivery into a recipient's inbox. Outbox heartbeats publish after successful polls at most once every 15 seconds. A write gets a one-second wait, logs a distinct heartbeat error, and cannot hold up dispatch or shutdown indefinitely; overlapping pending writes are suppressed. Scheduled-job health requires the registered lease reconciliation scheduler and a successful scheduler-generated execution within the last three minutes, with its next iteration advanced. Manually enqueued reconciliation jobs do not refresh this signal. Both the scheduled time and completion time must be recent. Rebuild @parcelis/jobs and restart workers when updating the heartbeat format; old scalar heartbeats remain unhealthy until a new scheduled run completes. A newly started worker may need one scheduler interval (about a minute) before this check becomes operational.

This POC stores up to 2,880 samples per component in memory; history resets on restart. The displayed percentage counts fully operational samples among samples with a known status during that session (unknown samples are excluded), not a contractual uptime measure. Canary jobs test Redis-to-worker processing without sending email or changing leases. Storage creates a small unique object, reads its contents through signed URLs at the public storage endpoint, and attempts cleanup even if upload or download fails. A cleanup failure degrades storage. Only this probe's generated object is deleted. Signing and transfer share a three-second deadline; deletion gets 1.5 seconds. Both clients are released even if signing stalls. Each canary uses a private Redis connection, bounded enqueue/state/result reads, and bounded removal/forced-close cleanup. A query that exceeds the response deadline may continue until the database driver times out.

For deployment, run the monitor on a separate host and put HTTPS in front of it. Co-locating it with Parcelis leaves shared host outages undetectable from that page. Durable history, incident editing, alert retries, and monitoring the monitor itself remain production follow-up work.