name: x-auto-dm description: "Monitor X/Twitter post replies for keyword triggers and auto-DM followers with gated content. Use when setting up lead-gen campaigns, gated PDF/repo distribution, or automated DM funnels on X. Handles reply polling, follower verification, DM delivery, follow-nudge replies, unsuccessful_first_dm retry list, ignore list, and per-action cost tracking."
X Auto-DM Campaign Bot (v2)
Overview
Monitors replies on a specific X post for keyword triggers (skill, skills, agent skills), verifies the commenter follows you, and sends a DM with your gated content. Non-followers get a public reply nudge and land on unsuccessful_first_dm. If DM fails a 2nd time, the user is added to ignore_list permanently for that campaign.
Folder Structure
x-auto-dm/
├── SKILL.md
├── scripts/
│ ├── x_auto_dm.py # Main bot (classes + CLI)
│ └── x_auto_dm_config.json # Campaign config (edit this, not Python)
│ └── data/ # Auto-created at runtime
│ ├── processed_users.json
│ ├── unsuccessful_first_dm.json
│ ├── ignored_users.json
│ ├── processed_comments.json
│ ├── cursors.json
│ └── cost_stats.json
│ └── logs/
│ └── x_auto_dm.log
└── references/
└── x-api-costs.md
Prerequisites
.env Variables
X_CONSUMER_KEY=your_consumer_key
X_CONSUMER_KEY_SECRET=your_consumer_secret
X_ACCESS_TOKEN=your_access_token
X_ACCESS_TOKEN_SECRET=your_access_token_secret
Packages
pip install tweepy python-dotenv
X App Permissions
Read + Write + Direct Messages in the X Developer Portal.
Configuration
Edit scripts/x_auto_dm_config.json — never touch the Python file:
{
"post_id": "1234567890123456789",
"my_user_id": "9876543210",
"campaign_name": "agent_skills_guide",
"keywords": ["agent skills", "skills", "skill"],
"dm_message": "Hey! Here's the guide...",
"follow_nudge": "@{username} Follow me to get the DM...",
"polling_interval_seconds": 600,
"pending_retry_delay_minutes": 60,
"max_results_per_poll": 1000
}
To run a second campaign, copy the config, change campaign_name and post_id, run a second instance. All data files are campaign-namespaced.
CLI Commands
python x_auto_dm.py run # continuous monitoring
python x_auto_dm.py run --once # single poll cycle (testing)
python x_auto_dm.py verify # test X API auth
python x_auto_dm.py status # show campaign stats
python x_auto_dm.py clear # reset campaign data
Campaign Flow
Poll replies on post_id (every polling_interval_seconds)
│
For each reply containing keyword:
│
├── Already in processed_users? ───────────→ SKIP
├── Already in ignore_list? ───────────────→ SKIP
│
├── CALL 1: Search replies ($0.005)
├── CALL 2: Check if user follows me ($0.005)
│ │
│ ├── FOLLOWS → CALL 3: Send DM ($0.015)
│ │ ├── Success → processed_users ✓
│ │ └── Fail → unsuccessful_first_dm
│ │
│ └── DOESN'T FOLLOW
│ ├── On unsuccessful_first_dm?
│ │ ├── YES → CALL 3: Try DM ($0.015)
│ │ │ ├── Success → processed_users ✓
│ │ │ └── Fail → ignore_list ✗ (DONE)
│ │ │
│ │ └── NO → Reply nudge ($0.005)
│ │ Add to unsuccessful_first_dm
│
Separate timer (pending_retry_delay_minutes):
│
└── Re-check unsuccessful_first_dm users
├── Now following? → Send DM
└── Still not? → Wait for next retry cycle
Cost Per User
| Scenario | Calls | Cost |
|---|---|---|
| Follower + DM works | search + follow + DM | $0.025 |
| Non-follower → nudge | search + follow + reply | $0.015 |
| Retry → DM works | search + follow + DM | $0.025 |
| Retry → DM fails (ignored) | search + follow + DM | $0.025 |
| Already processed/ignored | search only | $0.005 |
Architecture Notes
- XClient class wraps all API calls with automatic cost tracking
- CampaignMonitor class handles campaign logic, signal handling, retry timer
- since_id pagination — only fetches new replies each cycle (cheaper)
- Signal handling — catches SIGINT/SIGTERM, 1-second sleep granularity for fast exit
- Separate retry timer — pending users checked on their own schedule to avoid wasting follower-check calls
- All state is campaign-namespaced — multiple campaigns share the same data files safely