Skip to content
← Public packages

@danj/hey-email

HEY.com email for Kody: search threads, read messages, download PDF attachments, and write drafts. Sending is barred until you allow it.

Integrations

Version
2.0.0
License
Published
October 16, 2026
Pinned commit
f4d8fe2
Rating
No ratings yet
Forks
0
Adaptation effort
—

@danj/hey-email

Intent

Let your agents, and the Kody packages they build, search, read, and draft your HEY mail. The package calls HEY at app.hey.com, through the same API that HEY's CLI and SDK use. Kody never runs the hey CLI. You run it once on your computer, to sign in for Kody.

Sending is barred until you allow it. SENDING in src/config.js is the switch. While it is 'barred', no function sends mail, and your agent saves drafts for you to send. The section "The sending switch" explains how to allow sending.

The package is publish-locked on Kody. A change goes live only after you review it and click Promote this commit.

What you can ask your agent

Ask your agent for any of these:

  • "Find the invoice from Acme last week and read me its total from the PDF."
  • "Summarize my thread with Ada."
  • "Draft a reply to Ada that says the Tuesday slot works." The agent saves a reply draft and gives you its edit link.
  • "Email Bob that I'm running late." The agent saves a new draft addressed to Bob and gives you its edit link.
  • "Move this Logos newsletter thread to Paper Trail." The agent moves that one thread. Other mail from the same sender stays where it is.

You do these yourself:

  • Send each draft. Open its edit link in HEY, check the text and the recipients, and send.
  • Sign in to HEY, and paste your secrets on Kody's setup pages.
  • Review and promote each change to the package on Kody.
  • Decide whether sending is ever allowed.

What it does

Each ./name export imports as kody:@danj/hey-email/name.

ExportWhat it doesHEY calls
kody:@danj/hey-emailReturns your HEY { id, name }. Use it as a smoke test.GET /identity
./searchSearches your mail. Pass limit greater than 10 to fetch further pages.GET /advanced_search.json, then ?page=2 and so on
./paper-trailMoves one thread to the Paper Trail, by topic id or entry id.GET /boxes.json, then POST /topics/{id}/moves.json
./boxesLists your boxes, such as the Imbox and the Feed.GET /boxes.json
./topicReads one thread.GET /topics/{id}.json
./entriesLists the messages in a thread.GET /topics/{id}/entries.json
./messageReads one message with its HTML body.GET /messages/{id}.json
./attachmentsLists PDF attachments on a message, then downloads one file as base64. The default export is listPdfs: metadata only, no bytes.GET /messages/{id}.json to list. One GET for a chosen PDF.
./draftsLists, reads, and saves drafts with their recipients. HEY keeps each saved draft as a draft. It also exports the three send functions.GET /entries/drafts.json, GET /messages/{id}/edit.json, POST /messages.json, POST /entries/{id}/replies.json
./replySaves a reply draft on a thread.GET /entries/{id}/replies/new.json, POST /entries/{id}/replies.json
./sendsendMessage, sendReply, and sendDraft.None while sending is barred. While it is allowed, a confirmed send calls POST /messages.json, POST /entries/{id}/replies.json, or PUT /messages/{id}.json.

AGENTS.md lists each function's inputs, results, and errors.

The default export of ./attachments is listPdfs. It returns metadata only (index, filename, contentType, url, filesize) and has no bytes. There is no combined download of every file. Call downloadPdf for one PDF as base64.

Set it up

