A secure peer-to-peer message routing server for the Bitcoin SV ecosystem. Provides identity-based message delivery, real-time WebSocket communication, and full mutual authentication using BRC-103 signatures.
The Message Box Server implements encrypted, store-and-forward messaging with support for both HTTP and WebSocket transports. Messages are identified by sender identity keys and recipient message boxes, encrypted end-to-end, and stored in MySQL until acknowledged by the recipient. The server speaks HTTP and WebSocket protocols, maintains connection state via @bsv/authsocket rooms, and can emit Firebase push notifications to registered devices.
Clients connect with identity-based authentication, send and receive messages through authenticated endpoints, and can opt into real-time WebSocket delivery for immediate notifications.
| Type | Requirement |
|---|---|
| Database | MySQL 8.0 via mysql2 |
| External services | Wallet Storage (WALLET_STORAGE_URL), Firebase Admin SDK (optional) |
| ts-stack packages | @bsv/sdk, @bsv/auth-express-middleware, @bsv/authsocket, @bsv/payment-express-middleware, @bsv/wallet-toolbox |
| Method | Path | Purpose |
|---|---|---|
| POST | /sendMessage | Send encrypted message to recipient (authenticated) |
| POST | /listMessages | Page unacknowledged messages with limit and offset/skip |
| POST | /acknowledgeMessage | Mark messages as read/delete them (authenticated) |
| POST | /registerDevice | Register a push-notification device for the authenticated identity |
| GET | /devices | List the authenticated identity's devices with redacted tokens |
| GET | /permissions/get | Read a recipient permission |
| GET | /permissions/list | List recipient permissions |
| GET | /permissions/quote | Quote up to 100 recipients |
| POST | /permissions/set | Set an authenticated recipient permission |
| GET | /health | Public process liveness |
| GET | /ready | Public database readiness |
| GET | /docs, /openapi.json | Public API documentation |
The omitted page size is 1,000 in the standard profile and is operator
configurable. Responses include limit, offset, nextOffset, and hasMore.
See Service Resource Profiles for
all limits, shared state, BRC-105 pricing, memory evidence, and scaling guidance.
{identityKey}-{messageBox} formatauthenticated, joinRoom, sendMessage, leaveRoom| Variable | Required | Description |
|---|---|---|
| NODE_ENV | No | development, staging, or production |
| BSV_NETWORK | No | mainnet, testnet, ttn, or teratestnet (default mainnet) |
| PORT | No | HTTP/WebSocket port (default 8080; takes precedence) |
| HTTP_PORT | No | Compatibility port fallback |
| HOSTING_DOMAIN | No | Public domain for overlay advertisement (e.g., http://localhost:8080) |
| SERVER_PRIVATE_KEY | Yes | 256-bit hex private key for server identity and auth signing |
| ROUTING_PREFIX | No | Optional path prefix for all routes (e.g., /api) |
| ENABLE_WEBSOCKETS | No | Set to 'true' to enable real-time messaging (default true) |
| LOGGING_ENABLED | No | Set to 'true' for verbose debug logging |
| WALLET_STORAGE_URL | No | URL of wallet storage service (e.g., https://store-us-1.bsvb.tech) |
| KNEX_DB_CLIENT | No | Database client (default: mysql) |
| KNEX_DB_CONNECTION | Yes | JSON connection config: {"host":"localhost","port":3306,"user":"root","password":"...","database":"messagebox-backend"} |
| MIGRATE_KEY | No | Optional key to authorize migration operations |
| ENABLE_FIREBASE | No | Set to 'true' to enable Firebase push notifications |
| FIREBASE_PROJECT_ID | No | GCP project ID for Firebase |
| FIREBASE_SERVICE_ACCOUNT_JSON | No | Firebase service account JSON (inline) |
| FIREBASE_SERVICE_ACCOUNT_PATH | No | Path to Firebase service account JSON file |
| MESSAGE_BOX_CORS_MODE | No | public (default), allowlist, or disabled |
| MESSAGE_BOX_CORS_ALLOWED_ORIGINS | No | Exact comma-separated origins in allowlist mode |
| MESSAGE_BOX_CORS_ALLOWED_HEADERS | No | Strict comma-separated browser request-header allowlist; omit for additive compatibility |
| MESSAGE_BOX_MAX_BODY_BYTES | No | HTTP JSON ceiling (default 4194304) |
| MESSAGE_BOX_WEBSOCKET_MAX_BODY_BYTES | No | WebSocket payload ceiling (default 1048576) |
See Public Service Edge Security for rate, timeout, WebSocket authorization, CORS/CSP, and error controls.
# Install dependencies
npm install
# Development with hot-reload
npm run dev
# Database setup (MySQL 8.0 running)
docker compose up -d mysql
# Build for production
npm run build
# Run production build
npm start# Build Docker image
docker build -t messagebox:latest .
# Run the service directly on port 8080
docker run -d \
-e NODE_ENV=production \
-e PORT=8080 \
-e SERVER_PRIVATE_KEY=<hex-key> \
-e KNEX_DB_CONNECTION='{"host":"mysql","port":3306,"user":"root","password":"...","database":"messagebox-backend"}' \
-e ENABLE_WEBSOCKETS=true \
-p 8080:8080 \
messagebox:latest
# Or use docker-compose
docker compose up -dThe multi-stage Dockerfile compiles TypeScript for a minimal production runtime. The Node process serves HTTP and WebSocket traffic directly; deploy a trusted platform ingress or load balancer in front when required.
Knex migrations run automatically at server startup. After building, they can also be run manually:
npx --no-install knex --knexfile out/knexfile.js migrate:latest --env productionMigrations tracked in src/migrations/:
2022-12-28-001-initial-migration.ts – Core messages table with identity keys2023-01-17-messages-update.ts – Payload storage updates2024-03-05-001-messageID-upgrade.ts – MessageID uniqueness constraints2025-01-31-001-notification-permissions.ts – Firebase notification permissions2025-01-31-002-device-registrations.ts – Device registration tracking2026-07-26-001-message-permission-scope.ts – Enforce one box-wide or sender-specific permission per scope2026-07-26-002-list-query-indexes.ts – Add indexes for bounded inbox and sender-list queries2026-08-04-001-resource-safety.ts – Add retained-state accounting, resource-safety fields, and durable payment replay claims2026-09-21-001-message-payment-intents.ts – Bind paid sends to exact requests and preserve cross-boundary recovery statePreserve both payment_replays and message_payment_intents across rollouts.
For body payments, the replay claim is non-expiring. An intent advances from
prepared to wallet_accepted to completed; an ambiguous prepared intent
after process termination requires reconciliation against the wallet before
retrying. Do not delete or reassign an intent to force a retry. The down
migration refuses to drop a populated intent table, and an older image cannot
safely interpret this recovery protocol. Prefer roll-forward; if rollback is
unavoidable, drain all replicas, reconcile every intent, and restore the prior
image together with a verified pre-migration database backup. See the
deployment guide
for the full recovery and rollback procedure.
Periodic snapshots provide the general message/permission RPO, but paid sends require zero silent loss of replay and intent evidence. Retain continuous point-in-time/binlog history and independent wallet transaction/audit evidence from each snapshot through the present. After a restore, keep paid sends disabled until every post-snapshot wallet action, replay claim, and intent has been reconstructed and reconciled; an unresolved interval must not be retried or converted into a second payment request.
GET /health and GET /healthz report process liveness without authentication.GET /ready verifies database connectivity and returns a non-sensitive 503
response while dependencies are unavailable.@bsv/message-box-client/ready.The service package is private and is distributed as a container, not as a public npm package.