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

# Outbound goals, approvals, and direct send

> The three governance controls behind the Outbound hub — conversion goals that measure what a campaign actually drove, a supervisor approval gate before campaigns launch, and direct send for one-off messages outside campaign machinery.

# Goals, approvals, and direct send

The Outbound section of the dashboard exposes three controls that decide *how* sends leave your tenant, not just *what* you send:

* **Goals** — a conversion you care about (a reply, a purchase, a booking) defined once and attributed back to the messages, campaigns, and agents that touched the contact before they converted.
* **Approvals** — a supervisor gate in front of the launch button, so a campaign one operator drafts cannot go out until someone with approval rights reviews it.
* **Direct send** — the path for one-off sends that should never have been a campaign: one message to one contact, or a one-time CSV batch, with the same suppression and consent enforcement as a campaign.

All three are tenant-configured. Nothing changes how you send until you turn it on. This page covers each one and ends with a worked example that runs a campaign through a goal, the approval gate, and the send. For the campaign object these controls operate on, see the [campaign lifecycle concept](/concepts/campaign-lifecycle) and the [end-to-end campaign guide](/guides/campaign-end-to-end).

## When to use which

| Situation                                                             | Use                                                    |
| --------------------------------------------------------------------- | ------------------------------------------------------ |
| You need to prove what a campaign drove, not just that it delivered   | Define a goal and read its attribution stats           |
| A non-admin operator should not launch to a large audience unreviewed | Enable the approval gate and set an audience threshold |
| A support agent needs to answer one customer once                     | Direct send to a single contact                        |
| A finance export needs one CSV of recipients messaged today           | Direct send CSV batch                                  |

## Goals: define what counts as a conversion

A goal is a named conversion definition: *when event X happens for a contact, count it as a conversion, and attribute it to the messages and campaigns that touched the contact first.* Create goals in the dashboard under Outbound → Goals, or over the API:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/analytics/goals" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Promo purchase",
    "type": "custom_event",
    "config": { "event_type": "purchase.completed" },
    "value_cents": null,
    "lookback_days": 14,
    "attribution_model": "last_touch",
    "enabled": true
  }'
```

| Field               | Notes                                                                                                                                                                                                                  |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`              | How conversions arrive: `custom_event` or `webhook_hit` (an event you send in), `pixel_fire` (a tracking pixel URL Orbit mints for the goal), `manual` (recorded by API call), or `tag_added` (a contact tag applied). |
| `config`            | Type-specific matching — e.g. the `event_type` a custom event must carry.                                                                                                                                              |
| `value_cents`       | Optional fixed value per conversion. Leave `null` to carry the value on each conversion event instead.                                                                                                                 |
| `lookback_days`     | How far back a conversion can reach to credit a touch. Longer windows credit more touches; shorter windows credit fresher ones.                                                                                        |
| `attribution_model` | `last_touch`, `first_touch`, `linear`, or `time_decay` — decides how credit splits when several messages preceded the conversion.                                                                                      |

Goal events join the same event stream as everything else — the [CDP event model](/concepts/cdp-event-model) is the umbrella that makes a purchase event, a message delivery, and a tag change comparable enough to attribute. If you want conversions to arrive from your own backend, post them to the CDP ingest endpoint with a matching `event_type`; the goal does the rest.

A campaign also carries a lighter-weight goal concept of its own: drips accept `exit_goals` (pull a contact out of the sequence when they convert) and journeys have a per-campaign conversion-goal definition with its own analytics rollup. Use a tenant-level goal when you want cross-campaign attribution; use the campaign-level goal when the question is "did this journey work."

## Approvals: put a gate in front of launch

The approval gate inserts a review step between "an operator clicks send" and "messages leave the platform." It is aimed at teams where one person drafts campaigns and a different person is accountable for what goes out — the dual-control pattern regulated teams run on email platforms.

**How the gate engages.** Launching a campaign (`POST /campaigns/:id/send`) checks two org-level settings, readable via `GET /campaigns/approvals/settings`:

* `require_approval_default` — when `true`, every launch by a non-admin member is gated.
* `min_recipients_for_approval` — an optional audience threshold; only launches at or above this resolved audience size are gated.

Both default to off. Until an owner or admin enables them, launches go straight through — the gate is strictly opt-in:

```bash theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/campaigns/approvals/settings" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "require_approval_default": true, "min_recipients_for_approval": 500 }'
```

**Who does what.** A member or developer submits the launch. Instead of sending, the campaign moves to `pending_approval`, the launcher gets a `202` back with an approval id, and the request lands in the supervisors' queue. An owner or admin reviews it — Outbound → Approvals in the dashboard, or `GET /campaigns/approvals/pending` over the API — then either:

