Skip to content

Kody is live

Watch the launch video — what Kody is, and why it exists.

← Public packages

@noah/hey-email

HEY.com email integration with Kody: search threads, read messages/attachments, create drafts, and send with explicit confirmation.

README.md

174 lines · 5.6 KB · Markdown

@noah/hey-email

Intent

Give Kody packages a thin, secret-backed way to search, read, and draft the signed-in user's HEY mail without running the local hey CLI inside Workers. Powered by the heyToken user secret against https://app.hey.com.

Success means another package can search threads, read bodies, download PDF receipts, and save a reply draft that the user opens in HEY. Nothing in this package sends mail unless a send function is called with { confirm: true }.

Prerequisites

  • A HEY account. HEY is a paid product from 37signals with a free trial — sign up at hey.com and see hey.com/pricing. A @hey.com personal address and a HEY for Domains address both work.
  • The hey CLI, used once, locally, to mint the token. Kody never runs it.
  • A Kody account, to store that token as the heyToken secret.

Setup

1. Install the HEY CLI

macOS / Linux / WSL2:

curl -fsSL https://hey.com/install-cli | bash

Windows (PowerShell):

irm https://hey.com/install-cli.ps1 | iex

Homebrew, mise, Scoop, Nix, deb/rpm/apk and go install also work — see the install guide. Confirm it is on your PATH:

hey version
2. Sign in
hey auth login

This opens a browser for HEY's own OAuth server (PKCE). Credentials land in your system keyring. Verify:

hey auth status

You want "authenticated": true and "auth_type": "oauth".

Do not sign in with hey auth login --cookie. That stores a browser session cookie rather than a bearer token, and hey auth token will refuse to print it, so there is nothing to give this package.

3. Print the token
hey auth token

That is a bearer token for your mailbox. Treat it like a password: do not commit it, paste it into a chat, or echo it into shell history you sync.

4. Save it to Kody as heyToken

Add a user secret at kody.codes/account/secrets/new:

FieldValue
NameheyToken
Valuethe output of hey auth token
Allowed hostapp.hey.com

Pipe it straight from the CLI to your clipboard so it never hits the screen:

hey auth token | pbcopy        # macOS
hey auth token | wl-copy       # Linux (Wayland)

Approve the host if Kody prompts you on the first fetch, and approve the package's access to the secret if asked.

5. Verify
import identity from 'kody:@noah/hey-email'
await identity()

Done when that returns your HEY user id and name. A HEY 401 means the token is missing, stale, or was saved from a --cookie login.

Keeping the token working

The CLI refreshes its own token automatically, but the Kody secret is a static copy and does not refresh with it. When it lapses, calls start failing with HEY 401. Fix it by re-running step 3 and updating the same secret:

hey auth refresh && hey auth token

hey auth status reports expires_at for the local credential, which is a good indication of when the copy in Kody will need refreshing.

Security

The token is read from the secret per request and is only ever sent to app.hey.com. resolveHeyUrl rejects every other origin, so attachment links parsed out of untrusted email HTML cannot redirect a credentialed fetch off-site.

Exports

  • kody:@noah/hey-email — identity smoke test
  • kody:@noah/hey-email/search — advanced search
  • kody:@noah/hey-email/boxes — list boxes
  • kody:@noah/hey-email/topic — get topic JSON
  • kody:@noah/hey-email/entries — list topic entries
  • kody:@noah/hey-email/message — full message JSON (/messages/{id}.json)
  • kody:@noah/hey-email/attachments — list/download PDF attachments from message HTML
  • kody:@noah/hey-email/drafts — list / create / read drafts; optional send with confirm
  • kody:@noah/hey-email/reply — alias for createReplyDraft
  • kody:@noah/hey-email/sendsendMessage / sendReply / sendDraft (confirm required)

Draft vs send

HEY's POST /entries/{id}/replies.json sends when to is present and saves a draft when to is omitted. createReplyDraft never sends to.

Every send function is a dry run unless you pass { confirm: true }, so a mistaken call reports what it would have sent instead of mailing anyone.

Draft edit URL: https://app.hey.com/messages/{id}/edit Topic URL: https://app.hey.com/topics/{topicId}

Troubleshooting

SymptomCauseFix
HEY 401Secret is stale, missing, or from a --cookie loginhey auth refresh && hey auth token, update heyToken
hey auth token refuses to printSigned in with --cookiehey auth logout, then hey auth login
Refusing to send HEY credentials to …A non-HEY URL was passed to a fetch helperExpected: only app.hey.com is allowed
Secret resolves but the host is blockedapp.hey.com not approved for the secretApprove it at /account/secrets
Attachments come back emptyRead the entry instead of the messageUse getMessage; entries omit attachment markup

Not a replacement for the CLI

This package is for agents calling HEY inside Kody. For your own terminal, use the CLI directly — it also ships an agent skill and an MCP server (hey mcp). See docs/agents.md.

License

MIT. See LICENSE.

Smoke test

import identity from 'kody:@noah/hey-email'
import { listDrafts } from 'kody:@noah/hey-email/drafts'
await identity()
await listDrafts()