From 2e8b19e449f6df9c93e0e02807ba735b205cabae Mon Sep 17 00:00:00 2001 From: Harley Alexander <43975092+mayteio@users.noreply.github.com> Date: Sat, 24 Oct 2026 10:19:53 +0100 Subject: [PATCH] feat(email): map every transactional template to its workflows template Adds the Workflows message template id for all 36 Customer.io transactional templates, so each one can move through the transactional-emails-via-workflows flag without a deploy. Adds a check that every Workflows map key names a Customer.io template. Generated-By: PostHog Desktop Task-Id: a5c9c2a4-4563-4d98-a4b6-ef13d86f4473 --- posthog/email.py | 35 +++++++++++++++++++++++++++++++++++ posthog/test/test_email.py | 4 ++++ 2 files changed, 39 insertions(+) diff --git a/posthog/email.py b/posthog/email.py index 0beaa96d52ec..885853852055 100644 --- a/posthog/email.py +++ b/posthog/email.py @@ -172,7 +172,42 @@ def is_email_available(with_absolute_urls: bool = False) -> bool: # Message template ids in the US project, for templates that can send through Workflows instead of Customer.io. # A template sends through Workflows only for recipients the flag below matches. WORKFLOWS_TEMPLATE_ID_MAP: dict[str, str] = { + "2fa_enabled": "01a1645b-71b3-0000-6894-2ea67c11761f", + "2fa_disabled": "01a16464-c956-0000-a67a-e8014f9566bd", + "2fa_backup_code_used": "01a16465-2cfa-0000-2d68-689f6a6ce4f3", + "2fa_reset": "01a16465-992f-0000-b686-dfbd321721a6", "password_reset": "01a1597a-5d91-0000-99fa-9eeb18d293c5", + "invite": "01a16466-2c26-0000-00ea-11f6b69aa254", + "member_join": "01a16466-9901-0000-d083-3e41d7458c2b", + "email_change_old_address": "01a16466-fde6-0000-7fe2-5549cbf7fbdf", + "email_change_new_address": "01a16467-9143-0000-29d6-de5e283ede18", + "password_changed": "01a16467-f65c-0000-31de-f71cdefe710f", + "login_notification": "01a16468-8007-0000-8fd2-911a6b6e5891", + "personal_api_key_exposed": "01a16469-2b55-0000-aff5-d3bcfc293ff8", + "code_based_verification": "01a16472-1d73-0000-2828-3a196b3d5bc5", + "email_verification_code": "01a16473-0e70-0000-fcab-8290ccf17558", + "feature_flags_secure_api_key_exposed": "01a16469-95fd-0000-6717-9ec46285eef3", + "project_secret_api_key_exposed": "01a16472-afbb-0000-e94f-9c65348f46e9", + "oauth_token_exposed": "01a1646a-003a-0000-8d21-32e544374fe7", + "passkey_added": "01a1646a-85df-0000-d018-9cdc391f507a", + "passkey_removed": "01a1646a-ec21-0000-5989-5dd4bb7ecf47", + "new_conversation_ticket": "01a1646b-6047-0000-06f5-956fcb309c33", + "project_deleted": "01a1646b-e43c-0000-c54d-9ab304e2084e", + "organization_deleted": "01a1646c-4a57-0000-19c3-4d5fbb1d8030", + "approval_requested": "01a16311-33e6-0000-c533-f383fd8e70f0", + "approval_approved": "01a1646c-ae43-0000-dc0f-70a90476419c", + "approval_rejected": "01a1646d-2ca8-0000-849e-6d0f6fd6e7af", + "approval_expired": "01a1646d-8fb5-0000-b8bb-9347407f1e06", + "approval_applied": "01a1646d-eeb4-0000-b84b-6e0de52e284f", + "conversation_restore": "01a1646e-8a53-0000-76c1-e7a99a8ee3cb", + "proxy_provisioned": "01a16311-b3de-0000-8aec-7c865f3e5432", + "delegation_invite": "01a1646f-0222-0000-db43-2831af7b0f2d", + "provisioning_welcome": "01a1646f-ba3d-0000-ecbb-d588b45bcb19", + "baa_signed_ai_disabled": "01a16470-2afa-0000-aec4-390d56587c4c", + "integration_access_requested": "01a16470-c82d-0000-6436-a68687c19f8c", + "posthog_ai_access_requested": "01a16471-350d-0000-75a6-3ebb077c03a1", + "wizard_pr_ready": "01a16471-c17b-0000-8ee5-eb254065b7a1", + "loop_run_summary": "01a16473-9c4d-0000-99d8-958c03b0f1a0", } # Release conditions match the `email` and `transactional_email_template` person properties passed at evaluation, diff --git a/posthog/test/test_email.py b/posthog/test/test_email.py index c17d1891f063..408b0c2d90e1 100644 --- a/posthog/test/test_email.py +++ b/posthog/test/test_email.py @@ -25,6 +25,7 @@ from posthog.email import ( CUSTOMER_IO_TEMPLATE_ID_MAP, + WORKFLOWS_TEMPLATE_ID_MAP, EmailMessage, WorkflowsEmailError, _send_email, @@ -936,6 +937,9 @@ def test_all_http_templates_are_registered_in_customer_io_map(self) -> None: "'Unknown template name' at runtime and capture_exception will swallow it.", ) + # A Workflows key that names no template would never match a send, so that template stays on Customer.io. + self.assertEqual(set(WORKFLOWS_TEMPLATE_ID_MAP) - set(CUSTOMER_IO_TEMPLATE_ID_MAP), set()) + class TestMessagingHashSalt(BaseTest): def test_get_email_hash_defaults_preserve_legacy_secret_key_hashes(self) -> None: