> ## 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.

# Enviar una campaña de principio a fin: borrador, audiencia, dry-run, lanzamiento, analítica

> Lleve una campaña del borrador al lanzamiento y a la medición — elija una audiencia, previsualice destinatarios, ejecute el dry-run previo, lance como blast o drip y lea estadísticas, lift de holdout y ROI.

## Nota de idioma

Esta versión en español es una traducción abreviada: las secciones «4. Personalization and locale», «8. Subscribe to campaign webhooks» y «Worked example: retail promo, draft to analytics» existen solo en inglés: [/en/guides/campaign-end-to-end](https://docs.orbit.devotel.io/en/guides/campaign-end-to-end). Los bloques de código, rutas de endpoints, nombres de campos de API y códigos de error permanecen en inglés.

# Enviar una campaña de principio a fin

Esta guía recorre el ciclo de vida completo de una campaña por la API: crear un borrador, adjuntar una audiencia, verificar la personalización, ejecutar el dry-run previo, lanzar y medir el resultado. Una vez recorrido, la misma secuencia funciona para cada canal — SMS, WhatsApp, email, RCS, voice, push.

## 1. Requisitos previos

* **Una clave API con el scope `campaigns:write`.** Todas las lecturas de abajo (preview de audiencia, dry-run, estadísticas) también aceptan `campaigns:read`, pero las acciones de lanzamiento requieren escritura. Cree o edite claves en el dashboard bajo Settings → API keys.
* **Node SDK (opcional).** `npm install @devotel-orbit/node` si prefiere las pestañas SDK a cURL.
* **Una identidad de envío para su canal.** Para SMS/MMS, un número o pool de remitentes con los registros adecuados (el A2P de EE. UU. requiere [10DLC](https://docs.orbit.devotel.io/guides/10dlc-registration.md)); para WhatsApp, una WABA conectada; para email, un dominio remitente verificado.
* **Una fuente de audiencia legitimada.** Una lista de contactos, un segmento en vivo, un CSV de direcciones, una audiencia CRM externa (lista de HubSpot / reporte de Salesforce) o un conjunto elegido a mano. Las audiencias deben venir de destinatarios con opt-in — la supresión y las bajas se aplican en el envío, no quedan a cargo del remitente.

## 2. Crear la campaña borrador

Cree la campaña con un canal, una audiencia y un `message_template`. El template es opcional al crear — un borrador sin template no puede lanzarse — pero proporcionarlo desde el inicio mantiene el borrador completo.

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/campaigns" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Retail promo — summer sale",
    "type": "blast",
    "channel": "sms",
    "audience_type": "list",
    "audience_id": "list_summerVIPs",
    "message_template": "Hi {{first_name}}, early access to our summer sale starts now: 25% off through Sunday. Reply STOP to opt out.",
    "send_time_optimization": "fixed"
  }'
```

```typescript Node.js (TypeScript) theme={null}
import { Orbit } from "@devotel-orbit/node";

const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY });

const campaign = await orbit.campaigns.create({
  name: "Retail promo — summer sale",
  channel: "sms",
  message_template:
    "Hi {{first_name}}, early access to our summer sale starts now: 25% off through Sunday. Reply STOP to opt out.",
});
```

Campos clave al crear:

| Campo | Notas |
| - | - |
| `type` | `blast` (un envío programado), `drip` (secuencia de varios pasos), `journey` (dirigido por eventos) o `ad`. |
| `audience_type` | `all`, `list`, `segment`, `csv` o `manual` (ids de contacto elegidos a mano). |
| `audience_id` | Requerido cuando `audience_type` es `list` o `segment`. |
| `audience_source` | Descriptor CRM opcional (`hubspot_list`, `salesforce_report`, …) que sustituye al par lista/segmento en el envío. |
| `message_template` | Hasta 100.000 caracteres; los cuerpos de email caben de sobra. Los tokens de personalización usan la forma `{{token}}`. |
| `subject` | Asunto exclusivo de email. Se ignora en canales que no son email. |
| `send_time_optimization` | `fixed` (un horario de lote) o `recipient-optimal` (mejor hora por contacto, vea el paso 6). |
| `throttle_rate` | Tope de envío en mensajes/segundo, 0–1000. Ausente o 0 = ritmo por defecto de la plataforma. |
| `scheduled_at` | Marca de tiempo ISO del lanzamiento, editable después. Debe estar en el futuro. |
| `credit_cap_usd_cents` | Tope de gasto duro; la campaña se pausa sola al alcanzarlo. |

La respuesta devuelve la campaña en estado `draft`. Nada se envía hasta que el paso 6 la lance explícitamente.

## 3. Adjuntar y verificar la audiencia

**Lista o segmento.** Pase `audience_type: "list" | "segment"` con un `audience_id`, o un descriptor CRM `audience_source`. Los segmentos se evalúan en vivo en el envío, así que un segmento que crece sigue funcionando.

**Destinatarios CSV.** Para una nómina puntual, POSTee las direcciones directamente en el array `variables.csv_recipients` de la campaña (E.164 para canales telefónicos).

**Selección manual.** `audience_type: "manual"` con `variables.manual_contact_ids` apunta a ids de contacto concretos — mismo cálculo de elegibilidad que una lista.

Antes de lanzar, cuente siempre en qué se resuelve la audiencia. `POST /api/v1/campaigns/audience/preview` acepta la misma forma de audiencia que lleva la campaña y devuelve `matching_count` más una muestra de 10 contactos:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/campaigns/audience/preview" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "audience_type": "list",
    "audience_id": "list_summerVIPs",
    "channel": "sms"
  }'
```

