# @kody/notify ## Intent Give every Kody account a single fanout export that notifies the *signed-in user* across the destinations they connected: Kody email (`email_send` to their account inbox), Slack (via `@kody/slack` user-token helpers), Discord (bot token + their channel), and Telegram (bot token + their chat). Channel config lives in this package's `packageStorage()`. Success means a forked copy can dry-run, then send, without anyone hard-coding Kent or official Kody destinations. Live `@kody/notify` storage is the platform package bucket, not the caller's. Fork first (or computed `import(specifier)` of the user's copy) before saving channels. ## What it does One export fans out a short message to every enabled destination. Email is on by default (no secret). Slack, Discord, and Telegram stay off until the fork owner enables them **and** stores **their** channel or chat id. | Field | Purpose | | --- | --- | | `text` / `subject` / `title` / `html` | Message. `html` is email-only. | | `dryRun` | Preview without sending **and** without writing `configure`. | | `channels` | Optional subset: `email`, `slack`, `discord`, `telegram`. | | `configure` | Merge destination config into `packageStorage`. | ## Prerequisites / setup Do not ask the user to paste tokens into chat. Hand them the prefilled URL, wait until they confirm it is saved, then store *their* destination ids with `configure` + `dryRun: true` first. Never copy channel, guild, or chat ids from Kent, `@kody`, or another account. ### 1. Fork so storage is theirs Install/fork this listing into the user's account. Then invoke **their** `notify` (resolve the installed export specifier). Configuring the live `@kody/notify` package writes the platform bucket. ### 2. Email (no secret) Kody `email_send` already delivers to the signed-in account email. Tell the user: "Email is already connected — notifies go to your Kody inbox. Say if you want it off." ### 3. Slack (OAuth user token via `@kody/slack`) This package depends on `@kody/slack`. Need a **user-token** connection (not a bot token) with scope **`chat:write`**. Create a Slack app, register redirect URI exactly `https://kody.codes/connect/oauth`, add **User Token Scopes** `chat:write` (plus the read scopes `@kody/slack` uses), then open: ```text https://kody.codes/connect/oauth?provider=slack&authorizeUrl=https%3A%2F%2Fslack.com%2Foauth%2Fv2_user%2Fauthorize&tokenUrl=https%3A%2F%2Fslack.com%2Fapi%2Foauth.v2.user.access&apiBaseUrl=https%3A%2F%2Fslack.com%2Fapi&flow=confidential&tokenExchangeStyle=form&scopeSeparator=%2C&allowedHosts=slack.com&dashboardUrl=https%3A%2F%2Fapi.slack.com%2Fapps&scopes=chat%3Awrite%2Cchannels%3Ahistory%2Cchannels%3Aread%2Cgroups%3Ahistory%2Cgroups%3Aread%2Cim%3Ahistory%2Cim%3Aread%2Cmpim%3Ahistory%2Cmpim%3Aread%2Cusers%3Aread ``` Paste Client ID / Client Secret into the Kody form, not into chat. After connect, ask **them** which conversation to notify and save that id via `configure` (see [`AGENTS.md`](./AGENTS.md)). Second workspace: change `provider=` to `slack-work` and pass `integration: 'slack-work'`. Reconnect with `https://kody.codes/connect/oauth?provider=`. ### 4. Discord (bot token) User OAuth cannot post in servers. Posting requires a **bot**. 1. [discord.com/developers/applications](https://discord.com/developers/applications) → New Application → **Bot** tab → copy the bot token. 2. Invite the bot into **their** server with View Channel, Send Messages, Read Message History (permissions `68608`): ```text https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot&permissions=68608 ``` 3. Save the token (never paste it into chat). Approve host `discord.com`: ```text https://kody.codes/account/secrets/new?name=discordBotToken&description=Discord%20bot%20token&allowedHosts=discord.com&scope=user ``` 4. Store **their** destination channel snowflake via `configure` (see [`AGENTS.md`](./AGENTS.md)). Second bot: save another secret (`name=discordBotTokenWork`) with the same `allowedHosts=discord.com`, then pass that `secretName`. ### 5. Telegram (bot token) Telegram Login OAuth (`oauth.telegram.org`) is identity only and cannot send messages. Sending uses the **Bot API**. 1. [@BotFather](https://t.me/BotFather) → `/newbot` → copy the token. 2. Message the bot (or add it to the target group). 3. Save the token. Approve host `api.telegram.org`: ```text https://kody.codes/account/secrets/new?name=telegramBotToken&description=Telegram%20bot%20token%20from%20BotFather&allowedHosts=api.telegram.org&scope=user ``` 4. Store **their** numeric `chatId` via `configure` (see [`AGENTS.md`](./AGENTS.md)). ### Auth model | Destination | Kind | Default identity | Hosts | | --- | --- | --- | --- | | Email | Kody notify-self (`email_send`) | Account email | none | | Slack | OAuth user token via `@kody/slack` | Integration `slack` | `slack.com` | | Discord | Bot token secret | Secret `discordBotToken` | `discord.com` | | Telegram | Bot token secret | Secret `telegramBotToken` | `api.telegram.org` | Multi-account: set `configure.slack.integration`, `configure.discord.secretName`, or `configure.telegram.secretName`. Do not hard-code personal aliases. Never paste tokens into chat. Agents: see [`AGENTS.md`](./AGENTS.md) for fork invoke, configure dry-runs, smoke checks, and troubleshooting. ## Done when - User forked (or invokes their installed copy) before any `configure` write - Email path dry-runs without sending - Slack/Discord/Telegram only enabled after **their** channel/chat ids are saved - Live send requires explicit user confirmation of text and destinations ## Out of scope This package does not run a Discord gateway, manage the official Kody Discord server, or pin a personal “send me a message” channel. Use `@kody/slack` (and `@kody/discord` / `@kody/telegram` when listed) for richer provider APIs.