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

# CSV to launched SMS blast: the operator path end to end

> Walk the full path from a customers.csv on your disk to a launched SMS blast with per-recipient personalization — import with the wizard, pick the right sender identity per market, preflight the template, launch to one audience subset, and read the per-recipient conclusion log.

# CSV to launched SMS blast: the operator path end to end

Four deep guides cover the individual pieces of this flow — the CSV import wizard, sender-registration markets, the campaign create wizard, and personalization preflight — but they each stop at their own boundary. This page is the operator path that chains them into one sequence: **CSV on disk → launched blast with per-recipient variables**, with a verdict you can read recipient by recipient.

Walk it once in order and you never have to guess which deep page to consult at which step. Sections 2–5 name the deep page for the step they execute; come back to this page whenever you need "what's next."

## 1. The four files you are operating

The path crosses four surfaces in the dashboard, in this order:

| Step | Surface | Deep guide |
| - | - | - |
| Import | **Audience → Import** wizard — turns `customers.csv` into a saved static list | [Import and reconcile contacts](/guides/import-contacts) |
| Sender | **Outbound → Senders** — the registered identity each destination accepts | [Sender-ID pre-registration: the market-choosing matrix](/guides/sender-id-country-matrix) |
| Template | **Outbound → Templates** — the body with `{{var}}` tokens plus opt-out language | [Validate campaign personalization merge-tags before launch](/guides/campaign-personalization-preview) |
| Blast | **Outbound → Campaigns → Create campaign** — audience, send window, frequency cap, launch | [Build a blast or drip campaign with the create wizard](/guides/campaign-create-wizard) |

Two mend the fall-through you hit when the four guides are read separately:

* **Consent is recorded per contact, not per import.** The imported address becomes reachable only when a suppression scan and opt-in state let it through — that is why the template step ends with a checklist, not just a body.
* **The sender is chosen per destination country before the wizard step that launches.** Walking back from the launch gate to "pick a sender" is the rework this ordering avoids.

## 2. Import: customers.csv becomes a saved list

Start under **Audience → Import**. The wizard accepts a CSV with one row per recipient, carries `phone` and `email` as the addressing columns, and maps the rest onto contact fields. The columns this path cares about are the ones your template will dereference — `first_name` for `{{first_name}}`, `country_code` / `timezone` / `language` for the locale that quiet-hours and send windows evaluate against, and any business-specific custom field you defined beforehand (see [custom fields](/guides/custom-fields)).

Run the import deliberately:

1. **Upload and map.** Pick the file, match columns to field keys where the auto-detect guesses wrong, and leave `merge_strategy` at the default `skip` unless you are refreshing CRM attributes, in which case choose `merge`.
2. **Confirm the preview before it writes.** The dry-run answers three questions before anything lands — how many rows carry a parseable phone or email, how many match contacts already in Orbit, and how many duplicate inside the file itself. A preview with `rows_invalid > 0` or a nonzero in-file duplicate count means fix the export at the source; never commit an import whose preview you cannot explain.
3. **Watch it land.** The job runs in the background under **Audience → Imports**; rows it rejects land in a skipped-rows CSV with a per-row `reason` column you fix and re-import as a new job. A rollback window of 24 hours covers a mis-mapped run.
4. **Save as a static list.** When the job completes, materialize the imported cohort as a contact list (see [static contact lists](/guides/contact-lists-static-targeting)) — the create wizard's audience step picks lists, not import jobs.

If the import skipped rows for unparseable phone numbers, you have two paths: fix the export (the skipped CSV is keyed by row index for exactly this), or accept that those recipients never make the blast — the audience preview in step 5 will quantify the gap.

## 3. Sender: the right registered identity per market

Sender selection happens before the create wizard because a destination that requires pre-registration fails at send time, not at import time. Read the per-country registration level from `GET /api/v1/compliance/country-rules` — `none`, `recommended`, or `required` — and treat anything `required` as launch-gating until the registration reaches `approved`. The [sender-ID country matrix](/guides/sender-id-country-matrix) reads that signal per market; the five hardest (UK, Saudi Arabia, UAE, and the rest it lists) are the ones that surprise operators most.

For the blast itself, the choice is one of three:

* **A dedicated number** with the destination's registration approved. This is the only identity a `required` market accepts.
* **A Messaging Service sender pool** with an opt-out list attached — use it when you send on more than one identity and want per-pool throughput and suppression scoping.
* **The shared Devotel sender** only accepts markets whose registration level is `none` or `recommended`. If your audience spans a `required` market, the Review step will flag the unregistered destination and launch blocks on it either way.

Whatever you pick, outbound SMS still terminates through the Devotel wholesale network — the sender choice sets which identity is presented, never which route the message takes.

## 4. Template: preflight before first send

