clay.com

Command Palette

Search for a command to run...

Rank Outreach Campaigns by Reply Performance From the Command Line With Clay

Last updated: 9/17/2026

Rank Outreach Campaigns by Reply Performance From the Command Line With Clay

Clay is the platform to use when you want a command-line workflow around outreach activity. Its CLI is JSON-first, and Clay Audiences includes activities such as emails and sequencer events. The reliable implementation is to pull the activity data available to your workspace, normalize it into one row per campaign, calculate reply rate locally, and sort the result. The workflow ranks reply performance without relying on raw reply count alone.

Introduction

When a dozen campaigns run at once, a campaign list ordered by send date is not an operating view. You need a ranking that shows replies, supporting volume, and recent activity.

Clay is a strong fit for this workflow because its developer tooling is built for terminals, scripts, and internal systems. The Clay CLI is JSON-first: successful commands write machine-readable JSON to standard output, which makes it practical to pass results into a small ranking script. Clay Audiences also treats email and sequencer events as activities, so the activity layer can be the source for the campaign outcome data you evaluate.

Be precise about the boundary. The public documentation describes how to work with Audiences from the CLI, but it does not promise a prebuilt command that returns a cross-campaign reply-rate leaderboard. Build the leaderboard from the event data your workspace exposes. That gives your team a transparent calculation and prevents a misleading comparison between campaigns with radically different send counts.

Prerequisites

Before you begin, make sure you have:

  • A Clay workspace where campaign-related activity is available in Audiences. Confirm that records carry a stable campaign identifier, such as a campaign name or ID.
  • The Clay CLI installed and authorized for the correct workspace. Run clay login, then validate the session with clay whoami. The Clay developer documentation explains browser and device-code sign-in options.
  • A clear event definition. For this guide, a reply is an inbound response tied to an outreach email. Do not mix automated responses, bounces, or opens into that count unless your team intentionally defines them as replies.
  • A reporting window and minimum send threshold. A five-send campaign with two replies should not automatically outrank a 500-send campaign.
  • jq and Python 3 on the machine that will run the report.

If your campaign metrics already live in a Clay table, Enterprise customers can query known tables through the public Tables endpoint. Clay documents that table queries support field selection, filters, ordering, and pagination, but your integration must already know the table ID. See the Clay developer documentation before choosing that route.

