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

> ## Agent Instructions
> Every Lath operation is POST https://platform.trylath.com/<operation name with dots replaced by slashes>, with a JSON body and `Authorization: Bearer <key>`. `email.send` is POST /email/send.
> Branch on `error.code`, never on `error.message`. Every refusal also carries `error.fix`, which names the next step.
> Send an `Idempotency-Key` header on any operation that is not retry-safe, so a retry cannot run it twice.
> A `lath_test_` key emails only the account's own members and sends no SMS; a `lath_live_` key reaches real recipients and is billed.
> The OpenAPI document, generated from the same registry as the routes, is at https://platform.trylath.com/openapi.json.

# Broadcasts and automations

> One message to an audience, and messages that send themselves when something happens. Both refuse to start until every reason they would fail is fixed.

## A broadcast is a draft that tells you what is wrong with it

`email.broadcast.create` makes a draft: a **published** template, sent from an address on a **verified** domain, to every contact with current consent for a topic — optionally narrowed by a segment.

It returns `readiness` immediately. An unverified sender, a missing postal address for the footer, an unknown topic, an unpublished template: each comes back as a problem with a `code`, what is wrong and the fix. You find out at create time, not when you press send on a campaign to twenty thousand people.

`email.broadcast.update` changes it while it is still a draft. `email.broadcast.list` and `.get` read them back — and `get` counts progress separately, because the `counts` on the broadcast itself are written once at send and never recomputed.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://platform.trylath.com/email/broadcast/create \
    -H "Authorization: Bearer $LATH_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name":"March update",
      "template":"product-update",
      "from":"news@yourdomain.com",
      "topic":"product-news"
    }'
  ```

  ```ts TypeScript theme={null}
  import { createLath } from "@trylath/sdk";

  const lath = createLath({ key: process.env.LATH_API_KEY });

  const { result } = await lath.email.broadcast.create({
    name: "March update",
    template: "product-update",
    from: "news@yourdomain.com",
    topic: "product-news",
  });
  ```

  ```bash CLI theme={null}
  lath email broadcast create \
    --name 'March update' \
    --template product-update \
    --from news@yourdomain.com \
    --topic product-news
  ```
</CodeGroup>

## Sending is the point of no return, and it is guarded

`email.broadcast.send` refuses unless all four hold: the sender is on a verified domain, the template is published, the topic exists, and the footer has a postal address. Those are the conditions under which a bulk send is lawful and deliverable, so they are enforced rather than recommended.

It then queues **one message per consenting, unsuppressed contact**, with that contact's properties as variables and a personal unsubscribe link. The consent guide covers what counts as consenting; nothing here bypasses it.

Give `scheduledAt` and the broadcast is queued now and released at that time, rather than depending on something being awake to trigger it.

## Pause, resume and cancel do exactly what they say

**Pause** holds a broadcast that is sending: messages not yet handed to delivery stop. Messages already sent are not recalled — nothing can recall those, and an API that implied otherwise would be lying.

**Resume** puts the held messages back in the queue and continues from where it stopped. Nobody is sent to twice: the ones that already went are untouched, and the audience policy is checked again for each remaining message at the moment it sends — so somebody who unsubscribed during the pause is not sent to.

**Cancel** closes a draft, and cancels any of its messages still queued. Again, what has already gone has gone.

SMS mirrors all of it — `sms.broadcast.create`, `.send`, `.pause`, `.resume`, `.cancel` — with the same guards and the stricter consent rules the consent guide describes for SMS.

## An automation is a broadcast that waits for a reason

`email.automation.create` takes an **event that enrols a contact**, an optional filter on that event's payload, and a tree of steps: `wait`, `send`, `branch`, `property.set`. A branch can test a contact property, a consent record, or a field of the event that started the run.

It is a draft with readiness, exactly like a broadcast, and **nothing runs until you activate it**. `email.automation.activate` is refused with the first readiness problem while any send inside it could not go out — an automation that is half-sendable is worse than one that is off, because it fails silently on a subset of people.

Once active, every matching event enrols its contact and the worker advances the runs.

## Changing one that is already running

`email.automation.update` changes the name, trigger, steps or re-entry rule. Runs already in progress keep the steps they started with **only until their next step** — so if the tree changes shape, pause it first. A contact halfway through a sequence that is being rewritten underneath them is where the surprising behaviour lives.

**Pause** stops new enrolments and freezes runs where they are; activating again resumes them. **Archive** retires it: no new enrolments, every run in progress cancelled, and the definition and history stay readable — because what you sent last quarter is a record you may need long after you stopped sending it.

`email.automation.get` returns readiness and run counts by status. `email.automation.run.list` shows the runs, and `email.automation.run.cancel` stops one contact's run without touching anybody else's.
