@emitsignal/cli

EmitSignal CLI — publish and stream signals from your terminal


Keywords
curl, integrations, notifications, ntfy, pubsub, push-notifications
License
AGPL-3.0-only
Install
npm install @emitsignal/cli@1.4.0

Documentation

EmitSignal — dev-native push notifications

Real-time notifications, from your shell to your phone

Pipe alerts, deploys and CI straight to your phone — emit a signal from anything that can run curl.

Quick look · Features · Packages · Getting started · Architecture


What is EmitSignal?

EmitSignal is a self-hostable, real-time notification platform built for developers. Publishers POST messages to named topics; subscribers receive them live over Server-Sent Events in the web dashboard and mobile app, and as push notifications on their phone. Emails and push are dispatched asynchronously through queued workers, so publishing stays fast.

No SDK required to send — if it can make an HTTP request, it can emit a signal. The publish API is ntfy-style: set everything through headers and skip the JSON.


Quick look

Two ways to send a signal: curl needs nothing installed, the CLI is nicer to live in.

With curl — works from anything that can make an HTTP request

Publish a message — header-based, no body parsing required:

curl -X POST https://emitsignal.com/publish/alerts \
  -H "X-Title: Deploy finished" \
  -H "X-Priority: high" \
  -H "X-Tags: ci,prod" \
  -d "v2.4.0 shipped to production"

Or send JSON, authenticated with an API key:

curl -X POST https://emitsignal.com/publish/alerts \
  -H "Authorization: Bearer es_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Latency alert", "body": "p99 over 800ms", "priority": 5, "tags": ["prod"] }'

Subscribe and stream signals live over SSE:

# one topic
curl -N https://api.emitsignal.com/topics/alerts/listen

# several at once, replaying the last 10 minutes first
curl -N "https://api.emitsignal.com/listen?topics=alerts,ci,deploys&since=$(($(date +%s000) - 600000))"

Schedule for later — relative durations (30m, 2h, 1d) or a unix timestamp:

curl -X POST https://emitsignal.com/publish/reminders \
  -H "X-Title: Stand-up in 30 minutes" \
  -H "X-Delay: 30m" \
  -d "Don't forget the daily"
With the CLIemitsignal, aliased to es

Install — a single static binary, no runtime required:

npm i -g @emitsignal/cli

# or the install script
curl -fsSL https://github.com/emitsignal/emitsignal/releases/latest/download/install.sh | sh

Authenticate once — writes a token to ~/.emitsignalrc:

es login

Publish a message — the title defaults to the first line of the body:

es publish alerts "v2.4.0 shipped to production" \
  --title "Deploy finished" \
  --priority 4 \
  --tag ci,prod
# ✓ published → alerts · cmt4hcs5m000001t64ujxh3m5 · 73ms

Subscribe and stream signals live:

# one topic
es listen alerts

# every subscription, replaying the last 10 minutes first
es listen --since 10m

# glob a channel, and only surface high-priority signals
es listen --channel "alerts/*" --priority ">=4"

Point it at a self-hosted instance:

es config set-url https://signals.internal.example.com

Scheduling is header-only for now — es publish has no --delay flag yet, so use the curl form above for delayed signals.


Features

  • TopicsPOST /publish/<topic> to any named topic; subscribers tune in by name, no pre-registration. Topic names may contain slashes (alerts/prod, ci/web).
  • Live delivery (SSE)GET /topics/:name/listen or GET /listen?topics=a,b,c, both with ?since= backlog replay and heartbeats.
  • Push notifications — delivered to the Expo mobile app (iOS, Android) via queued workers.
  • Priorities15, or the aliases min / low / default / high / urgent.
  • Tags & actions — categorize with X-Tags, attach interactive X-Actions to a message.
  • SchedulingX-Delay accepts relative durations (5m, 2h, 1d, 1w) or a unix timestamp, up to a year out.
  • Webhooks — receive from external services at POST /h/:slug with built-in templates for github, grafana, stripe, vercel, and custom.
  • Attachments — upload files alongside a message, stored locally or on S3.
  • Flexible auth — magic link, passkey (WebAuthn), API keys (es_…), and optional GitHub and Apple sign-in, via Better Auth.
  • Rate limiting — per-IP and per-user limits, backed by Redis, fail-open if Redis is down.
  • Email digests — transactional emails through a pluggable provider (log, smtp, or Resend).

Packages

This is a Bun workspace monorepo.

Package Description
@emitsignal/server Elysia API — topics, auth, SSE, queued delivery
@emitsignal/website TanStack Start web dashboard
@emitsignal/mobile Expo React Native app (iOS, Android, Web)
@emitsignal/cli Terminal client — publish and stream from your shell
@emitsignal/shared Shared TypeScript types and API client
@emitsignal/emails React Email templates
@emitsignal/docs Mintlify documentation site (CLI & API reference)
@emitsignal/docker Docker Compose stack for local dev and deployment
@emitsignal/e2e-testing Playwright end-to-end tests

Getting started

You'll need Bun >= 1.3 and Docker.

bun install
docker compose -f packages/emitsignal-docker/docker-compose.dev.yml up

That brings up the whole stack with hot reload — PostgreSQL, Redis, an SMTP inbox (localhost:3134), all BullMQ workers, the API server (localhost:5100) and the website (localhost:5173).

Run pieces by hand (without Docker)
# API + workers
cd packages/emitsignal-server
cp .env.example .env          # edit DATABASE_URL, REDIS_URL, etc.
bun run db:migrate && bun run db:seed
bun run dev:worker            # all workers (separate terminal)
bun run dev                   # API server

# web dashboard
cd packages/emitsignal-website && bun run dev

# mobile app
cd packages/emitsignal-mobile && bun run start

Workspace-wide scripts run from the root: bun format, bun lint, bun test, and bun dev (runs dev in every package).


Architecture

┌─────────────┐              ┌─────────────┐              ┌─────────────┐
│   Website   │  SSE/HTTP    │   Server    │   BullMQ     │   Workers   │
│  (TanStack) │ ◄──────────► │  (Elysia)   │ ────────────► │ email/push/ │
└─────────────┘              └──────┬──────┘              │  schedule   │
                                    │                      └──────┬──────┘
┌─────────────┐              ┌──────┴──────┐                     │
│   Mobile    │  SSE/HTTP    │  PostgreSQL  │              ┌──────┴──────┐
│  (Expo/RN)  │ ◄──────────► │  + Redis    │              │   Email     │
└─────────────┘              └─────────────┘              │  Provider   │
                                                           │ smtp/resend │
                                                           └─────────────┘

A publish immediately fires an in-process event bus (powering live SSE) and enqueues a push job; scheduled messages skip the bus and go straight to the schedule queue. Dedicated workers drain the email, push, and schedule queues so the publish path never blocks on delivery.

Built with

Bun · Elysia · Prisma + PostgreSQL · Redis + BullMQ · Better Auth · TanStack Start · Expo / React Native · React Email · Tailwind CSS · TypeScript