You do the steps that need your HEY sign-in, your secrets, or your review. Your agent does the rest. Your secret values never pass through the agent.

  1. You need a HEY account. HEY is a paid 37signals product with a free trial at hey.com. A @hey.com address and a HEY for Domains address both work.

  2. You install the HEY CLI on your computer, version 1.4.0 or later. Follow the hey-cli install guide.

  3. You sign in to HEY for Kody, in a new folder. To HEY, Kody is then a separate device, like a second computer. Your own CLI login stays as it is. Run these lines in one terminal window, and keep that window for steps 5 and 7.

    HEY_KODY=$(mktemp -d)
    XDG_CONFIG_HOME="$HEY_KODY" HEY_NO_KEYRING=1 hey auth login
    XDG_CONFIG_HOME="$HEY_KODY" HEY_NO_KEYRING=1 hey auth status

    hey auth login opens HEY's sign-in page in your browser. HEY_NO_KEYRING makes the CLI save this login in the folder, not in your keychain. In the status, the Auth: line must show oauth, and the Storage: line must show file. The Token: line shows the token cut short with ..., so do not copy it from there. Step 5 copies the full values. Do not add --cookie or --token to the login. Those logins store no refresh token, so this package cannot use them. HEY can email you about a sign-in from a new device.

  4. Your agent sends you three Kody setup links, one for each secret. Each link fills in the secret's name, its description, and the allowed host app.hey.com. The heyToken and heyRefreshToken links also ask you to let this package update them, so it can save new tokens after a refresh.

  5. You copy each value from the folder and paste it on its link. Each command copies one value to the clipboard. The first two need jq, which macOS 15 and later include.

    • heyToken: jq -j '."https://app.hey.com".access_token' "$HEY_KODY/hey-cli/credentials.json" | pbcopy
    • heyRefreshToken: jq -j '."https://app.hey.com".refresh_token' "$HEY_KODY/hey-cli/credentials.json" | pbcopy
    • heyInstallId: tr -d '\n' < "$HEY_KODY/hey-cli/install_id" | pbcopy

    After the last paste, clear the clipboard with pbcopy </dev/null. On Linux, replace pbcopy with wl-copy or xclip -selection clipboard, and clear the clipboard with wl-copy --clear. If you use a clipboard manager, delete the three entries from its history. Tell your agent when all three secrets are saved.

  6. Your agent runs identity(). It must return your HEY id and name.

  7. You delete the folder with rm -rf "${HEY_KODY:?}". Kody now holds the only copy of this login. Nothing else refreshes its tokens, so Kody's copy stays valid.

  8. Your agent locks the package on Kody, if it is not locked yet. From then on, each change waits on the package page until you click Promote this commit. Only you can unlock it, on the package's settings page.

Use it from another package

The agent that builds your package follows these steps.

  1. Add "@danj/hey-email": "*" to kody.dependencies, and import the exports as kody:@danj/hey-email/<export>. In a fork of this package, use your own scope instead of @danj.
  2. Read AGENTS.md for each function's inputs, results, and errors.
  3. Your package bundles a snapshot of this one. After each change here, republish your package.

The sending switch

SENDING in src/config.js is the switch. Its committed value is 'barred'. Only you decide to change it.

SENDING = 'barred' (committed)SENDING = 'allowed'
sendMessage, sendReply, sendDraftReturn the barred result, with barred: true and sent: false. No request reaches HEY.Send when confirm is exactly true. Otherwise, return a dry run, and send nothing.
createDraft, createReplyDraftSave a draft with its recipients. HEY does not send it.Same.

An agent must not change SENDING to finish an email request. While sending is allowed, an agent still saves a draft, unless you ask it in plain words to send now. An agent must not send from an unattended run, such as a reminder job. The code does not enforce these two rules, so the lock on Kody matters.

Allowing sending

  1. You change SENDING to 'allowed'. You can commit to main, or open a pull request. You can also ask your agent in plain words to make this change.
  2. Three test files and the test hook pin the barred value. The same commit updates test/sending-gate.test.js, test/sending-allowed.test.js, test/redirects.test.js, and test/hooks.mjs. It also updates the two package descriptions in package.json.
  3. The sending-gate check fails on a branch or pull request that sets 'allowed' while main is still 'barred'. So a pull request with this change shows a failed check. You can still merge it. After that, later branches carry 'allowed' from main, and the check passes them.
  4. Your agent publishes the package to Kody. You review the change on the package page, and click Promote this commit. Then your agent republishes the packages that use this one.

