Skip to content

Repository files navigation

Dialer

Embeddable sales telephony for any product — "Stripe Connect for outbound calling".

CI License: MIT Node TypeScript PRs welcome

Quickstart · Architecture · API · SDK · React · Contributing


Your app keeps its users, leads and CRM records. Dialer owns everything that makes a phone ring: rep presence, queues, power-dial loops, call state, recordings, compliance gates, caller-ID selection, routing and signed webhooks. You integrate with a server SDK, a React package and a webhook endpoint.

<DialerProvider token={session.token} apiUrl={apiUrl} realtimeUrl={realtimeUrl}>
  <PowerDialer
    campaignId={campaignId}
    onLead={(lead) => showCrmRecord(lead.externalId)}
    onDisposition={({ callId, code, notes, lead }) => saveToCrm(lead?.externalId, callId, code, notes)}
  />
</DialerProvider>

Features

  • Power, preview and manual dialing — a session reducer drives the whole loop: reserve, present, dial, talk, wrap up, repeat.
  • Swappable media plane — the MediaPlane interface covers call control; FreeSWITCH (WebRTC for reps, SIP trunk out) implements it, and a fake implementation runs the whole platform with no telephony infrastructure.
  • Pluggable carrier layer — carriers are adapters behind one TelephonyCarrier interface, resolved from a registry at runtime. Different organizations can run different carriers at the same time. BulkVS and a fake ship in the box; a new one is a factory and a registerCarrier call.
  • Realtime — WebSocket gateway backed by NATS JetStream, with replay and resume so a refreshed tab rejoins a live call.
  • Recordings — streamed to any S3-compatible store (S3, R2, GCS, MinIO).
  • Compliance built in — DNC lists, calling-window enforcement and consent checks gate every origination attempt.
  • Signed webhooks — HMAC signing and verification that run on Node, the browser and edge runtimes.
  • Multi-tenant from the start — organizations, API keys, rep JWTs and RBAC, with credentials encrypted at rest using AES-256-GCM.

How it works

            ┌───────────────────── your product ─────────────────────┐
            │  server: @dialer/sdk (API key)   browser: @dialer/react (rep JWT)  │
            └──────────┬────────────────────────────────┬────────────┘
                       │ REST :4000                     │ WS :4001 (+ SIP/WSS :7443 media)
   ┌───────────────────▼────────────────────────────────▼───────────────────┐
   │ CONTROL PLANE                                                          │
   │  apps/api (Fastify)   apps/realtime (ws + JetStream replay)            │
   │  apps/worker (dialer loop, call state machine, webhooks, recordings)   │
   │  apps/telephony (:4002, hosts MediaPlane, NATS request/reply)          │
   │      Postgres ── Redis (locks/presence) ── NATS JetStream ── S3/MinIO  │
   └──────────────────────────────┬─────────────────────────────────────────┘
                                  │ ESL 8021 / recordings volume
   ┌──────────────────────────────▼─────────────────────────────────────────┐
   │ MEDIA PLANE   MediaPlane adapter — FreeSWITCH (SIP/WSS for reps, SIP   │
   │               trunk to carrier) | fake   ·   coturn (TURN behind NAT)  │
   │ CARRIER       TelephonyCarrier adapter — bulkvs | fake | your own      │
   └────────────────────────────────────────────────────────────────────────┘

Quick start

Requires Node >= 22, pnpm 10 and Docker.

pnpm install
cp .env.example .env
docker compose up -d      # postgres, redis, nats (JetStream), minio
pnpm db:migrate
pnpm db:seed              # prints org, API keys, reps and a campaign
pnpm dev                  # api, realtime, worker, telephony, admin, example-crm

Open http://localhost:4200, sign in as a seeded CRM user, click Queue open leads, then Start dialing.

Ports: API 4000, realtime 4001, telephony 4002, admin 4100, example CRM 4200.

TELEPHONY_PROVIDER=fake and CARRIER_PROVIDER=fake are the defaults, so the platform runs end to end without a carrier account. For real audio, switch to freeswitch and start the media plane with docker compose --profile telephony up -d --wait; see docs/FREESWITCH.md.

With the fake carrier, the destination's last four digits pick the outcome: 0000 unknown number, 0001 busy, 0002 decline, 0003 provider unavailable, 0004 no answer, 0005 answer then hangup after 4s, 0006 early media then answer, anything else answers in about 1.2s.

Integrating

Four steps, in full in the Quickstart.