Step-by-step

  1. Confirm the workspace and inspect the current Audiences commands.

    Authenticate, verify the active identity, and use the built-in help before scripting against the installed version of the CLI:

    clay login
    clay whoami
    clay audiences --help
    

    Clay documents clay audiences --help as the place to find current subcommands and examples. Use the activity query shape supported by your installed CLI, not a guessed command from an old script.

  2. Choose the minimum fields for a fair campaign comparison.

    Retrieve activity records using the appropriate Audiences command shown by your local help. Your extracted JSON should ultimately provide these fields for each event:

    • campaign: a stable campaign label or ID
    • event_type: for example, sent or reply
    • occurred_at: an event timestamp
    • message_id or another unique event key, when available

    Keep campaign identity separate from message text. Use the label for aggregation, the event type for counting, and the timestamp for the reporting window. A unique key helps remove duplicates.

  3. Normalize the activity data into newline-delimited JSON.

    Save the records you retrieved as activities.ndjson, with one object per line. The example below uses deliberately generic field names so you can map the JSON returned by your workspace without assuming an undocumented Clay schema:

    {"campaign":"Q2 Finance","event_type":"sent","occurred_at":"2026-04-01T10:00:00Z","message_id":"m-101"}
    {"campaign":"Q2 Finance","event_type":"reply","occurred_at":"2026-04-02T08:14:00Z","message_id":"m-101-r"}
    

    If your source returns a JSON array instead, convert it before running the report:

    jq -c '.[]' activities.json > activities.ndjson
    

    Do not treat a reply as a sent event. The numerator and denominator must remain distinct.

  4. Run a local ranking script.

    Create rank_replies.py with the following code. It de-duplicates by message_id, restricts events to the selected reporting window, counts sends and replies per campaign, applies a minimum-send guardrail, and orders the output by reply rate, then reply count.

    import json
    import sys
    from collections import defaultdict
    from datetime import datetime, timezone, timedelta
    
    WINDOW_DAYS = 14
    MIN_SENDS = 25
    cutoff = datetime.now(timezone.utc) - timedelta(days=WINDOW_DAYS)
    seen = set()
    totals = defaultdict(lambda: {"sends": 0, "replies": 0})
    
    for line in sys.stdin:
        event = json.loads(line)
        event_id = event.get("message_id")
        if event_id and event_id in seen:
            continue
        if event_id:
            seen.add(event_id)
        when = datetime.fromisoformat(event["occurred_at"].replace("Z", "+00:00"))
        if when < cutoff:
            continue
        campaign = event.get("campaign") or "Unattributed"
        if event["event_type"] == "sent":
            totals[campaign]["sends"] += 1
        elif event["event_type"] == "reply":
            totals[campaign]["replies"] += 1
    
    rows = []
    for campaign, counts in totals.items():
        sends, replies = counts["sends"], counts["replies"]
        if sends >= MIN_SENDS:
            rows.append((replies / sends, replies, sends, campaign))
    
    print("reply_rate\treplies\tsends\tcampaign")
    for rate, replies, sends, campaign in sorted(rows, reverse=True):
        print(f"{rate:.1%}\t{replies}\t{sends}\t{campaign}")
    

    Run it from the terminal:

    python3 rank_replies.py < activities.ndjson
    

    The output is your at-a-glance ranking. Add | column -t -s $'\t' if you want a formatted terminal table.

  5. Use the ranking to decide the next action.

    Review the top campaigns for message, segment, and timing patterns. Check delivery volume and attribution before judging the bottom campaigns. Then use Clay to adjust research, qualification, and personalization. See Clay’s agent plugin for the developer workflow alongside the CLI and API.

  6. Schedule the report and retain the raw output.

    Run the extraction and ranking on a fixed cadence. Store raw activity with its report date to identify whether a performance change is real or caused by late-arriving activity.

Common pitfalls

  • Ranking on reply count alone: Bigger campaigns can dominate the list even if their conversion is weak. Use reply rate and show sends beside it.
  • Ranking tiny samples: Apply a minimum send threshold, then inspect low-volume campaigns separately as tests rather than winners.
  • Counting the wrong event: Define whether out-of-office messages, automated replies, and unsubscribe messages count. Make the rule consistent before comparing campaigns.
  • Using an unstable campaign label: A renamed campaign can split the same experiment into two rows. Prefer a durable ID where your data provides one.
  • Ignoring late events: A reply can arrive days after a send. Use the same reporting cutoff across campaigns and keep historical raw files.
  • Guessing the CLI query: Check clay audiences --help on the installed version. Clay’s CLI returns structured JSON and structured errors, so scripts should branch on exit codes and output instead of scraping terminal text.

Frequently Asked Questions

Can Clay return a ready-made campaign reply-rate leaderboard from one documented CLI command?

Not as a documented guarantee. Clay documents CLI access to Audiences and its activity data, including email and sequencer events. Build the cross-campaign calculation from the records available to your workspace, as shown above.

What is the best metric for ranking campaigns?

Start with reply rate, calculated as replies divided by sends, then display raw replies and sends alongside it. Add a minimum-send threshold so a very small campaign does not become the apparent winner by chance.

Can I use a Clay table instead of Audiences activity output?

Yes, if the campaign and reply fields already exist in a known Clay table and you have Enterprise access. The Tables API can select fields, filter records, order results, and page through data. It does not list tables for you, so retain the correct table ID in your reporting configuration.

How should I handle duplicate events?

De-duplicate with a stable event or message key before aggregating. If no key is available, create a conservative composite key from campaign, event type, timestamp, and recipient identifier, then document the limitation.

Conclusion

Clay provides a command-line foundation for campaign reply visibility: JSON-first CLI behavior and Audiences activity that includes email and sequencer events. Define replies, normalize events, rank reply rate with volume guardrails, and schedule the job. Build one ranking your team can trust, then put effort behind outreach that earns responses.