> ## Documentation Index
> Fetch the complete documentation index at: https://docs.telzino.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Outbound Best Practices

> A recommended baseline for outbound agents: consent, calling hours, disclosure, opt-outs, retries, answering machine detection, pacing and CRM integration

Telzino enforces guardrails on every outbound call; your CRM decides who is called, when, and on what legal basis. This guide sets out how to configure an outbound agent so both halves work together, and a baseline to start every campaign from.

> [!WARNING]
>
> This is operational guidance, not legal advice. AI-voice calls are generally treated as artificial-voice calls under the TCPA, so the consent and opt-out rules for those calls apply. Have your own counsel review your consent and calling practices before you launch.

## Who is responsible for what

| Telzino enforces | Your CRM owns |
| - | - |
| Refusing calls with no consent basis, when consent is required | Whether consent actually exists, and the records behind it |
| Calling hours on the callee's local clock | The call list itself |
| Suppression (do-not-call) lists at organization, reseller and partner level, including opt-outs heard on a call | National DNC and reassigned-number scrubbing |
| Per-number attempt caps and the retry schedule | Pacing within the agent's concurrency limit |
| The per-agent concurrency limit | Backing off and retrying when the agent is at capacity |
| AI disclosure delivery, recorded on each call | Adding opt-outs from other channels to the suppression list |
| An audit export that includes unanswered attempts | Retaining consent evidence for your own audits |

Every call is checked before it is dialled. A call that fails any check is refused with an error and never placed:

```mermaid theme={null}
flowchart TD
    T[Trigger outbound call] --> C{Consent basis sent,<br/>if required?}
    C -- no --> E1[400 consent_required]
    C -- yes --> W{Callee inside<br/>calling hours?}
    W -- no --> E2[409 out_of_window,<br/>or scheduled]
    W -- yes --> S{Number not on the<br/>suppression list?}
    S -- no --> E3[409 suppressed]
    S -- yes --> P{Under per-number<br/>attempt caps?}
    P -- no --> E4[409 policy_violation]
    P -- yes --> A{Agent below its<br/>concurrency limit?}
    A -- no --> E5[429 agent_at_capacity]
    A -- yes --> D[202 call queued and dialled]
```

The order shown is for illustration; any failed check stops the call.

## Before you launch

