Skip to main content

Dialer Worker

The Dialer Worker is a Go-based service that executes outbound auto-dialer campaigns. It polls the Laravel API for active campaigns, rate-limits call initiation, and initiates calls through Cloudonix.

Purpose

The worker handles:

  • Polling for active campaigns every 10 seconds
  • Fetching pending destinations from Laravel
  • Enforcing CAC (Concurrent Active Calls) and CPS (Calls Per Second) limits
  • Initiating calls via the Laravel API
  • Receiving CDR webhooks from Laravel
  • Managing retry queues in Redis
  • Selecting Caller IDs from pools

Architecture

Build and Run

The worker starts automatically with the full OPBX stack:

docker compose up -d

Local Development

cd dialer-worker

cp .env.example .env
# Edit .env with your configuration

make deps
make build
make run

Makefile Targets

TargetDescription
make buildBuild the worker binary
make runRun with go run
make testRun tests
make fmtFormat Go code
make lintRun golangci-lint
make docker-buildBuild Docker image
make docker-runRun Docker container with .env
make healthCheck worker health endpoint

Environment Variables

VariableDefaultRequiredDescription
LARAVEL_API_URLhttp://localhost:8000YesLaravel API base URL
LARAVEL_API_TOKENYesBearer token for Laravel API
REDIS_HOSTlocalhostYesRedis host
REDIS_PORT6379YesRedis port
REDIS_PASSWORDNoRedis password
REDIS_DB0NoRedis database
WORKER_IDworker-1YesUnique worker identifier
POLL_INTERVAL10sNoCampaign polling interval
WEBHOOK_PORT8081YesHTTP server port for CDR webhooks and health checks
note

The worker exposes a single HTTP server on WEBHOOK_PORT. Health checks are available at GET /webhooks/health on this port.

| WEBHOOK_SECRET | — | No | HMAC-SHA256 secret for CDR webhooks |

note

Laravel uses a dedicated dialer Redis connection with no key prefix for all keys shared with the Go worker. This ensures both services read and write the same keys.

Campaign Lifecycle Overview

  1. Worker polls GET /v1/dialer/worker/campaigns/active
  2. For each active campaign, it registers CAC/CPS limits
  3. It fetches pending destinations: GET /campaigns/{id}/destinations/pending
  4. For each destination, it acquires a Redis lock, checks CAC, selects a Caller ID, and calls POST /calls/initiate
  5. On successful initiation, it increments the active call counter
  6. When Laravel receives a CDR webhook, it decrements the counter and the worker receives a relayed CDR event
  7. The worker updates call status and schedules retries if needed

Redis Key Patterns

Key PatternPurpose
dialer:cac:{id}:activeActive call counter per campaign
dialer:call:{session}Call state hash
dialer:lock:{key}Distributed locks
dialer:retry:{campaign}Retry sorted set
dialer:worker:{id}Worker heartbeat
dialer:idem:{key}Webhook idempotency

Horizontal Scaling

Multiple worker instances can run concurrently:

docker compose up -d --scale dialer-worker=3

Each instance polls independently and uses Redis for distributed locking and shared state.

Troubleshooting

IssueSolution
Worker not dialingCheck LARAVEL_API_URL and LARAVEL_API_TOKEN; verify campaign status is ACTIVE
Health check unreachableCheck WEBHOOK_PORT and query GET /webhooks/health on that port
CAC counter driftThe worker reconciles the counter against live call state on each poll cycle
Webhook auth failuresEnsure WEBHOOK_SECRET matches the value used by Laravel
High memory usageReduce POLL_INTERVAL or scale horizontally