Skip to content

feat: brew.send(key, payload) and fire by key (13.1.0) - #55

Open
thedogwiththedataonit wants to merge 7 commits into
mainfrom
claude/send-by-key
Open

thedogwiththedataonit wants to merge 7 commits into
mainfrom
claude/send-by-key

Conversation

@thedogwiththedataonit

@thedogwiththedataonit thedogwiththedataonit commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

What

App email by trigger key, the SDK half of the DX push (brew.send). Rebased on main d804502 (10-16).

  • brew.send(key, payload, options?) on every client, including withBrand(...) clients:
    brew.send('auth.password-reset', { email, confirmationUrl, appName }) fires
    POST /v1/automations/triggers/by-key/{triggerKey}/fire with { payload } and resolves to the accepted fire
    (triggerInstanceId, automationRunIds, triggerKey, ...). { idempotencyKey } is forwarded as the
    Idempotency-Key header; { raw: true } passes through.
  • brew.automations.triggers.fireByKey({ triggerKey, payload }, options?): the object form, mirroring fire
    (TPayload generic, raw overload). An unknown key throws BrewApiError 404 TRIGGER_KEY_NOT_FOUND, a Brew-event
    trigger 409 TRIGGER_NOT_FIREABLE; an empty, . or .. key throws a TypeError before any request (dot segments
    would otherwise resolve to another route). FireTriggerByKeyResponse.triggerKey is required.
  • BrewTriggerKeyRegistry (augment with declare module '@brew.new/sdk'): empty by default, so send takes any
    string key and { email: string } & { [k: string]: SendPayloadValue | undefined }. Once augmented, unknown keys and
    wrong payloads stop compiling; a key typed as a union must carry a payload that fits every key in it, and a key whose
    payload is itself a union is satisfied by any one of its alternatives (Greptile P2, fixed in d073ec7).
  • Spec: brew-v2 main 8c7020e535 plus exactly the operations and schemas of GetBrew/brew-v2#2013 (d556eaf50d)
    and #2017 (63736f0f99). With #2017 now stacked on #2013 on current main, the vendored file is byte-identical to
    #2017's own spec. Trigger rows carry key + appEmail, create / patch take key, TriggerFireAccepted.triggerKey,
    codes TRIGGER_KEY_NOT_FOUND / TRIGGER_KEY_RESERVED / TRIGGER_KEY_TAKEN. Types regenerated with
    bun run generate:types.
  • Docs: README "Send your first app email", docs/send.md, docs/automations.md trigger keys, the two-positional
    brew.send exception in docs/development.md#the-brewsend-exception (AGENTS.md pointer; AGENTS.md is 8,176 bytes).

Version: 13.1.0 (not released)

npm already has 12.2.0 and 12.3.0. Main's CHANGELOG "Unreleased" section (#71, canvas revoke response) changes a
public method signature and is planned as 13.0.0 under RELEASING.md's version policy (brew-cli's RELEASING waits
for it). This PR only adds, so it is the next minor after that: 13.1.0. The CHANGELOG heading is
13.1.0 (not released yet), above Unreleased. package.json stays at main's version: the release commit
(npm version 13.1.0) sets it, so merging this before 13.0.0 is cut cannot skip 13.0.0. Cut 13.0.0 first.
#64 (adapters + appEmails) is 13.2.0. Consumers that need brew.send pin ^13.1.0.

Rebase notes

  • Conflicts: client.ts (main's canvas resource + send, both kept), package.json (main's 12.3.0 kept),
    AGENTS.md (main's text + the one-line brew.send pointer), CHANGELOG (main's history kept; this PR's entry rewritten as
    13.1.0; the 12.1.0 backfill kept), spec + generated types (rebuilt, not merged).
  • The old branch's docs/development.md section lived in a merge commit and was dropped by the rebase; restored in
    1fe8154.

Publish gate

Do not tag or publish until GetBrew/brew-v2#2013 and #2017 are deployed and the live spec matches the vendored one
(the by-key operation and the trigger key / appEmail fields). Until then the by-key route answers 404 and trigger
rows carry no key. If either PR changes its spec before merge, re-vendor and run bun run generate:types.