* **Approves** (`POST /campaigns/:id/approve`) — the campaign re-enters the normal launch path and sends exactly as if the supervisor had launched it themselves.
* **Rejects** (`POST /campaigns/:id/reject`, optional `reason`) — the campaign returns to `draft` for edits. Nothing was sent.

**What the reviewer is deciding.** The approval is on the campaign as it will launch: the resolved audience (the gate snapshots the audience size at request time), the message body, and the schedule. A supervisor who wants the body changed rejects with a reason and the campaign goes back to draft — there is no partial approve-with-edits step.

The gate changes *who can launch*, not *how messages leave*. An approved campaign dispatches over the same pipeline as a direct launch — the same suppression, consent, and quiet-hours enforcement, and the same provider chain.

## Direct send: one-off messages without a campaign

Campaigns are the right shape for audiences — lists, segments, schedules, analytics. Direct send is the right shape when none of that applies: a transactional one-off, a follow-up to a single conversation, or a one-time CSV run that will not repeat.

Three entry points, all under Outbound → Direct send:

1. **One contact.** Pick a contact in the picker; Orbit shows every reachable channel for them (SMS, WhatsApp, email) and the exact identifier it will address. You confirm the channel and land in that channel's composer with the recipient prefilled.
2. **A CSV list.** Upload a CSV of phone numbers or emails, map a template with per-row variables, and send the batch. It behaves like a batch send: parse, render, idempotent submit — without creating a campaign object.
3. **Straight to a channel composer.** If you already know the channel, jump directly to the SMS, WhatsApp, or email composer and address it yourself.

**Guardrails do not relax because a send is one-off.** Direct sends still pass through the same enforcement as campaign sends:

* Suppression lists and channel-level opt-outs are checked — a suppressed or opted-out recipient is held back, same as in a campaign.
* Frequency caps still count a direct send toward the recipient's budget.
* Quiet hours and send gating still defer or block sends that fall inside a recipient-local quiet window (see the [send gating and quiet hours concept](/concepts/send-gating-and-quiet-hours)).

**Limits.** Direct send has no audience resolution, no scheduling, no A/B variants, no holdout, and no campaign analytics rollup — deliveries report into message history, not a campaign stats page. If you find yourself scheduling the same CSV weekly or wanting per-variant measurement, that send has outgrown direct send; make it a campaign and the [end-to-end guide](/guides/campaign-end-to-end) applies.

## Worked example: gated launch with a goal attached

A complete pass through all three controls: define a goal, gate the launch behind a supervisor, send, and read attainment.

**1. Define the goal** (once per tenant) — count every `booking.completed` event as a conversion worth whatever the event carries, attributing over a 14-day lookback:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/analytics/goals" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Demo booking",
    "type": "custom_event",
    "config": { "event_type": "booking.completed" },
    "lookback_days": 14,
    "attribution_model": "last_touch"
  }'
```

**2. An operator drafts and tries to launch.** With `require_approval_default` on and the audience above the threshold, the launch parks instead of sending:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/campaigns/cmp_demo123/send" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

```json theme={null}
{ "gated": true, "approval_id": "capap_…", "status": "pending_approval" }
```

The campaign sits in `pending_approval` — live but parked, as the [campaign lifecycle](/concepts/campaign-lifecycle) status table puts it.

**3. A supervisor reviews and approves.** They list the queue, check the audience snapshot, body, and schedule, then approve:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/campaigns/cmp_demo123/approve" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

The campaign launches on the normal path: `sending`, then `running`, then `completed` as the audience drains.

**4. Conversions land against the goal.** As recipients book demos, your backend posts `booking.completed` events to CDP ingest with the contact id attached. Read attainment back on the goal — total conversions, attributed revenue, and which campaigns carried the credited touches:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/analytics/goals/<goal_id>/stats?window_days=30" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

The campaign that the gate held for review now shows up as an attributed campaign on the goal's stats — approval and attribution are two views of the same launch.

## Related pages

* [Campaign lifecycle](/concepts/campaign-lifecycle) — the status machine approvals park a campaign in, and what terminal outcomes feed analytics.
* [Send a campaign end-to-end](/guides/campaign-end-to-end) — the full launch workflow these controls attach to.
* [CDP event model](/concepts/cdp-event-model) — the event stream goal attribution reads.
* [Consent and suppression model](/concepts/consent-and-suppression-model) — the enforcement direct send does not bypass.
