Skip to content

feat(workflows): let agents set up an email sending domain via mcp - #116863

Closed
Silthus wants to merge 2 commits into
PostHog:masterfrom
Silthus:upstream/email-domain-agent-setup
Closed

Silthus wants to merge 2 commits into
PostHog:masterfrom
Silthus:upstream/email-domain-agent-setup

Conversation

@Silthus

@Silthus Silthus commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Problem

  • An agent on the PostHog MCP cannot set up a Workflows email sender: no tool creates a sender, reads its DNS records, offers one-click DNS setup, or verifies the domain.
  • Enabling the routes alone would not help. Personal API keys and OAuth tokens get "This action does not support personal API key access" on email/verify, PATCH email and both Domain Connect actions, because none of them is in the viewset's scope lists.
  • The four routes declare no schemas, so generated tools would get untyped or empty inputs.

This recreates the fork drafts Silthus#343 and Silthus#352 on current master. Their earlier upstream drafts, #109703 and #113059, are closed.

Warning

🟡 Merge risk: medium · two-way door (merge-risk rubric)

  • Every caller of the four email sender and Domain Connect routes now goes through new request serializers, with or without the flag. A payload the web app sends that they reject would break sender edits or Domain Connect links.
  • Token access, SES writes from tokens and the address check stay off until workflows-email-domain-agent-setup is on. The web app's payloads match the serializers, and HTTP tests cover each route for session, API key and OAuth callers.

Changes

Agents can take a domain from "add a sender" to "verified" over MCP, behind the default-off workflows-email-domain-agent-setup flag. The backend checks the same flag, so turning it on enables the tools and the API access together.

Tool Does
integrations-email-create Creates the sender and registers its domain
integrations-email-verify-create Returns the DNS records with per-record status, and verifies
integrations-domain-connect-check-retrieve Says if the DNS host supports one-click setup
integrations-domain-connect-apply-url-create Returns a URL the person approves at their DNS host
integrations-email-partial-update Changes the display name, reply-to address or MAIL FROM subdomain
  • Scoped API keys and OAuth reach the four actions only while the flag is on for the caller. The check reads (integration:read); verify, apply-url and the update write (integration:write), because verify and apply-url call SES.
  • Session access does not change, including email and proxy Domain Connect links while the flag is off.
  • Scoped credentials get Domain Connect for email senders only. A proxy request from a token returns 403 before any URL is signed.
  • The four actions declare request, query and response schemas. URLs and success responses do not change.
  • email/verify and PATCH email on a non-email integration return 400 instead of 500.
  • apply-url returns 404 for an unknown or non-email integration_id, and for an unknown proxy_record_id. Before, these returned 500 or a "try again later" 400.
  • apply-url accepts a redirect_uri only when it points back to PostHog.
  • With the flag on, PATCH email with a different address returns 400. The check ignores case.
  • The generic integrations-create tool stays disabled: it takes an untyped config for 30+ kinds, including credentials. integrations-email-create reuses the same route with kind: email injected and a typed config.
  • A new setting-up-an-email-domain skill teaches the flow, including SPF merging and keeping an existing DMARC record.
  • A new sandboxed eval suite, eval_email_domain_setup, runs an agent with the skill against the real API over MCP, with SES, DNS and Domain Connect simulated per case.
  • An MCP codegen fix: param_overrides.<field>.required now also works with input_schema. Before, codegen ignored it.
  • Mechanical: the email sender serializer moved to posthog/api/integration_domain_serializers.py with help_text, and the OpenAPI types, MCP schemas, tool definitions and snapshots are regenerated.

Note

Turn the flag on only after this PR has deployed to Django. The MCP server deploys separately, and until Django has the change every tool call returns 403.

Start with EmailDomainAgentSetupPermission and the four actions in posthog/api/integration.py. No web app screen changes.

How did you test this code?

Test rationale:

  • TestEmailIntegrationScopedAccess (API key, OAuth and session, flag off/on/unresolved): fails if a token reaches verify or update with the flag off, or a session loses access. It also covers read-only scope, member role, a foreign integration, a project-restricted key and the read-only MCP policy. No existing test called these actions with a token.
  • test_email_domain_actions_with_scoped_api_key (12 cases in the existing API key class): fails if an action moves between scope lists, if a response body changes shape, or if apply-url stops returning 404/403 for unknown, non-email or proxy requests.
  • test_email_update_from_the_app_keeps_the_sender_address: fails if a sender stored with capitals cannot be edited, or if the address check runs with the flag off.
  • test_integration_domain_serializers.py (SimpleTestCase): per-context required fields on apply-url, a malformed proxy_record_id, and maildev outside DEBUG.
  • products/workflows/evals/test/: the simulation runs the real SES provider and Domain Connect code; the scorers reject split SPF records and case-changed TXT values.
  • MCP unit tests: the apply-url tool never forwards context: proxy; the codegen required override works with input_schema; the sender tools appear only with the flag on.

Ran locally on this head with an isolated test database (--nomigrations): posthog/api/test/test_integration.py, the serializer, Domain Connect, email model and eval tests, and the MCP generator, filtering, apply-url and schema snapshot suites. hogli build:openapi and hogli build:schema-mcp regenerated cleanly, repo-wide mypy passed, and hogli ci:preflight --strict passed.

Not run: upstream CI beyond the draft subset, which waits for a maintainer to approve the fork runs. Also not run: the eval suite itself (it needs LLM gateway and Braintrust keys), a live MCP session against a running stack, real SES, or a real Domain Connect approval.

👉 Stay up-to-date with PostHog coding conventions for a smoother review.

Release status

  • This change is behind a feature flag and is not available to users

Docs update

Updated the OAuth development guide with the sender and Domain Connect access that scoped credentials get while the flag is on.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: Claude Code, Claude Opus 5.5 (claude-opus-5-5)

🤖 Generated with Claude Code

Add five MCP tools for creating, verifying and editing Workflows email
senders and for Domain Connect, typed request and response schemas for
their endpoints, and scoped API key and OAuth access behind the
workflows-email-domain-agent-setup flag. Add the
setting-up-an-email-domain skill and its eval suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Oct 14, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

Regenerate the MCP tool inputs with the reply_to field, hide redirect_uri
from the Domain Connect tool, mention reply-to in the guide and the skill,
drop a duplicate test flag entry, and type the test auth headers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@scheduled-actions-posthog

Copy link
Copy Markdown
Contributor

This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, please remove the stale label – otherwise this will be closed in another week. If you want to permanently keep it open, use the waiting label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant