| name | reddit-dm |
| description | Send a Reddit direct message from an agent, through the user's own logged-in Chrome, and verify it actually landed in the sent folder instead of trusting the HTTP 200. Use whenever someone wants to DM or private-message a Reddit user, message an OP about their post, follow up with someone who commented, reach out to a moderator, or automate a Reddit DM from a script or agent — including phrasings like "DM this person on Reddit", "send them a private message", "message the OP", "reach out to u/someone". Also use when a Reddit DM is failing: a 200 that sent nothing, a RATELIMIT error, a recipient who has DMs disabled, an expired session, or a message the recipient says never arrived. This skill contains the diagnosis path for all of those. One recipient per send, from the user's own account. Not for bulk outreach, not for scraping recipient lists, and not for posting or commenting — use reddit-post for those.
|
Sending a Reddit DM from an agent
A DM looks like a smaller version of a post. It isn't. Posts fail loudly and publicly — you can
open the permalink and see nothing there. DMs fail quietly: Reddit returns 200 OK with an
errors array in the body, the agent reports success, and the message simply never exists. Most of
this skill is about closing that gap.
The architecture, and why
rdtx attaches over CDP to a Chrome the human launched, and issues the write as a same-origin
fetch from inside the logged-in page. This is inherited wholesale from reddit-post; the
reasoning is identical and the evidence is in references/browser-session.md.
The automation flag is set at launch, not by attaching. Puppeteer and Playwright pass
--enable-automation, which sets navigator.webdriver = true. Start Chrome with only
--remote-debugging-port and attach afterwards, and it stays false.
"Prove your humanity" is transient. It's a JS proof-of-work that self-resolves in seconds.
rdtx polls through it. Treating it as a block is the most common way to abandon a working setup.
Let the page build the request. The write reuses Reddit's own cookies and CSRF material
verbatim, so there is no auth protocol to reimplement.
Setup, once
node scripts/rdtx.mjs launch
node scripts/rdtx.mjs login
node scripts/rdtx.mjs doctor
rdtx never handles the password.
Sending
node scripts/rdtx.mjs dm someuser \
--subject "Quick question about your CI post" \
--text "Hey — saw your thread in r/devops about..."
node scripts/rdtx.mjs dm someuser --subject "..." --text "..." --yes
subject is required by Reddit and capped at 100 characters — rdtx rejects longer ones before
making any request rather than letting Reddit truncate silently.
One recipient per invocation. There is deliberately no --from-file and no list input. This is
a design property, not a missing feature: the thing that makes a DM worth sending is that it was
written for the person receiving it, and a CLI that accepts a list of strangers is a spam tool that
will get the account banned. If you find yourself wanting to loop this over a list, that is the
signal to stop and reconsider the approach, not to write the loop.
Verifying, which is the part that matters
A 200 is not delivery. Reddit accepts the request, then reports refusals inside the body. rdtx
therefore does two things after a send:
- Parses
json.errors and maps the three that imply different next steps (below).
- Re-reads
/message/sent.json and looks for the message by recipient + subject.
The result carries verified_in_sent: true|false. Treat false as unknown, not as failure —
Reddit's sent folder can lag a few seconds. Re-check with:
node scripts/rdtx.mjs sent --limit 25
If it still isn't there after a minute, it didn't send, whatever the original response said.
When it fails
| What you see | What it means | What to do |
|---|
error: rate_limited (RATELIMIT) | Reddit's DM limit for this account. New and low-karma accounts hit this within a handful of messages. | Wait it out. The window is on the order of minutes to hours and scales with account age and karma. Retrying immediately extends it. |
error: recipient_blocks_dms (NOT_WHITELISTED_BY_USER_MESSAGE) | The recipient only accepts DMs from people they follow, or has them off. | Nothing to fix. Reply to their comment publicly instead — that is the channel they left open. |
error: no_such_user (USER_DOESNT_EXIST) | Username is wrong, deleted, or suspended. | Check the spelling. rdtx already strips u/ and @, so the failure is a real one. |
error: not_logged_in | The session expired. | rdtx login, then retry. |
subject_too_long | Over 100 chars. | Shorten it; nothing was sent. |
sent: true, verified_in_sent: false | Reddit accepted it; the sent folder hasn't caught up. | rdtx sent in a minute. Do not re-send — you will deliver it twice. |
Do not re-send on an ambiguous result. A duplicate DM is worse than a missing one: it reads as
automated, and it is the fastest way to get reported. Verify first.
Rate and etiquette, briefly
Reddit's DM limits are much tighter than its posting limits and they are not published. Empirically
a young account gets a few messages before RATELIMIT; an aged one with karma gets more. This is
not a knob to tune around — it is the platform telling you the volume it will tolerate.
The account sending the DM should look like a participant, not a broadcaster. If it has no comment
history in the space it is messaging about, the message reads as cold outreach whatever it says.
reddit-voice and reddit-scout exist for that groundwork.
Verify the installed runtime without Reddit
node scripts/verify-synthetic.mjs
Starts the production CLI without --yes, checks the audit entry, and arms guards that fail if the
CLI tries to reach a network or agent-browser. A pass proves the local dry-run boundary only; it
does not authorize or prove a real send.
Audit
Every send and every dry run appends to ~/.rdtx/audit.jsonl with executed: true|false. That file
is the record of what this account actually did — read it before assuming a message went out.
node scripts/rdtx.mjs audit