Si pasa `channel`, la respuesta también devuelve una proyección neta: coincidencias brutas menos filas de supresión, menos opt-outs del canal, menos contactos sin dirección entregable para ese canal. Si `matching_count` vuelve en 0, arregle la audiencia antes de seguir — aquí es donde itera.

## 5. Pre-vuelo: ejecutar el dry-run

Antes del lanzamiento, POSTee el dry-run. Es de solo lectura — no muta estado, no toca el wallet y no encola jobs.

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

El informe agrupa los buckets centrales:

* `audience.total_in_audience` — recuento bruto de coincidencias.
* `audience.suppressed` / `opted_out` / `unreachable` — las cohortes de la proyección neta.
* `audience.deliverable` — el recuento neto exacto que intentará el lanzamiento.
* `cost.unit_price_cents` / `cost.total_cents` — tasado con el mismo resolver que usa la ruta de envío.
* `cost.balance_cents` / `cost.sufficient` — comprobación del wallet; un dry-run en verde implica una puerta de saldo en verde al lanzar.
* `quiet_hours.skipped_estimate` y `quiet_hours.window` — proporción de saltos proyectada por las quiet hours locales del destinatario.
* `warnings[]` — avisos no bloqueantes, mostrados aparte de los fallos duros.

Un dry-run sano con `warnings: []` y `cost.sufficient: true` despeja el lanzamiento.

## 6. Lanzar y elegir la forma de envío

Lance con `POST /api/v1/campaigns/cmp_abc123/send` (o `scheduled_at` para un lote diferido). Cuatro formas de envío entre las que elegir:

**Blast.** Un lote a una hora — la forma más simple; toda la audiencia entregable sale en una pasada al ritmo de `throttle_rate`.

**Secuencia drip.** `type: "drip"` con un array `steps` (hasta 50 pasos). Cada paso tiene su canal, su demora y su eventual condición de lectura.

**Journey.** `type: "journey"` — inscripción dirigida por eventos con analítica por nodo.

**Escalera de respaldo entre canales.** Para campañas omnicanal, el array `channels` es una cadena ordenada; cada entrada salvo la última lleva un disparador `fallback_on` (`failed`, `no_delivery` o `no_engagement`) y una ventana `fallback_after_seconds`.

Sea cual sea la forma, estos controles de ritmo aplican:

* **Throttle.** `throttle_rate` (msg/s) limita el ritmo de envío — el defecto de la plataforma es 50 msg/s.
* **Smart send.** `send_time_optimization: "recipient-optimal"` programa cada destinatario en su próxima mejor ventana y reparte la entrega durante las 24 h siguientes.
* **Ventana de envío.** `send_window: { start_local_hour, end_local_hour }` difiere a los destinatarios cuya hora local cae fuera de la ventana.

## 7. Observar y medir

**Estadísticas en vivo.** `GET /campaigns/:id/stats` devuelve totales de sent, delivered, failed, opened, clicked, replied.

**Lift de holdout.** Si la campaña fijó un holdout (`campaign_holdout_percent` en `variables`), `GET /campaigns/:id/holdout-lift` compara la cohorte tratada con el control y devuelve un delta de conversión con intervalo de confianza — así ve si el mensaje batió a no enviar nada.

**ROI / ROAS.** `GET /campaigns/:id/roas` devuelve ingreso atribuido, embudo de clics e ingreso incremental del holdout; `GET /campaigns/:id/cost` y `/campaigns/:id/export` sirven para conciliar el gasto.

## 9. Solución de problemas

* **La audiencia se resuelve a cero.** Re-ejecute el preview de audiencia pasando `channel`; la proyección neta indica qué cohorte la vació.
* **El dry-run muestra avisos en rojo.** Las entradas de `warnings[]` son explícitas — «no provider registered» significa que el canal no tiene identidad de envío configurada; «insufficient balance» significa `cost.sufficient: false`.
* **El lanzamiento devuelve 402 o una forma pending-approval.** Falló la comprobación de saldo o el circuito de aprobación está activo — recargue el wallet o espere la aprobación.
* **Campaña journey atascada en paused.** Una campaña se pausa sola al llegar a `credit_cap_usd_cents` o con una condición drip rota. Resuelva el bloqueo y luego `POST /campaigns/:id/resume`.


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