// 1. Server: mint a short-lived rep token for one of your users.
const dialer = new Dialer({ apiKey: process.env.DIALER_API_KEY!, baseUrl: "http://localhost:4000" });
const s = await dialer.reps.createSession({ externalUserId: user.id, displayName: user.name });

// 2. Server: create a queue and a campaign, once.
const queue = await dialer.queues.create({ name: "CRM leads", strategy: "PRIORITY" });
const campaign = await dialer.campaigns.create({ name: "Outbound", dialMode: "POWER", queueId: queue.id, state: "ACTIVE" });

// 3. Server: push leads keyed by your own record ids.
await dialer.queueItems.enqueueBatch(leads.map((l) => ({
  campaignId: campaign.id, phone: l.phone, externalId: l.id, externalSource: "my-crm",
})));

// 4. Browser: drop <PowerDialer /> into your app (see the snippet above).

Outcomes come back through callbacks in the browser and signed webhooks on your server. A complete reference integration lives in apps/example-crm.

Carriers

Carriers are pluggable. packages/telephony defines the whole contract — TelephonyCarrier (numbers, CDRs, error normalization, optional STIR/SHAKEN and CNAM) and CarrierSipProfile (how the media plane reaches the trunk). Nothing above that layer is carrier-specific.

Adapters register a factory by name, and each organization picks its carrier through a provider account, so several carriers can run side by side in one deployment.

import { registerCarrier } from "@dialer/services";

registerCarrier("my-carrier", (credentials, sipConfig) => new MyCarrier(credentials, sipConfig));

Built in today: bulkvs (@dialer/carrier-bulkvs) and fake (@dialer/test-provider), both registered in packages/services/src/bootstrap.ts. Use the fake as the reference implementation, and see docs/TELEPHONY.md for the interface and docs/BULKVS.md for a real adapter end to end.

Repository layout

apps/
  api/            REST API (Fastify, :4000)
  realtime/       WebSocket gateway (:4001)
  worker/         JetStream consumers + housekeeping
  telephony/      MediaPlane host: fake | FreeSWITCH ESL (:4002)
  admin/          Vite admin console (:4100)
  example-crm/    Reference integration (:4200)
packages/
  core/           Domain enums, state machines, zod API schemas, events, routing, compliance
  services/       Business logic (calls, dialer, queues, reps, recordings, webhooks, carriers)
  database/       Drizzle schema, migrations, seed
  bus/            NATS JetStream + Redis helpers (locks, keys)
  auth/           API keys, rep JWT, AES-256-GCM credential encryption, RBAC
  telephony/      Carrier-neutral contracts: TelephonyCarrier, MediaPlane, events
  freeswitch/     FreeSWITCH MediaPlane (ESL) — a MediaPlane implementation
  carrier-bulkvs/ BulkVS — a TelephonyCarrier implementation
  test-provider/  FakeMediaPlane / FakeCarrier — implementations for local dev
  webhooks/       HMAC signing and verification (Node, browser, edge)
  sdk/            @dialer/sdk — REST client, RealtimeClient, Phone, PowerDialerSession
  react/          @dialer/react — DialerProvider, PowerDialer, Phone, hooks
  observability/  pino logger, optional OpenTelemetry
infra/            Dockerfiles, FreeSWITCH config, coturn, Kubernetes manifests
tests/            e2e (Playwright), load, concurrency

Scripts

Command Purpose
pnpm dev Run every app with Turbo
pnpm typecheck Typecheck the workspace
pnpm test:unit Unit tests
pnpm test:integration Integration tests (needs Docker services)
pnpm test:e2e End-to-end power-dial run
pnpm test:load Load harness
pnpm db:migrate / pnpm db:seed Apply migrations / seed dev data
pnpm release Build @dialer/sdk and @dialer/react tarballs into release/

Documentation

Start here QUICKSTART · ARCHITECTURE
Reference API · SDK · REACT · EVENTS · WEBHOOKS
Integrating INTEGRATIONS · CRM_ADAPTERS
Telephony TELEPHONY · FREESWITCH · BULKVS · RECORDINGS · ROUTING
Operating DEPLOYMENT · SCALING · RUNBOOK · SECURITY · COMPLIANCE

Contributing

Issues and pull requests are welcome — see CONTRIBUTING.md for setup, checks and conventions, and docs/CONTRIBUTING-AGENTS.md for a dense map of the subsystems. Security issues go through SECURITY.md, not public issues.

License

MIT

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages