A production-oriented notification delivery service that currently supports Telegram as its delivery channel.
It keeps notification-delivery complexity out of application services, focusing on reliable processing, controlled throughput, and predictable delivery for individual and bulk workloads.
- Queue-based delivery: Redis and BullMQ power separate staging and delivery queues for each service, keeping API requests fast and notification processing isolated.
- Flexible scheduling: priority levels, recipient timezones, and configurable delivery windows control when notifications are sent.
- Responsible rate limiting: service-wide and per-recipient limits provide backpressure, while Telegram
429responses pause and reschedule affected work automatically. - Resilient sending: transient network and Telegram server failures are retried, blocked recipients are cached, and delivery outcomes are persisted with structured error details.
- Secure service integration: versioned API tokens support credential revocation, and Telegram bot credentials are encrypted before database storage.
PostgreSQL stores notifications and service credentials, while Redis and BullMQ provide the queues used for asynchronous processing. New notifications wait in the staging queue until the scheduler determines that their delivery window and rate limits allow them to be sent. The scheduler then moves them to a priority queue, where a notification worker picks them up, sends them through Telegram, and records the outcome in PostgreSQL.
Built with TypeScript, Fastify, PostgreSQL, Prisma, Redis, BullMQ, React, and Docker.
Install backend dependencies from the repository root:
npm installInstall admin UI dependencies:
cd admin
npm install
cd ..Copy the examples:
cp .env.example .env
cp admin/.env.example admin/.envFill root .env values:
| Variable | Where to get it |
|---|---|
POSTGRES_URL |
Local development can use the value from .env. npm run dev reads it and starts a matching Docker Postgres container. |
REDIS_URL |
Local development can use the value from .env. npm run dev reads it and starts a matching Docker Redis container. |
PORT |
Choose the local backend port. The example uses 8080. |
APP_ENV |
Use development locally. Use production only for deployed environments. |
BASIC_AUTH_USER |
Choose a username for the admin UI and API docs. The example uses admin. |
BASIC_AUTH_PASSWORD |
Choose a password for the admin UI and API docs. The example uses admin. |
AUTH_TOKEN_PRIVATE_KEY |
Generate a base64 secret, for example openssl rand -base64 32. This signs service API tokens. |
DB_ENCRYPTION_PRIVATE_KEY |
Generate a base64 32-byte secret, for example openssl rand -base64 32. This encrypts service credentials in the database. |
LOAD_TESTING_TELEGRAM_API_URL |
Leave commented unless running local load tests with the Telegram mock server. It is allowed only when APP_ENV=development. |
Fill admin/.env values:
| Variable | Where to get it |
|---|---|
VITE_API_URL |
API base URL used by admin UI requests. Use / for local development so requests go through the Vite proxy. |
VITE_BASE_PATH |
Browser router base path for the admin UI. Use /admin for local development because the backend serves the built admin UI at this path. |
ADMIN_API_PROXY_TARGET |
Set it to the backend origin (host plus the PORT from the root .env), without an /api path. For example, use http://localhost:8080 when PORT=8080. |
When creating services through the admin UI, you will also need a Telegram bot token from BotFather.
Start only the Docker-backed local services for the initial Prisma setup:
npm run dev -- --no-serverKeep this process running while you generate Prisma code and sync the database.
In another terminal, run:
npm run prisma:update:devThis runs prisma generate && prisma db push, which generates the Prisma client
under src/database/generated and applies the schema to the local database.
After Prisma setup is complete, stop the npm run dev -- --no-server process
from step 3. Then start Postgres, Redis, and the backend watcher together:
npm run devThe API is available on the port configured by PORT.
In another terminal:
cd admin
npm run devThe admin dev server proxies /api requests to http://localhost:8080 by
default. Use the BASIC_AUTH_USER and BASIC_AUTH_PASSWORD values from .env
when prompted by protected backend routes.
Repository commands:
npm run admin:api:generate: exports the backend OpenAPI document to the ignoredadmin/openapi.jsonfile and regenerates the admin API client from it.
Backend commands:
npm run dev: starts Docker-backed Postgres/Redis and the backend watcher.npm run dev:server: starts only the backend watcher.npm run prisma:update:dev: regenerates Prisma client and pushes the schema to the configured database.npm run prisma:migrate: applies existing Prisma migrations.npm run typecheck: runs TypeScript without emitting files.npm run build: builds the backend intodist/.npm start: runs the built backend fromdist/main.js.
Admin commands:
npm run dev: starts the admin UI dev server.npm run build: builds the admin UI.npm run start: previews the built admin UI through Vite.npm run api:generate: regenerates the client fromadmin/openapi.json.
Start the backend and open:
http://localhost:8080/api/v1/docs
The API documentation is protected with basic auth. Use BASIC_AUTH_USER and
BASIC_AUTH_PASSWORD from .env.