# Check senders and runway on a schedule with Claude Code

A GitHub Actions workflow that runs Claude Code headless with a read-only key, asks which senders are down and which campaigns lack leads, and posts to Slack.

Works with: Claude Code, GitHub Actions, Slack.

MCP tools used (https://docs.versionseven.ai/guides/connect-your-ai):

- `sender_accounts` (read) The connected LinkedIn and email accounts.
- `reconnect_sender` (read) A link into the app to reconnect a sender.
- `campaigns_list` (read) List the organization's campaigns.
- `campaign_queue` (read) Backlog, daily capacity, runway and warnings.

The [sender monitor](https://docs.versionseven.ai/cookbook/sender-reconnect-monitor) and the [daily digest](https://docs.versionseven.ai/cookbook/daily-campaign-digest) are scripts that know exactly which fields to read. This recipe gets a similar result with no script at all: Claude Code runs headless on a schedule, with the Victoria AI MCP server attached over a read-only API key, is asked a plain question, and the answer goes to Slack. It's the right shape when the question changes often, or when you'd rather edit a sentence than a program.

## Before you start

- A Victoria AI API key with `accounts:read` and `campaigns:read` only, in the repository secret `VICTORIA_API_KEY`. Nothing in this workflow needs write access, and a read-only key means the run can't change anything even if the prompt is edited carelessly.
- An Anthropic API key in `ANTHROPIC_API_KEY` (or a Claude Code OAuth token from `claude setup-token` in `CLAUDE_CODE_OAUTH_TOKEN`). The run is billed to that account.
- A Slack [incoming webhook](https://api.slack.com/messaging/webhooks) URL in `SLACK_WEBHOOK_URL`.
- A GitHub repository to hold the workflow. It doesn't need any code.

## The workflow

```yaml
# .github/workflows/victoria-check.yml
name: Victoria sender and runway check
on:
  schedule:
    - cron: "0 7 * * 1-5"
  workflow_dispatch:

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Install Claude Code
        run: npm install -g @anthropic-ai/claude-code

      - name: Point Claude Code at Victoria AI
        run: |
          cat > victoria-mcp.json <<EOF
          {
            "mcpServers": {
              "victoria": {
                "type": "http",
                "url": "https://api.versionseven.ai/mcp",
                "headers": { "Authorization": "Bearer ${VICTORIA_API_KEY}" }
              }
            }
          }
          EOF
        env:
          VICTORIA_API_KEY: ${{ secrets.VICTORIA_API_KEY }}

      - name: Ask the question
        id: ask
        run: |
          claude -p "$(cat <<'PROMPT'
          Using the Victoria AI tools, answer in Slack mrkdwn, under 20 lines, no preamble:
          1. Call sender_accounts. List every account whose is_active is false, with its platform and name, and for each one call reconnect_sender and include the link it returns.
          2. Call campaigns_list, then campaign_queue for each active campaign. List every campaign with days_of_runway under 5, or null, with its backlog, daily capacity and any warnings.
          If nothing is wrong, say so in one line. Don't call any other tool.
          PROMPT
          )" \
            --mcp-config victoria-mcp.json \
            --allowedTools "mcp__victoria__sender_accounts,mcp__victoria__reconnect_sender,mcp__victoria__campaigns_list,mcp__victoria__campaign_queue" \
            --max-turns 30 \
            --output-format json > result.json
          echo "text<<EOF" >> "$GITHUB_OUTPUT"
          jq -r '.result' result.json >> "$GITHUB_OUTPUT"
          echo "EOF" >> "$GITHUB_OUTPUT"
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

      - name: Post to Slack
        run: |
          jq -n --arg text "$TEXT" '{text: $text}' \
            | curl -sS -X POST -H "Content-Type: application/json" --data @- "$SLACK_WEBHOOK_URL"
        env:
          TEXT: ${{ steps.ask.outputs.text }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
```

Commit it, add the three secrets under **Settings → Secrets and variables → Actions**, and run it once from the **Actions** tab with **Run workflow**. A message like this arrives:

```text
*Disconnected senders*
• jane-doe (linkedin): reconnect at https://app.versionseven.ai/...
*Short on leads*
• Q4 outbound: 3 days of runway (60 leads at 20 a day)
```

## How the pieces fit

- **`claude -p`** runs one non-interactive turn and exits; `--output-format json` wraps the answer in a JSON object whose `result` field is the text, which `jq` lifts into a step output for the Slack step.
- **`--mcp-config`** attaches the Victoria AI server for this run only, with the API key as the bearer header. The file is written by the previous step from the secret, so the key never appears in the workflow file or the log.
- **`--allowedTools`** names the four tools the prompt may use, with the `mcp__victoria__<tool>` naming Claude Code gives MCP tools. Everything else is refused for the run, even though the server would list more. With a read-only key the server lists only reads anyway; the allow list narrows it further to exactly the question.
- **`--max-turns 30`** caps the tool-call loop so a confused run can't spin. Two tools per sender plus two per campaign fits comfortably.
- **`reconnect_sender`** answers with a link into the app and mints nothing itself, so a read-only key is enough and nothing is consumed by asking.

## Variations

- **A different question**: edit the prompt. "Which campaigns got positive replies yesterday, by name?" needs `campaign_analysis` added to `--allowedTools`; the [weekly review](https://docs.versionseven.ai/cookbook/weekly-review-with-claude) prompt works here too, with its tools listed.
- **The official action**: `anthropics/claude-code-action@v1` runs the same thing from a workflow step, taking `prompt` and `claude_args` (where `--mcp-config`, `--allowedTools` and `--max-turns` go) and the API key as `anthropic_api_key`. The CLI steps above make the MCP file and the Slack hand-off explicit, which is why this recipe uses them.
- **A server instead of GitHub**: the same `claude -p` line under cron, with `ANTHROPIC_API_KEY` and `VICTORIA_API_KEY` in the environment.
- **A write**: pausing a campaign whose senders are all disconnected would need `campaigns:write`, `update_campaign` in the allow list and a sentence in the prompt. In an unattended run no one is there to approve, so Claude Code would pause whatever the prompt says to pause; the [sender monitor](https://docs.versionseven.ai/cookbook/sender-reconnect-monitor) does the same thing deterministically, which is the better tool for an unattended write.

## What to expect

- Each run makes a handful of tool calls, inside the key's 120 a minute, and spends nothing on Victoria AI's side: the conversation runs on your Anthropic account, and none of the four tools touches credits.
- A refused call (a tool outside the key's scopes, or over the limit) comes back to Claude as a tool error with a code such as `INSUFFICIENT_SCOPE` or `RATE_LIMITED`, and the answer says so rather than the run failing.
- The answer is the model's reading of the tools' output. It's reliable for lists and thresholds like these; for a number that drives a decision, the [daily digest](https://docs.versionseven.ai/cookbook/daily-campaign-digest) reads the same endpoints without a model in between.

## Next steps

- [Alert on disconnected senders with a reconnect link](https://docs.versionseven.ai/cookbook/sender-reconnect-monitor), the scripted version, with state and auto-pause.
- [Build a campaign from a brief with a Claude Code skill](https://docs.versionseven.ai/cookbook/claude-code-campaign-skill), the interactive side of the same connection.
- [Connect your AI](https://docs.versionseven.ai/guides/connect-your-ai#cursor-and-scripts) for API-key connections and their limits.
