Building a Custom Autodialer with FreJun
An autodialer is, at its core, a simple loop: take a list of contacts, call them one after another with a controlled gap in between, and log the outcome of each call. Since FreJun already exposes calling as a programmable API, you can build this loop entirely on your own backend — no separate autodialer product needed.
This guide walks through the architecture, the exact API calls involved, pacing/compliance considerations, and a working reference implementation.
1. How autodialing works on top of FreJun
Section titled “1. How autodialing works on top of FreJun”At a high level, your system becomes the “dialer engine” and FreJun becomes the calling layer:
The two building blocks that make this possible are:
- The Calling API / SDK — lets you trigger a call to any number.
- Webhooks — tell your backend when a call has ended (and give you the outcome), so you know when it’s safe to dial the next contact.
2. Prerequisites
Section titled “2. Prerequisites”Before you start:
- Authentication credentials — either:
- An OAuth 2.0 app (recommended for production integrations, supports webhooks), or
- An API Key (simpler, good for server-to-server/internal tools, if using API only for making calls).
- See
Setup and Authorizationin the FreJun docs for how to create these.
- A virtual number provisioned in your FreJun account.
- A publicly reachable HTTPS endpoint on your backend to receive webhooks.
3. Core API / SDK: triggering a call
Section titled “3. Core API / SDK: triggering a call”Please go through this documentation about how to integrate FreJun calling into your platform.
4. Knowing when a call ends: Webhooks
Section titled “4. Knowing when a call ends: Webhooks”Your dialer engine needs to know when a call finishes before it can safely place the next one. Don’t poll — subscribe to webhooks instead.
Make sure to follow the webhook documentation on how to subscribe to FreJun calling webhooks.
5. Designing the dialer engine
Section titled “5. Designing the dialer engine”This is the part you build. A minimal, reliable design looks like this:
5.1 Data model
Section titled “5.1 Data model”campaigns id, name, status, pacing_seconds, calling_hours_start, calling_hours_end, max_attempts
campaign_contacts id, campaign_id, candidate_number, candidate_name, status (pending | dialing | completed | failed | skipped), attempts, last_call_id, last_outcome, next_attempt_at5.2 The loop
Section titled “5.2 The loop”- Picker: A scheduled job / loop checks each active campaign, and if it’s currently within calling hours and no call is
in_progressfor that campaign/agent, pops the nextpendingcontact. - Dial: Call the contact’s number and campaign metadata (
job_id= campaign ID,candidate_id= contact ID). Mark the contactdialing. - Wait: Do nothing else for this agent/line until a webhook arrives — don’t fire the next call in parallel on the same agent.
- Webhook handler: On
call.summary, look up the contact viametadata.candidate_id, record the outcome (connected,no-answer,busy,failed), mark itcompletedor requeue it for retry. - Pacing gap: After marking the contact done, schedule the next pick-up after your configured pacing interval (e.g. 30–60 seconds) rather than immediately — this avoids hammering the API / SDK and gives agents breathing room between calls.
- Retry logic: If the outcome was
no-answer/busy/failed, requeue withnext_attempt_atpushed forward (e.g. +2 hours), up tomax_attempts.
5.3 Concurrency
Section titled “5.3 Concurrency”Each user_email can only be on one call at a time — that’s your natural concurrency unit. If you want to dial faster, add more licensed agents to the campaign and distribute the load accordingly.
6. Pacing, calling hours & compliance
Section titled “6. Pacing, calling hours & compliance”Since you’re now responsible for the dialing logic (rather than a packaged autodialer product), build these guardrails in yourself:
- Respect calling-hour windows — don’t dial outside the hours permitted in the region you’re calling into.
- Honor DND/opt-out lists — filter your contact list against any Do-Not-Call registry or explicit opt-outs before queuing (please note that this is handled on FreJun side too).
- Cap retry attempts — set a sane
max_attemptsper contact (e.g. 3) with cooldown between attempts, rather than retrying immediately. - Add a pacing gap between calls, even when agents are free — a small delay (20–60s) avoids overwhelming agents and keeps call quality high.
- Log every attempt — store the
call_idand outcome for every dial, not just the final one, so you have a full audit trail. - Rate limits — Please check the rate limiting policy at /getting-started/rate-limiting/.