Skip to content

Email templates: show "Managed by code" and the source file in the library #489

Closed

Description

@Silthus

Map: Map: send email from code with one PostHog call
Spec: Spec: code-managed email templates, section ""Managed by code" in the UI"

Cast: Opus 5.5, high
Repository: PostHog/posthog

Question

Can a person in the template library tell that a template comes from code, find its file, and know that the next upload overwrites their edits?

Delivers end to end. On a template with managed_by = code: a "Managed by code" tag on the library card, the table row and the template page header; a source line "<path> in <repository>, last uploaded from <ref>" with links through the shared git link helper (frontend/src/lib/components/Git/gitMetadataParser.ts), plain text for unknown hosts, a commit hash shortened to 7; a notice that the next upload from code overwrites changes made here. On any keyed template: the key on the template page. The API tab's example uses the new send route (/messaging/send/email/) with template.key on a keyed template and template.id otherwise. No lock: the editor stays as it is.

Port and rename from the fork's feat/workflows-code-managed-read-only: CodeManagedTag.tsx, CodeManagedSource.tsx, codeManagedWorkflow.ts (c5e2485c1d, ebdfa6b6be). LemonUI, not quill.

Test seams. A kea logic test for the derived isCodeManaged and source label from the loaded template, and Storybook stories for the tag and the source line (known host, unknown host, no source). Prior art: products/workflows/frontend/TemplateLibrary/messageTemplateLogic.test.ts.

Write scope. products/workflows/frontend/TemplateLibrary/ (MessageTemplate.tsx, MessageTemplateCard.tsx, MessageTemplatesTable.tsx, messageTemplateLogic.ts, TransactionalEmailApiExample.tsx, types.ts, new tag and source components with stories).

Proof.

  • Red: the logic test fails (no isCodeManaged).
  • Green: it passes; stories render.
  • Local: upload a template with cURL on the local stack, open the library and the template page, screenshot both at 1280px with the side panel open and at full width. Before and after screenshots in the PR. Copy through /writing-user-facing-copy.

Acceptance criteria

  • UI-made templates look exactly as before.
  • The tag, source line and notice show only on code-managed templates.
  • Unknown hosts render plain text, never a broken link.
  • Screenshots in the PR.

How to build (map #461)

  • /tdd red-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's master is 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

  1. self-assigned this
    on Oct 16, 2026
  2. Silthus commented on Oct 16, 2026

    @Silthus
    OwnerAuthor

    Runtime: T3 child task
    Task: node:delegated-task:command%3Amcp%3A955e36b3-3986-469c-b816-09cf656780af%3Adelegate-task%3Awf-email-impl-489-1
    Cast: Opus 5.5, high. Cap 180 minutes.

  3. Silthus commented on Oct 17, 2026

    @Silthus
    OwnerAuthor

    Resolution

    Landed: 0062c7672a8 on spec/code-managed-templates (fast-forward e5234aa0bf9..0062c7672a8, one commit).
    Branch: spec/code-managed-templates/489. No PR: the ship ticket #498 opens it.

    What landed

    • "Managed by code" tag (CodeManagedTag.tsx): a LemonTag with IconCode, data-attr="message-template-managed-by-code", on the library card and next to the name in the template page header. It shows only when managed_by is code. Tooltip: "This template is uploaded from a file in a repository. To change it, edit the file and upload it again."
    • Source line (CodeManagedSource.tsx, data-attr="message-template-code-source") under the title: "<path> in <repository>, last uploaded from <ref>". Each part links on GitHub, GitLab and Bitbucket. Unknown hosts are plain text. A commit SHA is shortened to 7 characters, and a branch links to the branch.
    • Key on any keyed template, next to the source line, with a copy button (data-attr="message-template-key").
    • Notice on code-managed templates, worded for what Email templates: refuse edits to code-managed templates on the server #491 allows: "This template is managed by code, so changes made here can't be saved. To change it, edit the file and upload it again. To edit it here, duplicate it." Its Duplicate action (message-template-code-managed-duplicate) creates an editable copy without key or ownership.
    • Server refusal: a save that the server refuses with 403 immutable shows the server's detail in the toast, not "Failed to save template".
    • API tab: the example uses POST /api/projects/{id}/messaging/send/email/ with "template": { "key": … } on a keyed template, and "template": { "id": … } otherwise, plus one line that explains the reference.
    • Logic: messageTemplateLogic gains isCodeManaged and templateSource, derived from the saved template. The pure part sits in codeManagedTemplate.ts.
    • Shared git helper: GitMetadataParser gains getFileLink, getRefLink, isCommitSha and accepts scheme-less remotes such as github.com/acme/app (the shape the upload stores). Ported from the fork's feat/workflows-code-managed-read-only (c5e2485c1d, ebdfa6b6be) and renamed to template vocabulary. Existing callers are unchanged.
    • MessageTemplate type gains key, managed_by and source_* from the generated MessageTemplateApi.

    Proof

    Red (red.log), new tests before the change: 12 failed, 35 passed.

    • GitMetadataParser.getFileLink is not a function
    • Count not find value with key "isCodeManaged" in logic …
    • the immutable case got "Failed to save template. Please try again." instead of the server's detail.

    Green: gitMetadataParser.spec.ts plus every test under products/workflows/frontend/TemplateLibrary/: 6 suites, 71 passed, nothing skipped.

    Seam Cases
    messageTemplateLogic (kea, API mocked) made in PostHog, keyed but gui, GitHub commit, branch, unknown host, no source → isCodeManaged and templateSource
    messageTemplateLogic save failure new 403 immutable row in the existing parameterized test
    GitMetadataParser.getFileLink scheme-less GitHub, GitLab SSH, Bitbucket HTTPS, unknown host, encoded ref and path

    Local gate on the landed tree:

    • tsgo --noEmit --checkers 1 in frontend/ through fleet heavy: exit 0, no errors.
    • hogli ci:preflight --strict --against origin/spec/code-managed-templates: 0 failures. Warnings only: complexity on MessageTemplate (33, already over before this change), MessageTemplateCard (12) and templateSource (11).
    • The pre-push hook (hogli ci:preflight --strict) passed on the push: 0 failures.
    • No backend change, so no build:openapi or mypy run was needed.

    Screenshots: Storybook from this worktree rendering the real App scene against mocked data (MockHog project, example.com repositories, no customer data). Narrow is a 560px window, about the 520px scene of a 1280px window with the side panel open. Wide is 1440px.

    Before After
    Library (wide) before after · narrow
    Code-managed template wide · narrow wide · narrow
    API tab wide · narrow wide · narrow
    Unknown host (plain text) wide · narrow
    Template made in PostHog (unchanged) wide · narrow

    Calls made

    Follow-ups

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

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions