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.
Last updated
- Claude Code
- GitHub Actions
- Slack
MCP tools used
sender_accountsread, accounts:readThe connected LinkedIn and email accounts.reconnect_senderread, accounts:readA link into the app to reconnect a sender.campaigns_listread, campaigns:readList the organization's campaigns.campaign_queueread, campaigns:readBacklog, daily capacity, runway and warnings.
The sender monitor and the daily 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:readandcampaigns:readonly, in the repository secretVICTORIA_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 fromclaude setup-tokeninCLAUDE_CODE_OAUTH_TOKEN). The run is billed to that account. - A Slack incoming webhook URL in
SLACK_WEBHOOK_URL. - A GitHub repository to hold the workflow. It doesn't need any code.
The workflow
# .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:
*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 -pruns one non-interactive turn and exits;--output-format jsonwraps the answer in a JSON object whoseresultfield is the text, whichjqlifts into a step output for the Slack step.--mcp-configattaches 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.--allowedToolsnames the four tools the prompt may use, with themcp__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 30caps the tool-call loop so a confused run can't spin. Two tools per sender plus two per campaign fits comfortably.reconnect_senderanswers 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_analysisadded to--allowedTools; the weekly review prompt works here too, with its tools listed. - The official action:
anthropics/claude-code-action@v1runs the same thing from a workflow step, takingpromptandclaude_args(where--mcp-config,--allowedToolsand--max-turnsgo) and the API key asanthropic_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 -pline under cron, withANTHROPIC_API_KEYandVICTORIA_API_KEYin the environment. - A write: pausing a campaign whose senders are all disconnected would need
campaigns:write,update_campaignin 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 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_SCOPEorRATE_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 reads the same endpoints without a model in between.
Next steps
- Alert on disconnected senders with a reconnect link, the scripted version, with state and auto-pause.
- Build a campaign from a brief with a Claude Code skill, the interactive side of the same connection.
- Connect your AI for API-key connections and their limits.