Tests

  • tests/resources/automations/triggers/fire-by-key.test.ts (MSW): dotted key in the path, body { payload }, auto and
    caller idempotency header, / and ? percent-encoded, ./../empty refused, { raw: true }, 404 mapping.
  • tests/send.test.ts (MSW via createBrewClient): body, undefined field dropped, idempotency option, X-Brand-Id
    from withBrand, 404.
  • tests/types/send-registry.test.ts + tests/types/augmented-registry/ (a separate TypeScript program; every
    @ts-expect-error must fire): unknown key, wrong payload, missing field, union key, union payload alternatives.

Local on head d073ec7: bun tsc, bun lint, bun run format:check, bun run test (124 files, 713 tests),
bun run build, generate:types freshness, bun install --frozen-lockfile (no changes), AGENTS.md budget: all pass.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no blocking issues remain.

Summary

Adds brew.send(key, payload, options?) and automations.triggers.fireByKey, with brand scoping, request options, and typed trigger keys.

  • Triggers can be fired by a stable key instead of a minted id.
  • brew.send sends app email by its trigger key.
  • Trigger keys can be set, cleared, and read with trigger details.
  • The changelog now includes the missing 12.1.0 release notes.
  • Trigger API notes and shared types now describe key-based fires.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["brew.send(key, payload, options)"] --> B["triggers.fireByKey"]
  C["withBrand client"] --> A
  B --> D["Shared HTTP client"]
  D --> E["POST /v1/automations/triggers/by-key/{triggerKey}/fire"]
  E --> F["Accepted fire or raw response"]
Loading

Reviews (4) · Last reviewed commit: "fix(send): keep a union payload's altern..." · Reviewed by Greptile

Comment thread src/send-registry.ts
Comment thread src/send-registry.ts
thedogwiththedataonit added a commit to GetBrew/brew-cli that referenced this pull request Oct 16, 2026
package.json goes to 0.17.0 and the Unreleased notes (mcp install, browser
sign-in, send and app-emails, the canvas SDK methods) become 0.17.0.
RELEASING.md gains the app email SDK gate: `send` and `app-emails` need the
SDK release carrying GetBrew/typescript-sdk#55 and #64, which is not on npm
yet, so the dependency and bun.lock stay on main's pin until it publishes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit to GetBrew/brew-cli that referenced this pull request Oct 16, 2026
package.json goes to 0.17.0 and the Unreleased notes (mcp install, browser
sign-in, send and app-emails, the canvas SDK methods) become 0.17.0.
RELEASING.md gains the app email SDK gate: `send` and `app-emails` need the
SDK release carrying GetBrew/typescript-sdk#55 and #64, which is not on npm
yet, so the dependency and bun.lock stay on main's pin until it publishes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit to GetBrew/brew-cli that referenced this pull request Oct 16, 2026
package.json goes to 0.17.0 and the Unreleased notes (mcp install, browser
sign-in, send and app-emails, the canvas SDK methods) become 0.17.0.
RELEASING.md gains the app email SDK gate: `send` and `app-emails` need the
SDK release carrying GetBrew/typescript-sdk#55 and #64, which is not on npm
yet, so the dependency and bun.lock stay on main's pin until it publishes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit and others added 4 commits October 16, 2026 12:10
- automations.triggers.fireByKey({ triggerKey, payload }, options?) for
  POST /v1/automations/triggers/by-key/{triggerKey}/fire, mirroring fire
  (TPayload generic, { raw: true } overload); empty/dot-segment keys throw
  a TypeError before any request.
- brew.send(key, payload, options?) on every client (and withBrand
  clients), typed by the augmentable BrewTriggerKeyRegistry: any string
  key while it is empty, exactly its keys and payloads once augmented.
- Regenerated spec from brew-v2#2017: trigger rows carry key + appEmail,
  create/patch take key, the fire body gains triggerKey, new
  TRIGGER_KEY_* error codes.
- README "Send your first app email", docs/send.md, automations.md trigger
  keys, AGENTS.md two-positional exception, CHANGELOG 12.2.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `brew.send(key, payload)` with `key: 'auth.password-reset' | 'order.paid'`
  no longer accepts a password-reset payload: `BrewSendPayloadFor<TKey>`
  takes a literal key's own payload and, for a union key, the intersection
  of its members' payloads. Literal-key errors stay on the offending
  property (a union-of-tuples rest signature moved every one of them to
  "No overload matches this call" on the call line).