Compose the body once in **Outbound → Templates** so the blast and any future campaign reuse it (see [outbound templates](/guides/outbound-templates)). Use `{{token}}` merge tags — `{{first_name}}` is the common case — and run the preflight in this order:

1. **Merge-tag resolution.** Every token must match the five standard contact fields, the `{{coupon_code}}` generator, or a key on the campaign's `variables`. A wrong token — `{{firstName}}` instead of `{{first_name}}`, an unsupported field like `{{loyalty_tier}}` that no contact carries — resolves to blank for every recipient, and the send never errors. The dry-run's `personalization` block plus its `warnings[]` array name the unresolved tags; treat any warning that begins with `Personalization` as launching-blocked until re-run clean. Full contract: [validate personalization merge-tags before launch](/guides/campaign-personalization-preview).
2. **Data coverage on the audience.** Tags resolve against contact records, so check the dry-run's coverage hints per standard-field tag — a high miss rate means the CSV carried addresses but not the fields your template needs. Fill the contact data or drop the tag.
3. **Opt-out token present.** Every freeform SMS body must contain carrier-accepted opt-out language — `STOP`, `UNSUBSCRIBE`, `OPT OUT`, or `cancel`. Missing it flips the compliance row red at Review and the launch refuses. On a per-brand custom keyword list, the same must hold — see [opt-out lists](/guides/opt-out-lists).
4. **Quiet-hours and list-hygiene dry-run.** Run `POST /campaigns/:id/dry-run` (the create wizard's Review step runs it inline) and read `quiet_hours.skipped_estimate` and `warnings[]` — anything "quiet hours fully closed" or "no provider registered" resolves here, not at send time. The full gate walk-through lives in the [outbound pre-flight checklist](/guides/send-gates-preflight-checklist); the TCPA window and recipient-local handling are in [TCPA quiet hours and windows](/guides/tcpa-quiet-hours-and-windows).

The personalizer renders `{{token}}` against a sample recipient in the personalization-preview panel — use it to eyeball the substitution before the dry-run gates pass.

## 5. Launch: one audience subset, fallback off, per-recipient readback

With a list, a sender, and a clean template, open the create wizard under **Outbound → Campaigns → Create campaign**:

1. **Setup.** `blast`, channel `sms`, pick the sender from step 3 (a dedicated number, a Messaging Service sender pool, or the shared Devotel sender when the destination allows it). Leave the fallback chain empty on the first send — SMS-only keeps the verdict readable.
2. **Audience.** Pick the static list from step 2. The wizard mounts the audience preview with the net projection (suppression minus opt-outs minus unreachable) — sanity-check it against your import counters.
3. **Message.** Load the approved template. Confirm the per-recipient personalization preview resolves.
4. **Schedule.** Send now, or set a future `scheduled_at`. SMS exits the 8 AM–9 PM recipient-local window as deferred, not failed — out-of-window sends queue and release when the window opens (see [send-gating and quiet hours](/concepts/send-gating-and-quiet-hours)).
5. **Review.** The compliance checklist gates launch on machine-verified rows plus attestations; an audience that resolves to zero recipients and a body missing opt-out language are the two blocking failures most often hit.

Launch returns the campaign tracking page. After the send settles, read the result per recipient, not in aggregate — statuses that name why a message reached or missed are:

* `failed` — the SMS left Orbit but the carrier rejected it. The status log's failure reason is the carrier code; permanent failures de-enroll the recipient from further sends on that channel.
* `skipped_suppressed` — an opt-out list row or suppression entry caught the recipient at send time. The blast's own post-launch report and the suppression console between them explain which list caught it.
* `skipped_quiet_hours` — recipient-local time fell outside the send window. The recipient still queues and delivers when the window reopens; the skipped count is a projection, not a loss.
* `unreachable` — no address on the channel (an imported row with a missing/invalid phone), or the contact had no deliverable SMS identity.

The failure modes most likely to populate `failed` on a first CSV-import blast are, in order: numbers the import accepted but carriers reject (landline, VoIP-only ranges), destinations the sender registration hasn't cleared (the step-3 miss), and raw phone formats the import normalized but the carrier route rejects (less common — the import's own normalization is usually the fix).

## Cross-links back to the four pieces

* [Import and reconcile contacts](/guides/import-contacts) — the CSV wizard mechanics: field mapping, merge strategy, and skipped-row recovery this walkthrough assumes.
* [Sender-ID pre-registration: the market-choosing matrix](/guides/sender-id-country-matrix) — the choosing matrix for sender registration per destination.
* [Build a blast or drip campaign with the create wizard](/guides/campaign-create-wizard) — every wizard step, the launch gates, and the post-launch tracking page.
* [Validate campaign personalization merge-tags before launch](/guides/campaign-personalization-preview) — the token contract the template step enforces.

For the full campaign lifecycle at the API surface — the programmatic equivalent of this walkthrough — [send a campaign end-to-end](/guides/campaign-end-to-end).


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