Skip to content

feat(transactional-email): add a command that sets up the first email send - #1487

Closed
Silthus wants to merge 2 commits into
PostHog:mainfrom
Silthus:feat/transactional-email-program
Closed

Silthus wants to merge 2 commits into
PostHog:mainfrom
Silthus:feat/transactional-email-program

Conversation

@Silthus

@Silthus Silthus commented Oct 22, 2026 •

Copy link
Copy Markdown

Problem

A team that has never sent email from code has no guided way to start with PostHog. Wizard can add PostHog to a project, but the setup agent has no path to send a transactional email, such as a welcome email after signup.

With this PR, npx @posthog/wizard transactional-email checks for a verified sender, writes the project secret API key to the env file, and adds the first send to the app's server code. A plain run that finds PostHog already installed lists the command in its intro spell book.

Part of Silthus/posthog#544.

Impact

Pays into Grow Workflows through organizations that send email from code (not measured yet: it needs the send source on each send, PostHog/posthog#120628). In scope: transactional email. Expected move: medium, up. Run cost: n/a.

Merge Danger

Warning

🟡 Merge risk: medium · one-way door: a release publishes the transactional-email command name (merge-risk rubric)

  • The skill this runs, omnibus-send-transactional-email, is not in the published skill menu yet (PostHog/context-mill#433, draft). Released before it, the command fails when it downloads the skill.
  • The transactional email API is in beta and on per project. Where it is off, the skill emits [ABORT] transactional email api not enabled. On the default Pi binding no program's [ABORT] ends the run yet (see Gaps), so such a run can still end on the success outro.
  • Merge after fix: drop stale introspection from interactive OAuth scope list #433 is released and the posthog-node release with emails.send is out. A revert removes the command and the spell book row.

Changes

  • New program transactional-email (src/programs/transactional-email/), built with createSkillProgram, on the default binding (Pi, linear).
  • New command transactional-email (nativeCommandFactory), registered in runCli.
  • The intro spell book of a plain run lists it last: transactional-email Send your first transactional email from code.
  • Two abort cases match the skill's "Inside the PostHog Wizard" section: the API is not enabled, and no server code was found. The linear sequence matches them against the run's abort reason.
  • No extra OAuth scopes. The PostHog MCP hides tools whose scopes the login lacks: the integrations-* sender tools and the workflows-* email tools. The skill's Wizard section treats a missing tool as inconclusive, sends the person to the PostHog UI to verify a sender, and takes only a send's 404 as "API not enabled".
  • README: a "Transactional email" section.

The skill's Wizard section (ask through wizard_ask, write the key with set_env_values, reach PostHog tools through posthog_exec, the [ABORT] reasons) is in PostHog/context-mill#433.

Test plan

Test seams:

  • CLI dispatch (src/cli/__tests__/programs-cli.test.ts): transactional-email runs with skill omnibus-send-transactional-email. Red before the command existed (module not found), green after.
  • Intro spell book (intro-menu.test.ts): the curated list resolves and ends with transactional-email, and the row fits 80 columns. Red, then green.
  • Abort cases (src/programs/transactional-email/__tests__): each [ABORT] reason from the skill matches exactly one case.
  • Bindings and flow: program-registry.test.ts pins the default linear binding. The flow-traces snapshot records intro, health check, auth, run, and outro for the program.

Checks: pnpm typecheck and pnpm lint pass with no errors. pnpm exec vitest run: 257 files and 3,810 tests pass.

$ npx tsx bin.ts --help
  bin.ts revenue-analytics    Set up PostHog for Revenue Analytics
  bin.ts transactional-email  Send your first transactional email from code

Gaps:

  • The Pi harness ends a run only on [posthog:abort], the gateway refusal marker. It ignores a skill's [ABORT], so these abort cases, like those of revenue-analytics and mcp-analytics, don't fire on the default binding yet. Fixing that is a separate PR.
  • No end-to-end run of the setup agent. It needs the skill in a published menu (or --local-context-mill), Wizard CI credentials, and a project with the transactional email API on.
  • No rendered screenshot of the spell book row. The intro-menu test covers the row's text.

Decisions

Question Answer Who Where
Offer transactional email setup in Wizard Yes, as a draft, in the core run where it fits 👤 Human Silthus/posthog#544
Where the entry hooks into the core run A native command plus a spell book row. An install-run offer needs a signal for "sends no email yet", and a seeded orchestrator task needs a flag and runner support 🤖 Agent pending approval
OAuth scopes for sender setup None now. The skill treats hidden tools as inconclusive and uses the UI path. Add integration:read and integration:write once the OAuth app's scope ceiling holds them, together with the migrate family 🤖 Agent pending approval
[ABORT] ignored on the Pi harness Out of this PR: a side fix for every program on Pi, not a workaround in one skill 🤖 Agent pending approval

Involvement: no grilling rounds, no prototypes, one GPT-6.1 Sol review round over this PR and PostHog/context-mill#433 (four findings: three fixed in #433, one deferred as above) and one final read (one finding, fixed in #433). Proof captured as command output.

LLM context

Written by Claude Opus 5.5 (claude-opus-5-5) as a build lane of a planning map. The setup agent copy follows the "Wizard once, then the setup agent" rule.

🤖 Generated with Claude Code

Silthus and others added 2 commits October 23, 2026 13:46
… send

`npx @posthog/wizard transactional-email` runs the context-mill skill
omnibus-send-transactional-email: the setup agent checks for a verified
sender, writes the project secret API key to the env file, and adds the
first send with posthog-node or the HTTP API. The intro spell book of a
plain run lists it, and the skill's two [ABORT] reasons end the run with
a next step.

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

Import `ProgramConfig` from `@programs/program-contract`, where the services
refactor moved it, and take the outro's `exit` step from the skill cleanup
in the flow trace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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