- `FireTriggerByKeyResponse.triggerKey` is required: a by-key fire,
  accepted or replayed, always names the key.
- With an empty registry a payload field may be `undefined`; `JSON.stringify`
  leaves it out of the body.
- Type tests: union key refused (with and without `{ raw: true }`), a
  payload fitting every member accepted, narrowed pairs accepted,
  `string | undefined` fields accepted, `email: undefined` refused.
  Mutation-checked against the old payload type. The 404 test mocks the
  API's real TRIGGER_KEY_NOT_FOUND suggestion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elog

- README and docs/send.md use the canonical kit keys and the shared auth
  payload (`appName` optional); no "receipts", no `npx brew.new init`.
- docs/send.md: the by-key route needs the brew-v2 #2013 + #2017 deploy
  before 12.2.0 is released; union-key and `undefined` field rules.
- CHANGELOG: the missing 12.1.0 entry (#50); 12.2.0 names the vendored
  spec commit and what else shipped since 12.1.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's Unreleased section (canvas revoke response) is planned as 13.0.0
under the method-signature version policy, and 12.2.0/12.3.0 are already
published. brew.send and fireByKey therefore ship as 13.1.0. The vendored
spec is brew-v2 main 8c7020e535 plus #2013 (d556eaf50d) and #2017
(104d635e70); package.json stays at main's version for the release
commit to bump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit and others added 2 commits October 16, 2026 12:14
…e rebase

AGENTS.md links to docs/development.md#the-brewsend-exception; the section
lived in the old merge commit and did not survive the rebase onto main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2017 was rebased onto #2013 (d556eaf50d) on brew-v2 main 8c7020e535, so
the vendored spec is now byte-identical to its own. New since 104d635e70:
a Brew-event trigger refuses a by-key fire with 409 TRIGGER_NOT_FIREABLE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 16, 2026
- CHANGELOG: this ships as 13.2.0, after 13.1.0 (#55) and main's planned
  13.0.0; the payload merge order reads `{ ...variables, email: to }`
  (the recipient wins), as the code does; error and warning codes listed
  are only those in the vendored spec; new: import modes (#2064),
  Better Auth invites, the migrate command.
- Spec: brew-v2 main 8c7020e535 + #2013 d556eaf50d + #2017 104d635e70 +
  #2066 c72bb45467 + #2064 70fb35dd71 (team sandbox #2059 not included).
- Packaging checks cover main's /embed and /react: typesVersions entries,
  smoke loads both (with react linked in), `npm pack --ignore-scripts`
  (main added a prepare script), and the only allowed peer is the
  optional react.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BrewSendPayloadFor intersected the payloads after distributing over them,
so a key whose registered payload is itself a union required every
alternative at once (Greptile P2 on #55): with 'invite.sent' as
{ channel: 'link', … } | { channel: 'code', … }, a key typed
'invite.sent' | 'auth.password-reset' collapsed to never. Distribute over
the keys only: (A1 | A2) & B. Type test added to the augmented registry
program (fails before the fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 16, 2026
- CHANGELOG: this ships as 13.2.0, after 13.1.0 (#55) and main's planned
  13.0.0; the payload merge order reads `{ ...variables, email: to }`
  (the recipient wins), as the code does; error and warning codes listed
  are only those in the vendored spec; new: import modes (#2064),
  Better Auth invites, the migrate command.
- Spec: brew-v2 main 8c7020e535 + #2013 d556eaf50d + #2017 104d635e70 +
  #2066 c72bb45467 + #2064 70fb35dd71 (team sandbox #2059 not included).
- Packaging checks cover main's /embed and /react: typesVersions entries,
  smoke loads both (with react linked in), `npm pack --ignore-scripts`
  (main added a prepare script), and the only allowed peer is the
  optional react.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thedogwiththedataonit thedogwiththedataonit changed the title feat: brew.send(key, payload) and fire by key (12.2.0) feat: brew.send(key, payload) and fire by key (13.1.0) Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant