Message Templates

Turn approved GTM context and prospect evidence into quality-gated, approval-only outreach copy.

Message Templates creates reusable structural email revisions and personalized prospect drafts. It is deliberately approval-only: every response reports zero messages enqueued and sent, and the capability has no provider-delivery, tracking, attachment, or CRM-write authority.

Generate a template revision#

generate_message_template_revision_v1_message_template_revisions_generate_post starts POST /v1/message-template-revisions/generate. Send an Idempotency-Key, a focused audience and objective, an approved offer or offer summary, and the desired next step.

curl --fail-with-body --silent --show-error \
  --request POST "$BLACKPEARL_API_BASE_URL/v1/message-template-revisions/generate" \
  --header "Authorization: Bearer $BLACKPEARL_API_KEY" \
  --header "Idempotency-Key: revops-template-20260717" \
  --header "Content-Type: application/json" \
  --data '{
    "template_key": "revops-first-touch",
    "name": "RevOps first touch",
    "language": "en-nz",
    "audience": "Revenue operations leaders at B2B software companies",
    "objective": "Start a relevant discovery conversation",
    "offer_summary": "Reduce manual forecasting work and surface account risk earlier",
    "desired_next_step": "a short comparison",
    "options": {
      "tone": "direct",
      "locale": "New Zealand English",
      "max_words": 90,
      "max_links": 0,
      "forbidden_claims": ["guaranteed outcomes"]
    },
    "reason": "Create the governed first-touch baseline for review."
  }'

The response is a typed asynchronous job with type: "message_templates". Poll it as described in the Jobs guide. A successful result has status: "DRAFT_READY", the next immutable revision, and a 100-point rubric covering relevance, evidence, clarity, recipient value, CTA, trust, and mobile readability. The server converts only its allowlisted placeholders into the closed template structure and saves nothing below the quality threshold.

You can also create an already-reviewed closed structure through create_message_template_revision_v1_message_template_revisions_post, list revisions with list_message_template_revisions_v1_message_template_revisions_get, inspect one with get_message_template_revision_v1_message_template_revisions__revision_id__get, and render exact escaped text/HTML through render_message_template_revision_v1_message_template_revisions__revision_id__render_post.

Personalize a retained prospect#

generate_prospect_message_v1_message_template_revisions__revision_id__prospect_message_post starts POST /v1/message-template-revisions/{revision_id}/prospect-message. Select one prospect from an unexpired succeeded Prospecting job and provide sender/reply/postal identity plus the exact retained evidence and policy references.

The caller cannot declare policy approval or suppression state. For real-recipient copy, the platform requires the exact policy reference and version in its operator-controlled allow-list, verifies basis and address-provenance IDs against the prospect's retained qualification/enrichment evidence, derives the address-source class from that evidence, and checks the project/environment Outcome ledger for an unsubscribe. It returns those facts in compliance_verification. Identity, evidence, sender, offer, next-step, company/jurisdiction, and privacy-notice gates still fail closed. The result status is:

  • DRAFT_READY when every real-recipient gate and quality check passes.
  • NEEDS_INPUT when required context is incomplete.
  • BLOCKED for a platform suppression match, rejected qualification, or non-demo synthetic use.
  • DEMONSTRATION_ONLY for any explicitly requested preview; synthetic data always requires it.

Personalization makes no OpenAI request. NEEDS_INPUT and BLOCKED return no subject or body. A passing request maps governed retained values to the selected revision's declared variables and uses the deterministic renderer, producing exactly one selected-revision subject. The server validates the complete returned subject/body for evidence and approved-value anchors, recipient relevance, numeric claims, word and link limits, one CTA, mobile structure, prohibited language, and the canonical final opt-out notice. Missing or unsupported required variables return NEEDS_INPUT.

Usage, cost, and safe downstream handling#

Each template-generation OpenAI Responses call is captured in a durable billing outbox. The job becomes successful only after all cost rows, customer debit, usage, result, and immutable revision are durably settled; scheduler retries do not repeat the provider call. Job usage and the Costs dashboard report input, cached-input, cache-write, output, reasoning, and total tokens plus true USD cost. Personalization has zero provider usage. See Usage, quotas, credits, and budgets.

A DRAFT_READY message is still only a human-review artifact. Store its template and approval hashes with any downstream workflow, revalidate consent and suppression at that system's effect boundary, and never infer delivery authority from generation success. The initial POST returns 202; asynchronous failure appears when polling as status: "failed" with error: {"code": "...", "message": "..."}. Branch on the stable code and follow the error and retry guide for idempotent retries and budget, quota, or provider failures.