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

# Launch Outbound

> Validate and launch a draft Outbound execution.

**CLI:**

```bash theme={null}
puffle campaign launch --id <id>
```

## Overview

The call is synchronous about **validation** (returns 400 if the draft is not ready) but asynchronous about sending: once it returns `200`, a Trigger.dev task owns the work. Poll `GET /api/campaigns/:id` for progress.

The status transitions atomically via compare-and-swap: `draft -> launching`, then inside the background task `launching -> active`. Only one launch can run at a time; a second concurrent call returns `409`.

## Error reference

| Status | When                                                                                            |
| ------ | ----------------------------------------------------------------------------------------------- |
| `400`  | Draft is not in `draft` status; the message names the current status                            |
| `400`  | Draft has no sender accounts, no pending contacts, or no sendable sequence nodes                |
| `400`  | Sequence contains nodes with missing content; message lists the positions                       |
| `400`  | Email senders are not `active` or are missing a configured sending inbox                        |
| `400`  | LinkedIn connected account no longer exists upstream and must be reconnected                    |
| `400`  | All contacts were skipped by dedup; nothing to send                                             |
| `401`  | Missing or invalid Bearer token                                                                 |
| `404`  | Outbound row does not exist or is not owned by the authenticated user                           |
| `409`  | A launch is already in progress or the row changed concurrently                                 |
| `500`  | Message resolution or Trigger.dev dispatch failed. Status rolled back to `draft`; retry is safe |
| `503`  | LinkedIn transient sender verification error. Retry after 30 seconds                            |

## AI agent notes

<Note>
  **Before calling this endpoint**, verify the draft is ready:

  1. `GET /api/campaigns/:id` -> `status` must be `"draft"`
  2. `GET /api/campaigns/:id/leads?status=pending` -> `total` must be greater than zero; review the actual recipients, not only an aggregate on the Outbound row
  3. At least one sender account is attached
  4. Every sendable sequence node has non-empty content and no unresolved personalization placeholder
  5. For email: senders are `active` and have a configured sending inbox
  6. For LinkedIn: the connected account resolves through the sending backend
  7. Present the channel, sender identity, pending recipient count, and final sequence to the human and obtain explicit launch approval

  Read `GET /api/campaigns/{id}` first when you need to inspect the draft before launch; this endpoint still performs final readiness checks and returns a specific blocker when launch is not allowed.
</Note>

**After a successful launch:**

* Status is `"launching"` immediately; do not re-launch.
* Within about 2 minutes the background task transitions to `"active"` and begins staggered sends.
* Poll `GET /api/campaigns/:id` every 30 seconds until status is `"active"`, then at 1-5 minute cadence for progress.
* `stats.total` is set at launch; counters such as `sent`, `replied`, and `bounced` increment as contacts execute.

If launch returns `400`, do not retry the same request. Surface the blocker, correct the draft, repeat the readiness checks, and ask for confirmation again if the recipient set, sender, or sequence changed. On `409`, fetch the Outbound before deciding whether another launch is needed. On `500`, the server restores `draft`; verify that state before one retry.

**Idempotency:** retrying the same call is safe. The background task is keyed by `campaignId + updated_at`, and on any failure the server rolls back writes.

**Stopping:** use `POST /api/campaigns/:id/pause` to halt mid-flight; `POST /api/campaigns/:id/resume` to continue.


## OpenAPI

````yaml post /api/campaigns/{id}/launch
openapi: 3.0.3
info:
  title: Puffle API
  version: 1.0.0
  description: >-
    HTTP API for the Puffle GTM platform - Feed and Lead Finder search
    preparation, campaign-backed Outbound execution, Puffle Agent runs, sender
    management, and Unibox reply workflows. Designed for operation by both
    humans and autonomous AI agents. Agents should start at the [Agent
    Playbook](/guides/agent-playbook) which prescribes a workspace-context check
    (`GET /api/context`) and core user journeys with exact call ordering.
  contact:
    name: Puffle Support
    url: https://puffle.ai
servers:
  - url: https://app.puffle.ai
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Campaigns
    description: >-
      Campaign-backed Outbound execution. These endpoints manage draft,
      launching, active, paused, and completed Outbound runs with sequence
      nodes, contacts, and sender accounts.
  - name: Accounts
    description: Signed-in user account, settings, and workspace context endpoints.
  - name: Billing
    description: Billing customer portal and account billing endpoints.
  - name: Senders
    description: >-
      Connected sender accounts, email inboxes, sending domains, DNS
      verification, warmup, and sender capacity.
  - name: Socials
    description: >-
      Read connected LinkedIn and X accounts, review current and archived posts,
      and reconcile publication status. Creating, editing, scheduling,
      publishing, OAuth connection, analytics, and mentions remain
      dashboard-managed workflows and are not part of the public Bearer-token
      API.
paths:
  /api/campaigns/{id}/launch:
    post:
      tags:
        - Campaigns
      summary: Launch Outbound
      description: >-
        Validate and kick off the background execution task for a draft Outbound
        execution. The call is synchronous about validation but asynchronous
        about sending once Trigger.dev takes over.
      operationId: launchCampaign
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            description: Campaign UUID
      responses:
        '200':
          description: Launch dispatched. Background task is running.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    enum:
                      - true
                  message:
                    type: string
                    description: Always "Campaign launch started" on success
                  runId:
                    type: string
                    description: Trigger.dev run identifier.
                  prospectsToProcess:
                    type: integer
                    minimum: 0
                    maximum: 9007199254740991
                  warning:
                    type: string
                  warnings:
                    type: array
                    items:
                      type: string
                  linkedinSenderSafety:
                    type: object
                    properties:
                      tier:
                        type: integer
                        minimum: 0
                        maximum: 9007199254740991
                      reason:
                        type: string
                        nullable: true
                      dailyLimit:
                        type: integer
                        minimum: 0
                        maximum: 9007199254740991
                      weeklyLimit:
                        type: integer
                        minimum: 0
                        maximum: 9007199254740991
                    required:
                      - tier
                      - reason
                      - dailyLimit
                      - weeklyLimit
                    additionalProperties: false
                  skippedProspects:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        name:
                          type: string
                        linkedin_url:
                          type: string
                          nullable: true
                      required:
                        - id
                        - name
                        - linkedin_url
                      additionalProperties: false
                  skippedCount:
                    type: integer
                    minimum: 0
                    maximum: 9007199254740991
                required:
                  - success
                  - message
                  - runId
                  - prospectsToProcess
                additionalProperties: false
        '400':
          description: >-
            Outbound not ready - bad status, missing senders, empty contacts,
            empty nodes, or all contacts skipped.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
                additionalProperties: false
        '401':
          description: Missing or invalid Bearer token.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
                additionalProperties: false
        '404':
          description: Campaign doesn't exist or isn't owned by the authenticated user.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
                additionalProperties: false
        '409':
          description: >-
            A launch is already in progress or the campaign status changed
            concurrently.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
                additionalProperties: false
        '500':
          description: >-
            Message resolution or Trigger.dev dispatch failed. Status rolls back
            to draft.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
                additionalProperties: false
        '503':
          description: >-
            LinkedIn only. Transient sender verification error. Retry after the
            upstream recovers.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
                additionalProperties: false
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: pk_live_...

````