Guides
Troubleshooting Campaign Delivery

Troubleshooting Campaign Delivery

The Campaigns API provides visibility into the execution control plane, enabling developers to trace campaign pipeline progress and programmatically diagnose failures at the batch level.

This guide covers how to interpret execution pipeline counts, differentiate between execution and package-level failures, and implement reliable alerting patterns.


Analyzing Pipeline Counts

The counts object returned by GET /campaigns/:id/status and GET /campaigns/:id/metrics maps directly to the stages of the execution pipeline:

{
  "counts": {
    "scheduled": 17491,
    "hydrated":  17491,
    "composed":  17485,
    "injected":  17485,
    "sent":      17480,
    "failed":         5
  }
}

Discrepancies between adjacent pipeline stages indicate specific operational boundaries:

  • scheduledhydrated: Represents audience resolution. A variance indicates records filtered out by suppression rules, missing consent, or invalid contact records.
  • hydratedcomposed: Represents content rendering. Variances typically involve merge tags referencing null fields or missing template assets causing per-piece composition failures.
  • composedinjected: Represents dispatch preparation. Variances generally point to missing sender credentials, disabled channels, or configuration mismatches.
  • injectedsent: Represents provider acknowledgment. A persistent variance here may indicate that the downstream channel provider has not yet acknowledged the payload or has rejected it asynchronously.
  • failed: The cumulative count of pieces that did not successfully reach the sent state.

Execution Failures vs. Package Failures

The API exposes two distinct failure surfaces corresponding to different layers of the infrastructure.

Execution Failures (execution.state: "failed")

An execution failure indicates that the run could not progress through the core pipeline. This state is exposed on GET /campaigns/:id/status:

{
  "execution": {
    "state": "failed",
    "lastExecution": {
      "status": "failed",
      "counts": { "scheduled": 5000, "hydrated": 0, "composed": 0, "injected": 0, "sent": 0, "failed": 5000 }
    }
  }
}

A common pattern is hydrated: 0, which indicates audience resolution failed—typically due to an empty segment, a deleted data schema column, or a missing audience binding.

Package Failures (packageStatus: "failed")

For channels generating execution packages (like Direct Mail), the pipeline may complete but artifact assembly may fail. This state is exposed on GET /campaigns/:id/runs:

{
  "runId": "5e5e5e5e-...",
  "runStatus": "enqueued",
  "packageStatus": "failed",
  "package": {
    "packageStatus": "failed",
    "failureKind": "artifact_write",
    "lastErrorCode": "AZURE_BLOB_403",
    "lastErrorMessage": "Tenant storage credentials rejected the write."
  }
}

The failureKind field provides operational categorization:

  • upstream_execution: The underlying run failed prior to package assembly. Look to execution.state for root cause details.
  • finalizer_aggregation: An error occurred while aggregating per-piece manifests. Often transient and safely retriable.
  • artifact_write: The platform encountered an IO or permission error writing to tenant-scoped storage.
  • handoff: The downstream provider integration rejected the assembled package upload.
  • provider: The provider acknowledged the hand-off but reported a subsequent processing failure.

Diagnostic Pattern

When integrating Campaigns API monitoring into operational workflows, monitor both execution status and package processing times.

import os, time, requests
 
API_KEY = os.environ["EXPERITURE_API_KEY"]
BASE = "https://api.experiture.ai/public/v1"
H = {"Authorization": f"Bearer {API_KEY}"}
 
def check_campaign_health(campaign_id):
    """Evaluates terminal failures and high failure rates."""
    s = requests.get(f"{BASE}/campaigns/{campaign_id}/status", headers=H).json()["data"]
    exec_ = s.get("execution", {})
 
    if exec_.get("state") == "failed":
        return {
            "campaign": campaign_id,
            "severity": "high",
            "reason": "execution_failed",
            "counts": exec_.get("lastExecution", {}).get("counts"),
        }
 
    counts = exec_.get("lastExecution", {}).get("counts") or {}
    scheduled = counts.get("scheduled", 0)
    failed = counts.get("failed", 0)
 
    # Establish an acceptable operational threshold
    if scheduled > 0 and (failed / scheduled) > 0.05:
        return {
            "campaign": campaign_id,
            "severity": "medium",
            "reason": "elevated_failure_rate",
            "rate": failed / scheduled,
            "counts": counts,
        }
 
    return None
 
def check_pending_runs(campaign_id, max_age_minutes=30):
    """Detects packages exceeding expected assembly timeframes."""
    runs = requests.get(f"{BASE}/campaigns/{campaign_id}/runs?limit=10", headers=H).json()["data"]["runs"]
    stuck = [
        r for r in runs
        if r.get("packageStatus") in ("pending", "assembling")
        and minutes_since(r["requestedAt"]) > max_age_minutes
    ]
    if stuck:
        return {
            "campaign": campaign_id,
            "severity": "warn",
            "reason": "package_stalled",
            "stuck": [{"runId": r["runId"], "status": r["packageStatus"]} for r in stuck],
        }
    return None

Alerting Best Practices:

  • Minor failure counts are expected (e.g., malformed addresses). Alerting should rely on proportional thresholds rather than absolute failure occurrences.
  • Time-based stall detection (max_age_minutes) is more robust than strict status polling, as large packages may legitimately reside in the assembling state during rendering.

Data Privacy and Artifact Classification

Execution artifacts include a piiClassification metadata field specifying data sensitivity levels:

none, contact, phone, address, postal_identity, message_content, tokenized_identifier

This classification supports downstream compliance tools, data warehouses, and audit pipelines by indicating whether an artifact contains sensitive information that requires redaction or secure logging.


Common API Error Codes

Operational API errors use the canonical JOURNEY_API.* namespace.

CodeMeaning
JOURNEY_API.PUBLIC.CAMPAIGNS.NOT_DRAFTAttempted configuration update (PUT) on a published campaign. Trigger new runs via POST /runs.
JOURNEY_API.PUBLIC.CAMPAIGNS.NOT_FOUNDCampaign identifier is invalid or does not exist within the token's tenant scope.
JOURNEY_API.CAMPAIGN_RUNS.NOT_PUBLISHEDAttempted POST /runs on a draft. The campaign must be published first.
JOURNEY_API.CAMPAIGN_RUNS.BACKPRESSUREThe concurrency limit (5 active runs) is reached. Implement backoff and retry.
JOURNEY_API.BROADCASTS.SCHEDULE.PREFLIGHT_BLOCKERSPreflight checks rejected the send. Execute POST /campaigns/:id/preflight for the detailed validation report.

See Also