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.

AGENTS.md

56 lines · 2.7 KB · Markdown

@noah/hey-email

Imports

import identity from 'kody:@noah/hey-email'
import search from 'kody:@noah/hey-email/search'
import listBoxes from 'kody:@noah/hey-email/boxes'
import getTopic from 'kody:@noah/hey-email/topic'
import getEntries from 'kody:@noah/hey-email/entries'
import getMessage from 'kody:@noah/hey-email/message'
import listAndDownloadPdfs from 'kody:@noah/hey-email/attachments'
import { listDrafts, createReplyDraft, createDraft, getDraft, sendDraft, sendMessage, sendReply, getReplyPrefill } from 'kody:@noah/hey-email/drafts'
import { sendMessage } from 'kody:@noah/hey-email/send'
import createReplyDraft from 'kody:@noah/hey-email/reply'

Smoke tests

await identity()
await search({ q: 'receipt', date: 'last_7_days' })
await listDrafts()

Edge cases

  • Auth is {{secret:heyToken}} only — never paste tokens.
  • Credentials are only sent to app.hey.com; resolveHeyUrl refuses other origins.
  • Prefer /advanced_search.json?q= for search; Search HTML endpoints return 406 without Accept handling.
  • Topic reads use /topics/{id}.json and /topics/{id}/entries.json.
  • Full message body + trix attachments: /messages/{entryId}.json via getMessage. Entries list does not include attachment markup — always fetch the message.
  • PDF download: parse data-trix-attachment JSON (""), then GET https://app.hey.com + url with the same bearer (follow redirects).
  • The CLI auto-refreshes its own token, but the heyToken secret is a static copy and does not. On HEY 401, have the user run hey auth refresh && hey auth token and update the secret. hey auth status reports the local expires_at.
  • A hey auth login --cookie login stores a cookie, not a bearer token; hey auth token refuses to print it and this package cannot use it.
  • Never auto-send. createReplyDraft / createDraft omit to so HEY stores a draft. sendMessage, sendReply, and sendDraft are dry-run unless { confirm: true }.
  • When the user explicitly asks to email someone, call sendMessage({ to, subject, text, confirm: true }). Do not send on a reminder job.
  • New send: POST /messages.json with { acting_sender_id, subject, content, to }. Reply send: POST /entries/{id}/replies.json with the same plus to. Draft send: PUT /messages/{id}.json with to.
  • Prefill: GET /entries/{entryId}/replies/new.json. Create draft: POST /entries/{entryId}/replies.json with { acting_sender_id, subject, content } only.
  • List drafts: GET /entries/drafts.json. Edit URL is /messages/{id}/edit.

Attachments

./attachments parses data-trix-attachment JSON and nested <action-text-attachment> / ActiveStorage .pdf URLs (some senders wrap the PDF inside an HTML trix blob).