---
name: discord-dm
description: "Send OR read Discord messages to/from an org member DM or team channel via the ClaudeTools bot - copy-paste-friendly delivery of links/commands the terminal would mangle, and READ replies (coord/repo sync does not carry Discord answers); address people by name (mike/howard/rob/winter). Triggers: DM <person> in discord, send that link to my discord, ping <person>, did <person> reply/answer, read discord replies, check discord DMs."
---

# discord-dm — direct Discord messaging to the org

Thin wrapper over the Discord bot API. The engine is
`.claude/scripts/discord-dm.sh`; this doc is the usage contract.

## When to use

- **Wrapped command lines** — Mike's standing rule: any wrapped/long single-line
  output (M365 consent links, long CLI one-liners, URLs with query strings) gets
  **DM'd to him in Discord** so it's cleanly copy-pasteable, not mangled by terminal
  wrapping. Default target for that is `mike`.
- Pinging a specific teammate with something actionable.
- Posting to a team channel when you want to choose the channel explicitly
  (`#bot-alerts` / `#dev-alerts`).

For routine one-line `[SYNCRO]` / `[RMM]` status alerts, keep using
`post-bot-alert.sh` (it auto-routes by prefix). This skill is for **person-targeted
DMs** and deliberate channel posts.

## Usage

```bash
bash .claude/scripts/discord-dm.sh <recipient> "message text"
echo "message text" | bash .claude/scripts/discord-dm.sh <recipient>
bash .claude/scripts/discord-dm.sh read <recipient> [limit]   # READ recent messages — SEE replies (default 15)
bash .claude/scripts/discord-dm.sh list          # print known users + channels
```

## Reading replies (`read`)

Sends are one-way, but people **answer** — always `read` before assuming silence. The bot
participates in every DM channel it opened, so `GET /channels/<id>/messages` returns full
content (the Message Content privileged intent only gates *gateway* events, not this REST
fetch). Works for users and channels:

```bash
bash .claude/scripts/discord-dm.sh read mike 10      # last 10 in the mike DM
bash .claude/scripts/discord-dm.sh read #dev-alerts  # last 15 in #dev-alerts
```

Output is oldest-first; lines from **them** are marked `>>>`, lines from us are indented.
After DMing someone a question, `read` that user to check for their answer — coord/repo sync
does NOT carry Discord replies, so this is the only way to see them.

`<recipient>`:

| Form | Effect |
|---|---|
| `mike` `howard` `rob` `winter` | opens/uses that user's DM channel |
| `#bot-alerts` / `bot` | posts to #bot-alerts (whole team) |
| `#dev-alerts` / `dev` | posts to #dev-alerts (Howard + Mike, private) |
| `chan:<id>` | posts to an arbitrary channel by ID |
| `<17-19 digit id>` | treated as a **user** DM |

## Prepopulated directory (from `.claude/users.json` + `post-bot-alert.sh`)

| Name | Discord user ID | | Channel | ID |
|---|---|---|---|---|
| mike | 264814939619721216 | | #bot-alerts | 624710699771232265 |
| howard | 624667664501178379 | | #dev-alerts | 1509998508198068484 |
| rob | 261978810713505792 | | | |
| winter | 624666486362996755 | | | |

Keep this table and the `USERS`/`CHANNELS` maps in the script in sync with
`.claude/users.json` when people are added/removed.

## Implementation notes / gotchas

- **Bot token** resolves from the SOPS vault
  (`projects/discord-bot/bot-token.sops.yaml` field `credentials.bot_token`),
  falling back to `projects/discord-bot/.env` `DISCORD_TOKEN`. Works from any
  machine, not just BEAST.
- **DMs** require opening the user's DM channel first
  (`POST /users/@me/channels {recipient_id}`) then posting to that channel id.
- **Multiline payloads:** always build JSON with `jq -nc --arg` and feed curl via
  `printf '%s' "$payload" | curl ... --data-binary @-`. Passing the JSON directly
  with `-d "$(...)"` mangled multiline content and returned Discord error
  `50109 invalid JSON body` (hit 2026-06-15 sending the CryoWeave consent link).
- **Not soft-fail:** unlike `post-bot-alert.sh`, a send failure exits non-zero —
  a requested DM is load-bearing, so surface the error.
- The bot must share a server with the user and the user must allow DMs from
  server members, or the DM-channel open / send can 403.
