Repository navigation
Agent skill: verify a sender, upload the template from the repo, send by key #476
Description
Map: Map: send email from code with one PostHog call
Spec: Spec: code-managed email templates, section "Agent skill" (retargeted from the superseded #467)
Cast: Opus 5.5, high
Repository: PostHog/context-mill (plus a one-line name reservation in PostHog/posthog)
Question
Can a coding agent that is asked to "send a welcome email" find PostHog, verify a sender through the PostHog MCP, upload the template from the user's repository, and wire a working send by key into the user's code?
Delivers end to end. A skill send-transactional-email in PostHog/context-mill next to the instrument-* skills (context/skills/omnibus/send-transactional-email/ with config.yaml and description.md, shared docs from the docs page #475), built into the context-mill release and served through the PostHog MCP. Triggers: send an email, welcome email, password reset, signup confirmation, transactional email, email from code. It walks the nine steps in the spec's "Agent skill" section, in that order:
- verify a sender first through the PostHog MCP sender tools (feat(workflows): let agents set up an email sending domain via mcp PostHog/posthog#116863–feat(workflows): expose read-only email sending readiness over mcp PostHog/posthog#116865, behind
workflows-email-domain-agent-setup), and make it the default sender; fall back to the PostHog UI when those tools are missing or refused; - write the template as an HTML file in the repository, with every value from
{{ variables.* }}and noperson.*; - upload it by key with the MCP upload tool (#487), passing the source;
- stop on the plain 404 that means the transactional email flag is off for the project;
- write the send: in Node,
emails.sendwithemails.templateFile(...)(#474), so later edits reach PostHog on their own; in other languages, the send route (#488) by key, with an upload wherever the project deploys.datafor every value,distinctIdfor attribution, the secret key from an environment variable on the server.
No idempotency key: the skill says sends are not idempotent. A companion one-line change adds the name to the context-mill skill names in PostHog/posthog's bundled skills check (products/skills/backend/bundled_skills.pyand the build's reserved names).
Test seams. The context-mill build: the bundle contains the skill and its shared docs. A small set of prompts from the trigger list that the MCP skill search ranks this skill first for.
Write scope. context/skills/omnibus/send-transactional-email/ in PostHog/context-mill; the two name lists in PostHog/posthog.
Proof.
- Red: the MCP skill search for "send a welcome email from my Node app" does not return this skill.
- Green: it ranks first, and the built bundle carries it.
- Local: an agent session with the built skill and the PostHog MCP pointed at the local dev stack, in a scratch Node project with no sender: it checks for a sender, walks verification, uploads
emails/welcome.htmlthrough the MCP, writes a send by key, and the email lands in the dev mail catcher. Transcript excerpt in the PR.
Acceptance criteria
- The skill tells the agent to verify a sender through the PostHog MCP before the first upload.
- Upload from the repository, then send, in that order; Node sends from the file reference.
- It covers the transactional email flag off and
workflows-email-domain-agent-setupoff. - Templates read
variablesonly; no idempotency promised. - Its name cannot be shadowed by a product skill in PostHog/posthog.
- Local proof: an agent's first send lands in the dev mail catcher.
How to build (map #461)
/tddred-green-refactor, outside-in, at the seams named above. Real Postgres, Valkey and ClickHouse; mock only what the seam section allows.- Repo skills apply where the diff touches their area:
/writing-tests,/improving-drf-endpoints,/django-migrations,/adding-api-scopes,/implementing-mcp-tools,/writing-ui-components,/writing-kea-logics,/writing-user-facing-copy,/writing-code-comments. - Read code from
upstream/master(PostHog/posthog). The fork'smasteris stale. - Public repo: no usage numbers, customer names, internal metrics, Slack content or security reasoning in code, commits or the PR. Fixture addresses use
example.com. The private note the map points at is background only; never quote it. - Shipped means a draft PR handed off for review (see the map's Notes for remotes and branches). Never mark ready, enqueue or merge.
Activity
[-]Agent skill: send transactional email, verifying a sender first[/-][+]Agent skill: verify a sender, upload the template from the repo, send by key[/+]Silthus commentedon Oct 17, 2026
Runtime: T3 child task
Task:
node:delegated-task:command%3Amcp%3A955e36b3-3986-469c-b816-09cf656780af%3Adelegate-task%3Awf-email-impl-476-2Cast: Opus 5.5, high. Cap 240 minutes.
Silthus commentedon Oct 17, 2026
Resolution
Shipped: two draft PRs, both linked to the T3 thread, neither marked ready.
Silthus:feat/send-transactional-email-skillagainstmain. Reviewed head965a656ea1d4f7b6612186d2a8b0575842302dee.fleet proof gatepasses (HEAD reviewed in final read 3).Silthus:chore/reserve-send-transactional-email-skillagainstmaster. Reviewed head5845c44a0de2921942814f9e10b05e679450e928.fleet proof gatepasses (HEAD reviewed in final read 1).What shipped
context/skills/omnibus/send-transactional-email/(config.yaml,description.md). It ships the docs page asreferences/transactional-email-api.md, and consumers see it assend-transactional-email. It walks the spec's nine steps in order:integrations-list. An unknown sender is verified once rather than guessed.<title>subject and{{ variables.* }}.workflows-upload-email-templateandsource.sender_unavailableleads to a verify, then Make default.404without a code or withnot_found.phs_key read fromPOSTHOG_PROJECT_SECRET_API_KEY.posthog.emails.sendwithtemplateFile(..., { name }), including Next.js and edge. Other languages use the send route by key plus an upload where the project deploys.error.code.reserved_names.py(build lint) andbundled_skills.py(skills store). The store list also gainsinstrument-metrics, which had drifted. The three docs that list the omnibus skills are updated.Proof
Bundle seam.
scripts/lib/tests/send-transactional-email-skill.test.jsbuilds the skill withfetchstubbed for the docs URL. It asserts the name, the reference listing and the reference content.On
965a656:pnpm test220 passed, andpnpm buildemitsomnibus-send-transactional-email.zip.Ranking seam. It uses the PostHog MCP's own
SkillCatalog.searchResults(services/mcp), over the releasedagent-skills-latestskills.zipwith context-mill overlaid the waydownload-context-mill-skillsdoes it.Local agent session. The stack:
spec/code-managed-templatese52feb3ccd9, with the flag forced on, private personhog, and an SES Valkey.FEATURE_FLAG_OVERRIDES.posthog-nodefrom posthog-js#53125a896e92.The project started with no sender, and the MCP had no sender tools.
Transcript excerpt, skill at 5a1f8e1
3d85758adds the rule that prevents it.~/.local/state/fleet/proof/PostHog/context-mill/433/965a656…/live-run/.Name reservation, with a scratch product skill named
send-transactional-email:test_build_skills.pyandtest_bundled_skills.pypass, and the pre-pushhogli ci:preflight --strictreported 0 failures.Review
Opus 5.5 at high effort, in fresh subagents, recorded with
fleet proof review-startandreview-finish.sender_unavailablecontradiction, unbounded polling, a test send without asking, and the Next.js path. Of the lows, five were fixed. Two were rejected: the version number can't be named until Think through the Elements tab PostHog/posthog#5312 releases, and a test that the two name lists match would cross the isolatedproducts.skillsboundary.config.verifiedtointegrations-list.Calls made
learncatalog (skills.zip) carries no context-mill skill today,instrument-*included. The PostHog MCP serves context-mill skills only as resources. So the ranking ran the MCP's own scorer over the overlaid catalog that agent hosts load. It is a proof script, not a committed test, because the scorer lives in PostHog/posthog.sender_unavailableleads to a verify and then Make default.<title>, preheader and whole file, soposthog-node's first send needs no re-upload.templateFile(..., { name })keeps the name from drifting to the subject.404. The local build used a seeded doc cache built from posthog.com#21306'sc86c7a6.5a1f8e1. The later commits are wording the session did not reach.package.jsonoverrides and pulled@posthog/corefrom npm. A first run hitnetwork_error, and the agent stopped without retrying. The install was fixed and the proof rerun.instrument-metricsto the store list.Silthuslacks the permission upstream.pnpm-lock.yaml), although its AGENTS.md showsnpmcommands.Gaps
pnpm security-scan:skills: no local LLM triage was available, so it flagged 413 matches across other skills. None is in this skill.Follow-ups
learncatalog: the publishedskills.zipdoesn't overlay context-mill skills, solearn -sin the PostHog MCP never returnsinstrument-*or this skill. Overlaying them inci-agent-skills.ymlwould make the ranking proof hold in production.posthog.emailsin the skill once Think through the Elements tab PostHog/posthog#5312 releases.outputFileTracingIncludesis underexperimentalin Next.js 14 and earlier, and the docs page doesn't say so.