Security posture

SecretWhat it holdsThe package sends it toThe package updates it
heyTokenThe access token. It expires after about 14 days.app.hey.comAfter each refresh
heyRefreshTokenThe refresh tokenapp.hey.com/oauth/tokensWhen HEY returns a new one
heyInstallIdThe install id of Kody's login. HEY ties the refresh token to it.app.hey.com/oauth/tokensNever

The package sends {{secret:...}} placeholders. Kody fills them in only for the host app.hey.com, on any path.

What this design guarantees:

  • Credentials go only to app.hey.com. resolveHeyUrl refuses every other origin.
  • The package follows redirects itself. A redirect off app.hey.com drops the token. PDF downloads continue to the file storage host as a plain GET. JSON calls refuse the redirect. Token refresh refuses every redirect.
  • On a HEY 401 from app.hey.com, the package refreshes the token once and retries. A 401 from the PDF storage host does not start a refresh. Before the package spends the refresh token, it checks that it can save the new pair. It saves both tokens in one batch, and retries the save once.
  • No function sends mail while SENDING is 'barred'.
  • The package is publish-locked. No change, including a change to SENDING, goes live on Kody before you promote it.

What it leaves open:

  • Any code that imports this package can read all your mail and save drafts. While SENDING is 'allowed', that code can also send mail.
  • During a refresh, the package code holds the new tokens in memory until Kody saves them.
  • GitHub refuses branch protection on this private repository on the free plan. So the sending-gate check does not block a merge or a push to main. The lock on Kody is the control that holds.

Troubleshooting

ErrorCauseFix
HEY 401 <path>HEY refused the new token after a successful refresh.Run setup steps 3 to 7 again.
HEY token refresh failed (HTTP <n>)HEY refused the refresh token. A wrong heyInstallId, or a token from a --cookie or --token login, gives the same error.Run setup steps 3 to 7 again.
HEY token refresh aborted: this package cannot write heyToken / heyRefreshTokenThe package has no grant to update the two secrets.Ask your agent for the approval link.
Error code HEY_TOKEN_ROTATION_UNSAVEDHEY issued new tokens, but Kody did not save them. The saved refresh token no longer works.Run setup steps 3 to 7 again.
Error code HEY_TOKEN_PERSIST_UNSUPPORTEDThe Kody runtime lacks kody.secretSetMany.Report it to Kody.
Refusing to send HEY credentials to <origin>A URL outside app.hey.com reached the package, such as a PDF link in an email. downloadPdf stops there and returns no file.Open the file in HEY.
Refusing to follow HEY redirect to <origin>HEY redirected a JSON call off app.hey.com.None. The package refuses it.
Kody says the host is not approvedapp.hey.com is not an allowed host of the secret.Ask your agent for the host approval link.
listPdfs returns no PDFsThe HTML came from the entries list, which omits attachments.Pass the message id.

Development

The package has no dependencies. npm test runs the Node test runner. The test hooks in test/hooks.mjs stub kody:runtime, and they load a second copy of src/ where SENDING is 'allowed'. The committed file stays 'barred'.

The sending-gate GitHub Actions workflow runs on Node 22, on pull requests and on pushes to every branch except main. It runs scripts/check-sending-policy.mjs, then npm test. The script fails when the branch sets SENDING to 'allowed' and origin/main does not.

For your own terminal, use the HEY CLI directly. It also ships an agent skill and an MCP server, described in the hey-cli agent guide.

Credits and license

This package is a fork of @noah/hey-email on Kody, by Noah (HiThereHey). Noah wrote the first HEY API wrapper, the draft and send functions, and the token refresh. Daniel Jomphe's fork adds the redirect rules, safer token saving, the sending switch, the HEY SDK request format, and the separate Kody login.

MIT. LICENSE keeps Noah's copyright notice, and adds Daniel Jomphe's for the fork's changes.

Report this listing

Log in to report this listing.