* **Use production.** The suppression list, retry policies, queued-retry cancellation and the compliance export are available on production only.
* **Check your API key's scope.** [Retry policies](https://docs.telzino.com/functions/retry-policies.md) belong to the account. A key scoped to a reseller or organization can read them but gets `403 insufficient_scope` on create, edit or delete. Ask your Telzino contact to create policies for you, or use an account-level key.
* **Call from a server.** The API sends no CORS headers, and the client secret must never reach a browser. Proxy every call through your backend, as the [Agent Runbook](https://docs.telzino.com/agent-runbook.md) describes.
* **Confirm outbound is unlocked.** A trigger on an organization where outbound is locked returns `403 feature_locked`.
* **Use a test organization first.** Keep pilot and QA traffic out of live customer organizations.
* **Caller ID is set by your PBX.** All outbound calls are placed through your PBX, so the number shown, and its STIR/SHAKEN attestation, follow your PBX and carrier configuration. Set the outbound caller ID there before launch. If your PBX passes the shared default number through instead and then rejects transfers on outbound calls (Zoom Phone does), turn on **Use this agent's DID or SIP user as caller ID on outbound calls** in the agent's Transfers settings (`config.outbound_caller_id_use_did`) so calls show the DID from the agent's SIP registration (or its SIP username or extension when there is no DID).

## Consent

Turn on `contactPolicy.require_consent_basis` for every outbound agent, so a call without a stated consent basis is refused with `400 consent_required` and never dialled. See [Outbound Compliance](https://docs.telzino.com/functions/outbound-compliance.md#consent-basis) for the full reference.

| `consent_basis` | Use it when |
| - | - |
| `express_written` | The contact gave prior express written consent (required for most marketing calls) |
| `ebr` | You rely on an established business relationship |
| `informational_exempt` | The call is purely informational, such as an appointment reminder or service notice |
| `other` | Accepted, but avoid it unless your counsel has approved the basis |

* Telzino checks that a basis is present. It does not verify it; your CRM is the source of truth.
* Always send `consent_reference` (up to 200 characters): the CRM record or form submission that proves consent. It is stored for the audit export.
* If you allow `other`, require a `consent_reference` with it in your own process.

## Calling hours

Set calling hours on every outbound agent: with no `calling_windows`, Telzino applies no hours restriction at all. We recommend **09:00 to 20:00 on the callee's local clock**, stricter than the federal 8am to 9pm and inside tighter state rules such as Florida's.

* `start_local` is inclusive and `end_local` exclusive, so `"20:00"` means before 8pm. Windows can't wrap midnight.
* Use `timezone_source: "callee_npanxx"`, which infers the callee's zone from the area code. Area codes spanning two zones resolve to the dominant one, and ported or moved numbers can be wrong.
* **Send `callee_timezone` whenever your CRM knows it.** It overrides the area-code lookup for that call and matters most for mobile numbers.
* Turn on **Reschedule out-of-window calls** in your retry policy, so a call triggered outside the window is queued for the next opening (`202`, status `scheduled`) instead of refused with `409 out_of_window`.

## AI disclosure and greetings

Disclose the AI in the first words the callee hears: put the disclosure in the agent's static outbound greeting, and also enable `aiDisclosure` so delivery is recorded on each call.

An outbound call opens in this order:

1. `outboundStaticGreeting`: spoken word for word, cannot be altered by the AI.
2. The outbound greeting (`outboundGreetingMessage`, or a per-call `greeting`).
3. The `aiDisclosure` text. Only `position: "after_greeting"` is supported.

Because the disclosure plays after the greeting, the static greeting is where it belongs if it must come first. A good opening identifies the business and the AI in one line, for example: *"Hi, this is an AI assistant calling on behalf of Acme Dental."*

* Set `aiDisclosure.enabled: true` explicitly; it is never defaulted.
* Each call records `disclosure_delivered` (`true`, `false`, or `null` when none was due) and `disclosure_timestamp`, and both appear in the result webhook. Treat any `false` as an incident to review.
* Disclosure is configured per agent. It can't yet be made mandatory at the reseller or partner level, so lock it into your agent template.
* `required_states` is stored but not yet evaluated. Treat disclosure as always on, not state-dependent.

## Do-not-call and opt-outs

A number on any [suppression list](https://docs.telzino.com/functions/outbound-compliance.md#suppression-list) that covers your organization (its own, its reseller's or its partner's) is never dialled: the trigger returns `409 suppressed`. Opt-outs heard on a call are added automatically; make sure opt-outs from any other channel land on a list the same day.

* **Opt-outs during a call are suppressed automatically.** When a callee asks not to be called again, the call is marked `opt_out_detected`, every queued retry to that number is cancelled, and the number is added to the suppression list with `created_by: "system"`. Which list depends on the [opt-in settings](https://docs.telzino.com/functions/outbound-compliance.md#where-opt-outs-are-written); by default it is the calling organization's. The result webhook reports `opt_out_detected` and `suppression_added`. See [stopping retries](https://docs.telzino.com/functions/retry-policies.md#stopping-retries).
* **Phone menus and voicemail.** An opt-out found only in the transcript of a call answered by a phone menu or voicemail cancels retries but doesn't add the number, because menu prompts such as *"to opt out, press 9"* are transcribed as the callee. A result with `opt_out_detected: true` and `suppression_added: null` means the number was not listed, either for this reason or because the add failed: review the call and add the number yourself if a person asked.
* **Add a number** from another channel with [`POST /v1/organizations/{organization_id}/suppression-list`](https://docs.telzino.com/api-reference/suppression-list/list-add), sending `phone_number` (E.164) and `reason: "opt_out"`. Adding a number already on the list succeeds, so it is safe to retry.
* **Stop pending retries** for opt-outs received outside a call with [`DELETE /v1/outbound-calls/queued?to_number=+15105550123`](https://docs.telzino.com/api-reference/outbound-calls/cancel-queued). It cancels every scheduled retry to that number across your account; a retry already dialling is not stopped.
* **Use E.164 everywhere.** Numbers are matched exactly; `(510) 555-0123` does not match `+15105550123`.
* **Consider wide suppression.** By default an opt-out heard on a call is added only to the organization that placed it, so a sibling organization can still dial the number. If you run several organizations, consider turning on **Apply opt-outs partner-wide** or **Apply opt-outs reseller-wide**. Check with counsel first: a partner-wide opt-out can block calls from unrelated businesses under the same partner, which is why it is off by default. Opt-outs from other channels can be added straight to the [reseller](https://docs.telzino.com/api-reference/suppression-list/reseller.md) or [partner](https://docs.telzino.com/api-reference/suppression-list/partner.md) list.
* **Keypress opt-out** ("press 9 to stop these calls") has to be written into the agent's instructions. The Keypad (DTMF) transfer toggle only maps keys to transfer destinations.

## Retries and attempt caps

Let Telzino handle redials with a [retry policy](https://docs.telzino.com/functions/retry-policies.md), and cap attempts per number so no one is called more than twice a day. Set it as the agent's `defaultRetryPolicyId`, or per call with `retry_policy_id`.

Recommended rules (outcomes not listed are never retried):

| Outcome | Attempts | Delay | Backoff |
| - | - | - | - |
| `no_answer` | 3 | 4 hours (14,400 s) | linear |
| `busy` | 2 | 20 minutes (1,200 s) | exponential |
| `voicemail` | 1 | next day (86,400 s) | linear |
| `failed` | 1 | 30 minutes (1,800 s) | linear |

Recommended `contact_policy` caps, enforced per number across your whole account:

| Cap | Value |
| - | - |
| `max_attempts_per_day` | 2 |
| `max_attempts_per_week` | 5 |
| `min_seconds_between_attempts` | 3,600 (1 hour) |

* Set `on_exhausted: "webhook"` with an HTTPS `on_exhausted_target`, so your CRM learns when a contact's attempts are used up.
* A call blocked by the caps returns `409 policy_violation`.
* Editing a policy doesn't change retry chains already in progress; each call keeps the rules it started with.

## Answering machine detection and voicemail

Turn on [answering machine detection](https://docs.telzino.com/functions/answering-machine-detection.md) (AMD) and leave a short voicemail that names your business and a callback number. AMD is off by default, and the abandon-rate metric only counts calls AMD classified as answered by a person, so without it your abandon rate reports `null`.

| `amdConfig` key | Recommended | Notes |
| - | - | - |
| `enabled` | `true` | Required for voicemail drops, IVR navigation and the abandon-rate metric |
| `sensitivity` | `balanced` | `fast` misreads more live people; `precise` adds delay before the agent speaks |
| `on_machine` | `leave_voicemail` | Or `hang_up` if you don't want to leave messages |
| `on_silence` | `reprompt` | Gives a quiet live person a chance to answer |

* **Voicemail message.** Keep it to two sentences: who is calling, why, and the number to call back. Set it on the agent (`voicemailMessage`) or per call (`voicemail_message`).
* **IVR navigation.** Leave [IVR navigation](https://docs.telzino.com/functions/ivr-navigation.md) off unless you call businesses with phone menus. It requires AMD; enabling it with AMD off returns `400` and places no call.
* **Test AMD on your own phones.** A live person misread as a machine hears a voicemail or nothing at all.

## Concurrency, pacing and abandon rate

Start a new campaign at 2 to 3 concurrent calls per agent, paced by your CRM, and raise it only after reviewing the first day's results.

* The concurrency limit is configured per agent; ask your Telzino contact for your agents' limit.
* A trigger past the limit returns `429 agent_at_capacity`; the call is not queued. Back off and retry with an increasing delay (for example 30 s, 60 s, 120 s).
* A call counts as abandoned when a person answers and hangs up before the agent engages. Each agent is flagged in [`GET /v1/metrics`](https://docs.telzino.com/api-reference/metrics) when its abandon rate passes `abandon_rate_alert_threshold`, which defaults to 3%, the FCC limit. Review flagged agents before the next campaign day.

## CRM integration

Your CRM triggers each call with [`POST /v1/agents/{agentId}/outbound-calls`](https://docs.telzino.com/api-reference/outbound-calls/create) and receives the result on a `callback_url`. Send these fields on every trigger:

| Field | Why |
| - | - |
| `to_number` | The destination, in E.164 |
| `consent_basis` and `consent_reference` | The legal basis and where its proof lives |
| `callee_timezone` | When known; corrects the area-code lookup |
| `metadata.campaign_id` | Groups calls for the compliance export (1 to 200 characters) |
| `metadata` (other keys) | Your CRM record IDs, echoed back in the result |
| `callback_url` | HTTPS endpoint that receives the call result |
| `call_context` | Optional per-call facts appended to the agent's instructions |

Handle the responses:

| Response | Meaning | CRM action |
| - | - | - |
| `202` | Queued, or `scheduled` for the next window | Store the call `id` |
| `400 consent_required` | No consent basis sent | Fix the record; don't retry blindly |
| `403 feature_locked` | Outbound not enabled | Contact Telzino |
| `409 suppressed` | Number is on the suppression list | Mark the contact do-not-call |
| `409 out_of_window` | Outside calling hours | Retry in the next window |
| `409 policy_violation` | Attempt caps reached | Stop for now |
| `429 agent_at_capacity` | Agent is full | Back off and retry |

* **Results.** The callback payload includes your `metadata`, the outcome, `opt_out_detected`, `suppression_added`, and `disclosure_delivered` / `disclosure_timestamp`. Update the contact, and mark it do-not-call on any opt-out. Telzino has already added the number to the suppression list unless `suppression_added` is `null`.
* **Verifying results.** The per-call `callback_url` is not signed. Use an unguessable callback URL and match each result to a call `id` you created. [Event webhooks](https://docs.telzino.com/event-webhooks/overview.md) are signed with HMAC if you need verifiable delivery.
* **Compliance export.** [`GET /v1/outbound-calls/export`](https://docs.telzino.com/api-reference/outbound-calls/export) returns every dialled number, including unanswered attempts, with the consent basis each was placed under. Run it at the end of each campaign and archive it. Exports too large return `413` rather than a partial file; narrow the date range.
* **Usage.** Outbound minutes appear in the standard minutes and billing reports alongside inbound.

## Recommended baseline

Start every outbound agent from this baseline, then adjust per use case with your counsel's sign-off.

| Setting | Baseline |
| - | - |
| `contactPolicy.require_consent_basis` | `true` |
| `contactPolicy.calling_windows` | `09:00` to `20:00`, `timezone_source: callee_npanxx` |
| `contactPolicy.abandon_rate_alert_threshold` | `0.03` (3%) |
| `outboundStaticGreeting` | Business name plus AI disclosure |
| `aiDisclosure` | `enabled: true` |
| `amdConfig` | `enabled`, `balanced`, `leave_voicemail`, `reprompt` |
| `voicemailMessage` | Who, why, and a callback number |
| IVR navigation | Off, unless calling businesses |
| Retry policy | 3× no answer, 2× busy, 1× voicemail, 1× failed; reschedule out-of-window calls |
| Attempt caps | 2 per day, 5 per week, 1 hour apart |
| `on_exhausted` | `webhook` to your CRM |
| Concurrency (CRM pacing) | 2 to 3 calls per agent to start |
| Agent instructions | Spoken and keypress opt-out, polite close |

The agent-level controls in one update ([`PUT /v1/agents/{agentId}`](https://docs.telzino.com/api-reference/agents/update)):

```json theme={null}
{
  "outboundStaticGreeting": "Hi, this is an AI assistant calling on behalf of Acme Dental.",
  "aiDisclosure": {
    "enabled": true,
    "text": "I'm an automated AI assistant.",
    "position": "after_greeting"
  },
  "contactPolicy": {
    "require_consent_basis": true,
    "abandon_rate_alert_threshold": 0.03,
    "calling_windows": {
      "start_local": "09:00",
      "end_local": "20:00",
      "timezone_source": "callee_npanxx"
    }
  },
  "amdConfig": {
    "enabled": true,
    "sensitivity": "balanced",
    "on_machine": "leave_voicemail",
    "on_silence": "reprompt"
  },
  "voicemailMessage": "Hi, this is Acme Dental calling about your upcoming appointment. Please call us back at 555-010-0123."
}
```

## Pre-launch checklist

Run every check in a test organization before the first live campaign.

> [accordion] Safeguards
>
> * A call with no `consent_basis` is refused with `400 consent_required`
> * A call outside calling hours is refused (`409 out_of_window`) or scheduled for the next opening
> * A number on the suppression list is refused with `409 suppressed`
> * A non-E.164 version of a suppressed number is normalized by your CRM, never dialled
> * A spoken opt-out mid-call ends the call, cancels retries, and adds the number to the suppression list (`suppression_added: true`)
> * A keypress opt-out works, if you offer one
> * The disclosure plays on every answered call, and `disclosure_delivered` is `true`
> * Attempt caps block a third call to the same number in one day

> [accordion] Call handling
>
> * AMD leaves the voicemail on a machine and does not misread a live person
> * Retries follow the policy, and `on_exhausted` reaches your CRM
> * Cancelling queued retries stops scheduled attempts
> * Your CRM backs off correctly on `429 agent_at_capacity`
> * The result webhook updates the right CRM record
> * The compliance export lists every attempt, including unanswered ones

## Known limitations

| Limitation | What to do |
| - | - |
| No default calling hours | Always set them |
| Area-code timezone can be wrong for ported numbers and split area codes | Send `callee_timezone` when known |
| A transcript-only opt-out on a call answered by a phone menu or voicemail isn't added to the suppression list | Review results with `opt_out_detected: true` and `suppression_added: null` |
| Opt-outs heard on a call go to the calling organization's list by default | Consider turning on partner-wide or reseller-wide suppression, after checking with counsel |
| Numbers match only in exact E.164 | Normalize in your CRM before sending |
| Cancelling queued retries doesn't stop one already dialling | Suppress the number too |
| Disclosure can't be mandated above the agent level | Lock it into your agent template |
| `required_states` is not evaluated | Disclose on every call |
| Editing a retry policy doesn't affect chains already running | Cancel and re-trigger if rules must change mid-campaign |
| `429` calls are not queued | Retry from your CRM with backoff |
| The result `callback_url` is not signed | Use an unguessable URL and match call IDs |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.