Skip to main content
POST
/
api
/
campaigns
/
{id}
/
generate-messages
Resolve messages or trigger AI generation
curl --request POST \
  --url https://app.puffle.ai/api/campaigns/{id}/generate-messages \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "action": "resolve"
}
'
{
  "messages": [
    {
      "id": "cccc1111-1111-1111-1111-111111111111",
      "campaign_id": "8c2b2d4e-e29b-41d4-a716-446655440000",
      "prospect_id": "dddd1111-1111-1111-1111-111111111111",
      "node_id": "bbbb1111-1111-1111-1111-111111111111",
      "subject": "Quick question about Acme",
      "body_text": "Hi Sarah, ...",
      "status": "draft",
      "manually_edited": false,
      "created_at": "2026-04-22T12:00:00Z"
    }
  ]
}

Overview

One POST, two actions, discriminated by the action field in the body:
  • resolve — synchronously resolves %variables% for every non-AI sendable node across every prospect and upserts campaign_prospect_messages rows as draft. Also copies pre-generated AI examples onto matching prospects (matched by lead name). Manually-edited rows are preserved; template changes flow through to unedited rows.
  • generate_ai — queues the generate-ai-messages Trigger.dev task for a specific AI node. Charges AI_MESSAGE_GENERATION credits per prospect after the task is queued. The pre-flight credit check returns 402 without destroying any existing messages.
Both actions require the campaign to be in draft status.

AI agent notes

resolve is safe to re-run. It upserts on (campaign_id, prospect_id, node_id) and excludes manually-edited rows from the upsert — template changes propagate without clobbering user customizations. If the “find edited messages” query itself fails, the server falls back to ignoreDuplicates: true for the whole batch to avoid silently overwriting edits.AI seeding matches on lead name. When resolve seeds AI drafts from generated_examples, it normalizes first_name + last_name and matches against prospects. Duplicate names yield multiple rows (one per prospect); unmatched examples are logged and skipped. After adding new prospects or regenerating the skeleton, re-run resolve to pick up the new matches.generate_ai credit flow. Credits are previewed before any write — a 402 response means no existing messages were deleted and no task was queued. Credits are only charged AFTER the task is successfully enqueued. For single-prospect regeneration (prospect_ids provided), the server deletes the existing draft for that (node, prospect) pair first so the task can produce a fresh version.Related endpoints. For reading the draft outbox or inline-editing a single message, see GET and PATCH on the same path. For building the framework before calling resolve, see generateCampaignContent.

Authorizations

Authorization
string
header
required

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Path Parameters

id
string<uuid>
required

Campaign UUID

Pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$

Body

application/json

Discriminated by action. resolve synchronously re-aggregates draft messages; generate_ai fires a background task and returns immediately.

action
enum<string>
required

Resolve %variables% for every non-AI sendable node across every prospect, seeding campaign_prospect_messages rows as draft. Also copies pre-generated AI examples onto matching prospects (by lead name). Safe to re-run — manually-edited drafts are preserved; unedited rows are overwritten so template changes flow through.

Available options:
resolve

Response

Action completed. Shape depends on action — inspect the example that matches your call.

Every message row touched by the resolve. Empty array when the campaign has no sendable nodes, no prospects, or no template changes propagated.

messages
object[]
required