Agent skill
sdr-daily
Run one day of the SDR loop on a RouterGrowth cold email campaign: read the new replies and bounces (events, email.messages), triage them, keep the suppression list current, send the follow-ups that are due and drip the next first-touch emails from an approved queue (email.send) inside a daily cap, then write the morning briefing.
Filed under Outbound email.
From RouterGrowth/skills · 12 skill entries · 2 · pushed 2026-10-03
What it does when it runs
Run one day of the SDR loop on a RouterGrowth cold email campaign: read the new replies and bounces (events, email.messages), triage them, keep the suppression list current, send the follow-ups that are due and drip the next first-touch emails from an approved queue (email.send) inside a daily cap, then write the morning briefing. Nothing new goes out without approval; the drip and the follow-ups come from copy approved at the cold-email-pipeline gates. Use when the user says "run the SDR", "daily briefing", "who replied", "send today's batch", "check the campaign", or wants to advance an outreach campaign by one day.
Automated analysis of the skill and the 0 files bundled beside it. A skill’s own description is written to be selected by an agent, so it describes the job and not the dependencies.
- Keys and connectors you must supply
- None found.
- Hosts it reaches
- www.routergrowth.com
- Tool permissions it declares
- No
allowed-toolsin the frontmatter. It does act, so it runs under whatever permissions your session already grants. - Actions present in the files
- shell
Install it
View source on GitHub ↗git clone --depth 1 --filter=blob:none --sparse https://github.com/RouterGrowth/skills.git /tmp/skills git -C /tmp/skills sparse-checkout set "skills/sdr-daily" mkdir -p ~/.claude/skills/sdr-daily cp -R "/tmp/skills/skills/sdr-daily/." ~/.claude/skills/sdr-daily/
Picked up without a restart. A project skill of the same name is shadowed by your personal one. For one repository only, swap ~/.claude/skills for .claude/skills. Claude Code docs ↗
Or take the whole library
This repo ships a .claude-plugin manifest, so Claude Code can install all 12 skills at once. Plugin skills are invoked as /<plugin>:<skill>, so they never collide with your own.
/plugin marketplace add RouterGrowth/skills /plugin
The folder is the same in every client that implements the format — 46 of them — so if yours is not above, only the destination changes.
The skill
Source on GitHub ↗Reproduced in full from RouterGrowth/skills/blob/c9161f688c06967dbdeca865201100cf67a0741c/skills/sdr-daily/SKILL.md, which is licensed MIT (repository). 1,420 words, 11 headings.
SDR daily
One day of a Sales Development loop. You do the mechanical work (read, classify, send what was already approved), surface the few human decisions, and hand back a short briefing. You run from the campaign folder the cold-email-pipeline skill produced: out/campaign.csv, out/sent-log.json (one object per send: run_id, message_id, to, subject, stage, sent_at), out/replied.json (one object per suppressed address: email, reason, date, note). The briefing goes to out/briefing-<date>.md.
Before you start
- Load the core
routergrowthskill (https://www.routergrowth.com/SKILL.md) if it is not loaded. Confirm access with the freebalancetool orroutergrowth balance. - Establish, once per campaign and reuse after:
CAMPAIGN_CSV(the approved rows with astagecolumn: J+0, J+4, J+10),INBOX_ID(theFromaddress indirectives/email-copy.md, confirmed against the freeemail.inboxes),DAILY_CAP(default 30, all stages together),LAST_RUN(the newestsent_atin the sent log; on the first run there is none, so read the inbox withoutafter). - Check that Gate 2 of the pipeline happened: the sent log carries the test send to the user's own address. If it does not, the drip does not start; send the test, ask the user to confirm it landed, and stop there for today.
- If
out/replied.jsondoes not exist, create it as[].
The one rule
Nothing new goes out without approval. The drip and the follow-ups are not new: their copy and targeting were approved at Gate 1 of the pipeline, and the domain passed the test send at Gate 2. So, without asking, you may: drip first-touch from the approved queue inside the cap, send due follow-ups, read and classify replies, draft responses, update the suppression file, run diagnostics. You may not: send a reply to a prospect (draft it, the user sends), start a queue whose copy was not approved, exceed the cap, or email anyone in replied.json.
Run order
1. Triage replies
Start with the event stream: one free call returns every reply, bounce and spam complaint since the last run, across all the workspace's inboxes. Use the CLI (0.6.0 or later; npm install -g routergrowth@latest if events is an unknown command):
routergrowth events --types email.received,email.bounced,email.complained --cursor-file out/events-cursor.txt -o out/events.json
--cursor-file reads the id of the last event the previous run saw and writes the new one back, so each run returns only what is new. With no cursor file yet, add --since <LAST_RUN>. When it reports "more waiting", read out/events.json, then run it again for the next page. Over MCP, the events tool takes the same types and an after cursor (the previous answer's next_after, which you then keep in out/events-cursor.txt yourself). Keep the events whose data.inbox is INBOX_ID:
email.receivedcarriesfrom,subject,preview, the fulltext,message_idandthread_id: classify from it with the table below, with no second call to fetch the message.email.bouncedcarriesrecipients,typeandsub_type.type: Permanentis a hard bounce: put every recipient inreplied.jsonwithreason: bounce. ATransientbounce is not suppressed.email.complainedis a spam complaint: suppress the recipients and count it in the hygiene step.
Read the inbox itself, as below, when there is no cursor file yet (the first run that uses events: it confirms the two agree), when the events call fails, or when LAST_RUN is more than 30 days old (events are kept 30 days). On the other days the events are the read, and the inbox call is skipped.
routergrowth run -c email.messages -i '{"inbox_id":"<id>","labels":["received"],"after":"<LAST_RUN>","limit":100}' --max-cost 0.01 --wait 30 -o out/inbox.json
The inbox holds the campaign's own sent mail too (label sent); labels: ["received"] keeps the read to what came in. Each row carries message_id, thread_id, from (a display string, Name <address>: parse the address), to, subject, preview (the first 200 characters, cut mid-word), labels, received_at, direction and bounced. A read costs $0.002. Match a reply to a campaign email on thread_id (the send and its replies share one) or on the sender's address against the sent log. When the preview is not enough to classify, fetch that one message in full with email.messages and message_id (returns text), and only mark the thread ambiguous when the full text still does not decide it. Classify:
| Class | Action |
|---|---|
| Interested | Draft a reply for the user to approve. Add to replied.json (stops follow-ups). |
| Not interested | Add to replied.json. No response. |
| Wrong contact | Note the redirect, propose the named person. Add the original to replied.json. |
| Auto-reply or out of office | Do not suppress. Note the return date. |
| Unsubscribe request | Add to replied.json immediately. Never contact again. |
| Ambiguous | Show verbatim. Do not guess. |
Bounces are not notices in the inbox read: a hard bounce shows as the label bounced (and bounced: true) on the campaign's own sent row, so read the sent rows since LAST_RUN once with labels: ["sent"] and put every bounced to in replied.json with reason: bounce. Out-of-office replies arrive from [email protected] with the original subject after "Re:" and the auto-reply text in the preview: they are auto-replies, never bounces, do not suppress them.
2. Hygiene
Count bounces since LAST_RUN against sends since LAST_RUN (the whole campaign on the first run). Any spam complaint (an email.complained event), or bounces above 5% of those sends, is the kill switch: recommend pausing the drip until the list and copy are reviewed, and do not drip today.
3. Follow-ups due
From out/sent-log.json, a J+0 sent 4 or more days ago with no reply and no J+4 is due for J+4; a J+4 sent 6 or more days ago with no reply is due for J+10. Skip anyone in replied.json. Send the due rows, oldest first, threaded on the original:
routergrowth run -c email.send -i '{"inbox_id":"<id>","to":["[email protected]"],"subject":"Re: <original subject>","text":"<approved J+4 body>","in_reply_to":"<message_id of the J+0 from the sent log>","reply_to":"[email protected]"}' --max-cost 0.01 --wait 30
in_reply_to takes the message_id the J+0 send returned (that is why the sent log keeps it); unsubscribe_url is optional, the opt-out sentence in the body is the floor. Count them as F. Follow-ups are time-sensitive and take the first claim on the cap.
4. First-touch drip
Budget: DRIP = DAILY_CAP - F. If positive, send the next DRIP J+0 rows that are not in the sent log and not in replied.json. Before the send, the free routergrowth history --file today.txt on today's addresses confirms nobody was contacted from another campaign in this workspace.
Spread the sends across the day rather than in one burst: on a scheduled run, send the batch due for this hour; on a manual run, space the sends with a pause between them and say what was sent when. On a fresh domain, ramp the cap: 5 to 10 a day for days one to three, 15 through day seven, then 25 to 30; the domain's age is not in any run output, so count from the first sent row in the inbox or ask. Log every send with its run ID, message_id, to, subject, stage and timestamp.
When the queue is empty, say so and suggest running cold-email-pipeline for the next batch.
5. Briefing
SDR briefing, <date>, <campaign>
Interested (<n>): drafts below, your approval to send
- <company> (<name>): "<gist>"
Not interested (<n>) suppressed. Redirects (<n>). Unsubscribes (<n>).
Sent today: <F + DRIP> of <DAILY_CAP>
- Follow-ups <F>, first-touch <DRIP>, queue remaining <n> (about <days> days)
Hygiene: bounces <n>, complaints <n>, domain OK or AT RISK
Your decisions today
1. Approve or edit <n> reply drafts (below)
2. <replenish the queue, pause, anything else human>
Charged today: $<total> across <n> runs (the sum of each run's billing.charged)
Then the reply drafts, one per interested thread.
Rules
- One daily cap across drip and follow-ups. Never exceed it.
- Suppression is sacred: anyone who replied (other than an auto-reply) is never contacted again by this campaign.
- Reply responses are drafted, never auto-sent, even on a scheduled run.
- Honesty on signal: zero replies in week one is normal; a booking that predates the campaign is not a result.
max_coston every run,--wait 30on every send and read. Every send logged with its run ID andmessage_id.
Scheduling
Once the user trusts the drafts (a week or two of manual runs), this skill can run each weekday morning on a schedule. The scheduled run drips, follows up, triages and briefs on its own, and still only drafts responses.
Other skills for the same job
Different authors, same problem. Matched on the words in the skill name, across every library in the catalogue except this one.
- daily-lead-steward by zapier · 342
- daily-product-digest by shawnpang · 337
- ai-sdr by chadboyda · 79
- 03-social-selling-daily-routine by SimonTheSalesBooster · 39
- daily-icp-feed by Abhipaddy8 · 19
- daily-briefing by zime-skills · 8
- sdr-master-prompts by Frontal-so · 6
- sdr-outbound-rules by Frontal-so · 6
Need help setting it up?
This page tells you what sdr-daily does and what it needs. Cheetah builds the agent setup it runs inside: data, CRM, sequencing and the guardrails.
Book a call →The directory stays free. There is nothing gated behind this.