Campaign Orchestrator Skill
Multi-channel follow-up campaign orchestrator for ShapeScale sales. Executes scheduled SMS + Email sequences with CRM integration and auto-termination on replies.
Overview
A Campaign is a defined sequence of steps (SMS/Email) that executes over time. When a lead replies to any message, the campaign automatically terminates.
Key Features
- Multi-channel: SMS (Dialpad) + Email (Gmail)
- Scheduled: Cron-based execution with configurable delays
- Personalized: Templates filled from Attio CRM data
- Auto-terminating: Replies stop all future scheduled steps
- Logged: All activities recorded in Attio
Setup
Environment variables required:
DIALPAD_API_KEY=your_dialpad_api_key
ATTIO_API_KEY=your_attio_api_key
GOG_KEYRING_PASSWORD=your_google_password # For Gmail access
Also ensure:
- Dialpad webhook is configured to hit this server
- Attio has company/contact records for leads
- Gmail API access enabled for sales email
Usage
Start a Campaign
# Start primary follow-up campaign for a lead
python3 campaign.py start "primary" --lead "Apex Fitness"
# Start with custom delay override (hours)
python3 campaign.py start "primary" --lead "Apex Fitness" --delay 2
# Start with Attio deal/company ID
python3 campaign.py start "post-demo" --lead "Apex Fitness" --attio-id "deal-uuid"
Pre-Campaign Checklist (MANDATORY)
Before starting ANY campaign, verify:
Customer Status Check
- Search memory/CRM for "already a customer" or "purchased" flags
- Check exclusion list in campaigns.json
- Verify email domain not in customer database
Email Formatting Check (for email steps)
- Preview template renders as proper paragraphs
- 2-4 sentences per paragraph, blank line between
- No single-sentence orphan paragraphs
- No hard line breaks mid-paragraph
Tone Check
- No apologetic language ("no worries", "sorry to bother")
- No easy outs ("if not relevant, no problem")
- Professional, not needy
NEVER campaign to existing customers unless explicitly requested for upsell.
Check Campaign Status
# Status for specific lead
python3 campaign.py status "Apex Fitness"
# All active campaigns
python3 campaign.py list
Stop a Campaign
# Manual termination (lead replied, not interested, etc.)
python3 campaign.py stop "Apex Fitness" --reason "replied_interested"
Remove a Lead
# Remove lead from campaigns (opted out, not interested)
python3 campaign.py remove "Apex Fitness"
Check for Responses
# Check if lead has responded to any prior messages
python3 campaign.py check "Apex Fitness"
# Shows response status for each completed step
# Warns if responses detected (safe to proceed or terminate)
View Pending Steps
# Show all pending campaign steps sorted by time
python3 campaign.py pending
# Useful for seeing what's due soon across all campaigns
Template Management
# List available templates
python3 campaign.py templates
# Preview a template
python3 campaign.py preview "primary"
Campaign Templates
| Template | Timing | Channel | Purpose |
|---|---|---|---|
primary |
+4 hours | SMS | Recap demo, share recording |
secondary |
+1 day | Pricing, detailed ROI | |
tertiary |
+4 days | SMS | Quick check-in |
quaternary |
+7 days | Final follow-up, case study | |
post-demo |
+0 hours | SMS | Immediate thank you |
Template Variables
Templates support variable substitution:
{name} - Lead first name
{company} - Company name
{deal_value} - Deal value from Attio
{owner} - Sales owner name
{demo_notes} - Notes from demo conversation
{checkout_link} - Personalized checkout URL
Architecture
campaign-orchestrator/
โโโ SKILL.md # This file
โโโ campaign.py # Main CLI (start, stop, status, list)
โโโ webhook_handler.py # Processes reply โ termination
โโโ primary.md # SMS follow-up template
โโโ secondary.md # Email template
โโโ post-demo.md # Immediate follow-up template
โโโ state/
โโโ campaigns.json # Campaign state persistence
State Management
Campaign state is stored in <workspace>/state/campaigns.json:
{
"campaigns": {
"Apex Fitness": {
"template": "primary",
"attio_id": "deal-uuid",
"started": "2026-01-27T13:00:00Z",
"steps_completed": ["sms_primary"],
"next_step": "email_secondary",
"next_scheduled": "2026-01-28T13:00:00Z",
"status": "active"
}
},
"templates": {
"primary": {...},
"secondary": {...}
}
}
Cron Integration
Campaign steps are executed via Clawdbot's cron system:
- Executor job: Runs every 5 minutes to check for due steps
- Per-campaign jobs: Created for each scheduled step
The scheduler script creates and manages these jobs automatically.
Webhook Handling
When Dialpad receives a reply to a campaign message:
- Dialpad sends webhook to server
webhook_handler.pyparses the reply- Looks up which campaign the original message belonged to
- Marks campaign as terminated
- Logs the reply to Attio
Integration Points
Dialpad SMS
python3 /home/art/niemand/skills/dialpad/send_sms.py --to "+14155551234" --message "..."
Gmail (via gog)
gog-shapescale --account [email protected] send-email --to "[email protected]" --subject "..." --body "..."
Attio CRM
attio note companies "company-uuid" "Campaign message sent: {message}"
Examples
Full Campaign Workflow
# 1. After demo, start campaign
/campaign start "post-demo" --lead "Dr. Smith's Clinic"
# 2. Check status next day
/campaign status "Dr. Smith's Clinic"
# Output: Step 1 sent, Step 2 scheduled for tomorrow
# 3. Lead replies "interested"
# Webhook automatically terminates campaign
# Logs reply to Attio
# 4. Manual follow-up if needed
/campaign start "secondary" --lead "Dr. Smith's Clinic" --delay 0
Monitoring Active Campaigns
# List all active
/campaign list
# Output:
# Active Campaigns:
# - Apex Fitness (primary) - Step 2/4, next: email
# - Dr. Smith's Clinic (post-demo) - Complete
# - Wellness Center (tertiary) - Step 1/3, next: sms
Troubleshooting
Campaign not sending:
- Check
cronis running:crontab -l - Check logs:
journalctl -u moltbotor campaign logs - Verify API keys:
echo $DIALPAD_API_KEY
Webhook not terminating:
- Verify Dialpad webhook URL is configured
- Check webhook handler is running
- Check
campaigns.jsonfor matching lead
Template variables not filling:
- Verify lead exists in Attio with required fields
- Check template syntax:
{variable}not{ variable }
License
Part of shapescale-moltbot-skills. See parent repository.