Guides
Automating Direct Mail

Automating Direct Mail

Direct Mail execution produces a production package — a collection of files (rendered PDFs, a printer manifest, an IMb manifest) that commercial printers use to execute physical mail drops.

This guide covers how to programmatically trigger a Direct Mail drop, override print profiles and mail classes per run, and retrieve the finalized artifacts.


Direct Mail Execution Flow

The Direct Mail execution model differs from digital channels in a few key ways:

  1. Asynchronous Finalization. POST /schedule-jobs and POST /campaigns/:id/runs return 202 Accepted with status: "finalizing". The broadcast finalizer hydrates the audience, composes the mailpieces, performs IMb encoding, and writes the package to tenant-scoped storage.
  2. Artifact Generation. When the package is ready, a manifest is generated containing the rendered mailpiece PDF, a printer-readable roster, and IMb data.
  3. Execution State. Direct Mail provides package-level completion states and IMb data, independent of downstream USPS scan-feed events.

Triggering a Drop and Retrieving Artifacts

The following example demonstrates how to trigger a run against a published campaign, poll for the finalized package, and list the generated artifacts.

import os, time, requests
 
API_KEY = os.environ["EXPERITURE_API_KEY"]
BASE = "https://api.experiture.ai/public/v1"
H = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
 
CAMPAIGN_ID = "4130bada-9264-465f-bc0c-a26bebcfcc81"
 
# 1. Trigger a fresh run against the published campaign.
r = requests.post(
    f"{BASE}/campaigns/{CAMPAIGN_ID}/runs",
    headers={**H, "Idempotency-Key": "2026-q2-spring-recall-001"},
    json={
        "requestKind": "on_demand",  # v1 accepts only on_demand
        "metadata": {"orderRef": "PO-44219", "authorizedBy": "ops-ticket-4421"},
        "audience": None,  # Inherit the campaign's stored targeting
    },
)
r.raise_for_status()
run = r.json()["data"]
run_id = run["runId"]
print(f"Queued run {run_id} ({run['status']})")
 
# 2. Poll for the finalized package.
while True:
    runs = requests.get(f"{BASE}/campaigns/{CAMPAIGN_ID}/runs?limit=10", headers=H).json()["data"]["runs"]
    current_run = next(r for r in runs if r["runId"] == run_id)
    print(f"runStatus={current_run['runStatus']} packageStatus={current_run.get('packageStatus')}")
    if current_run.get("packageStatus") in ("ready", "handed_off", "failed", "cancelled"):
        break
    time.sleep(15)
 
# 3. Process the artifacts.
if current_run["packageStatus"] in ("ready", "handed_off"):
    for art in current_run["package"]["artifacts"]:
        print(f"  {art['role']:30s}  {art['uri']}  ({art['piiClassification']})")
else:
    print(f"Package failed: {current_run['package'].get('failureKind')}{current_run['package'].get('lastErrorMessage')}")

Key Concepts:

  • Idempotency-Key: Ensures that retries do not trigger duplicate physical drops.
  • requestKind: In v1 the only accepted value is on_demand (any other value returns 400). Use metadata to record operational context like the authorizing ticket for a manual drop.
  • packageStatus: The package status indicates artifact readiness, whereas runStatus tracks the pipeline stage.

Overriding Print and Postal Settings

The POST /campaigns/:id/runs and POST /campaigns/:id/schedule-jobs endpoints accept optional override fields for Direct Mail. Omitted fields default to the tenant's active profile assignment.

Overrides are useful for:

  • Printer Routing: Modifying printProfileId to use a different tenant-configured print profile.
  • Postal Contracts: Modifying postalProfileId to use a different permit or payment account.
  • Mail Class Adjustments: Modifying mailClass for a specific drop (e.g., overriding a default marketing_mail to first_class).
  • Format Specification: Modifying productionFormatKey to enforce a specific layout.
curl -X POST https://api.experiture.ai/public/v1/campaigns/$CID/runs \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "requestKind": "on_demand",
    "printProfileId": "11111111-1111-1111-1111-111111111111",
    "postalProfileId": "22222222-2222-2222-2222-222222222222",
    "productionFormatKey": "letter_8.5x11_window_envelope",
    "mailClass": "first_class",
    "processingCategory": "letter"
  }'

Accepted Values:

  • mailClass: first_class, marketing_mail, periodicals, bound_printed_matter, other.
  • processingCategory: letter, flat, card, parcel, other. Must be compatible with the selected mailClass.
  • productionFormatKey: Canonical layout keys such as letter_8.5x11, postcard_6x11, etc.

Understanding artifactResolutionMode

Controls which version of the rendered artwork is used for the drop:

ModeUsage
latest_live (default)Utilizes the most recent approved render.
approved_preparation_setPins the drop to a specific, previously-approved render. Requires executionArtifactPreparationSetId. Used for strict compliance scenarios.
noneBypasses artifact preparation entirely. Suitable for proof-only validation flows.

The Package State Machine

A Direct Mail package progresses through several states, reflected in packageStatus:

  • pending: The run request is queued.
  • assembling: The finalizer is hydrating the audience, rendering pieces, and writing artifacts.
  • ready: All artifacts are successfully written and available.
  • handed_off: The package has been acknowledged by the downstream provider integration.
  • failed: The worker encountered an error. failureKind and lastErrorMessage provide diagnostic details.
  • cancelled: The package was cancelled administratively before materialization.

Polling Best Practices: A polling interval of 15 to 30 seconds provides timely updates without exceeding rate limits, allowing sufficient time for audience hydration and rendering.


Artifact Schema and Retrieval

When packageStatus is ready, the run object includes a package.artifacts[] array:

{
  "artifactId": "art-roster-001",
  "role": "printer_manifest_csv",
  "uri": "abfss://prod@experiture.dfs.core.windows.net/packages/4130bada/5e5e5e5e/roster.csv",
  "contentType": "text/csv",
  "rowCount": 17480,
  "byteSize": "12482051",
  "checksumSha256": "0f6b8...",
  "piiClassification": "address",
  "metadata": { "addressLayout": "us_legacy_4_line" },
  "createdAt": "2026-05-09T13:42:18.000Z"
}

Common Artifact Roles:

  • printer_manifest_csv: The roster file containing recipient data and merged fields required for imposition.
  • mailpiece_pdf_set: The rendered artwork, consolidated or per-piece depending on the print profile.
  • postal_imb_manifest_csv: Intelligent Mail Barcode payloads for tracking.
  • usps_edoc_placeholder: A structured placeholder artifact representing future PostalOne!® submissions.

Accessing the Files

The artifact.uri field provides a tenant-scoped storage path (e.g., abfss://, s3://). It is not a direct, publicly accessible download URL. Retrieve these files using your Experiture-managed compute credentials or via a configured credential broker integration.

If the goal is solely to confirm package generation, the unitCount, checksumSha256, and rowCount fields provide sufficient validation without downloading the payloads.


See Also