From 5fc3e6ef4216b97792c3cdd337f7863afa8f78b3 Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Sun, 28 Jun 2026 13:22:45 +0200 Subject: [PATCH 1/3] feat(templates): bulletproof CTA buttons in transactional emails Replace raw inline action URLs with email-client-safe, table-based CTA buttons (inline CSS, no external CSS/flexbox) plus a "Button not working?" fallback link, across verify-email, reset-password-email and signup-invite. Placeholders and brand-neutral styling preserved. --- config/templates/reset-password-email.html | 17 ++++++++++++++++- config/templates/signup-invite.html | 17 ++++++++++++++++- config/templates/verify-email.html | 17 ++++++++++++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/config/templates/reset-password-email.html b/config/templates/reset-password-email.html index 3e1fa001a..4454c0cea 100644 --- a/config/templates/reset-password-email.html +++ b/config/templates/reset-password-email.html @@ -7,7 +7,22 @@

Dear {{displayName}},

You have requested to reset your password on {{appName}}

-

Please visit this url to reset your password: {{url}}

+

Click the button below to choose a new password:

+ + + + +
+ Reset password +
+

+ Button not working? {{url}} +

The {{appName}} Support Team.


Hello,

You've been invited to join {{appName}}.

-

Click here to create your account: {{url}}

+

Click the button below to create your account:

+ + + + +
+ Create your account +
+

+ Button not working? {{url}} +

The {{appName}} Team.


Dear {{displayName}},

Thank you for signing up on {{appName}}.

-

Please verify your email address by visiting this url: {{url}}

+

Please verify your email address by clicking the button below:

+ + + + +
+ Verify email address +
+

+ Button not working? {{url}} +

This link will expire in 24 hours.

The {{appName}} Support Team.


From 6734790c88b57e88a1585912d2cfabbde07b9425 Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Sun, 28 Jun 2026 13:41:40 +0200 Subject: [PATCH 2/3] fix(emails): extend bulletproof CTA button to org-request + org-member-added templates Adversarial review found 2 transactional templates with a bare inline {{url}} left unstyled. Apply the same table-based CTA button + fallback line so all action-link emails are consistent. Claude-Session: https://claude.ai/code/session_011zXXYka6vU5utEGoT4frME --- config/templates/org-member-added.html | 43 +++++++++++++++++++++----- config/templates/org-request-new.html | 43 +++++++++++++++++++++----- 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/config/templates/org-member-added.html b/config/templates/org-member-added.html index 12298e938..006bdb3e2 100644 --- a/config/templates/org-member-added.html +++ b/config/templates/org-member-added.html @@ -1,8 +1,35 @@ - -{{appName}} invitation - -

Hello {{displayName}},

-

You have been invited to join {{orgName}} on {{appName}}.

-

Review and accept the invitation here: {{url}}

-

The {{appName}} Team.

- + + + + {{appName}} invitation + + +

Hello {{displayName}},

+

You have been invited to join {{orgName}} on {{appName}}.

+

Review and accept the invitation:

+ + + + +
+ Accept invitation +
+

Button not working? {{url}}

+

The {{appName}} Team.

+ + diff --git a/config/templates/org-request-new.html b/config/templates/org-request-new.html index 77a879cbb..59f837c18 100644 --- a/config/templates/org-request-new.html +++ b/config/templates/org-request-new.html @@ -1,8 +1,35 @@ - - - -

Hello,

-

{{requesterName}} ({{requesterEmail}}) has requested to join your organization {{orgName}} on {{appName}}.

-

Review this request in your dashboard: {{url}}

-

The {{appName}} Team.

- + + + + + + +

Hello,

+

{{requesterName}} ({{requesterEmail}}) has requested to join your organization {{orgName}} on {{appName}}.

+

Review this request in your dashboard:

+ + + + +
+ Review request +
+

Button not working? {{url}}

+

The {{appName}} Team.

+ + From 0388ef45b1d9bc860b7f8e7cdc8caf8c3370c5da Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Sun, 28 Jun 2026 14:14:52 +0200 Subject: [PATCH 3/3] fix(emails): add rel=noopener noreferrer to CTA buttons (review) Adversarial review (Copilot): the target=_blank CTA buttons across all 5 transactional templates need rel=noopener noreferrer to prevent reverse tabnabbing when opened in a browser from an email client. Claude-Session: https://claude.ai/code/session_011zXXYka6vU5utEGoT4frME --- config/templates/billing-payment-failed.html | 28 ++++++++++------- .../templates/billing-quota-reached-100.html | 31 ++++++++++++------- .../templates/billing-quota-warning-80.html | 31 ++++++++++++------- config/templates/org-member-added.html | 1 + config/templates/org-request-approved.html | 20 +++++++----- config/templates/org-request-new.html | 1 + config/templates/org-request-rejected.html | 18 ++++++----- .../reset-password-confirm-email.html | 12 ++----- config/templates/reset-password-email.html | 22 ++++++++----- config/templates/signup-invite.html | 22 ++++++++----- config/templates/verify-email.html | 22 ++++++++----- 11 files changed, 127 insertions(+), 81 deletions(-) diff --git a/config/templates/billing-payment-failed.html b/config/templates/billing-payment-failed.html index ea72734b1..a91468af0 100644 --- a/config/templates/billing-payment-failed.html +++ b/config/templates/billing-payment-failed.html @@ -1,12 +1,16 @@ - - - -

Hello,

-

We were unable to process the latest payment for your {{appName}} subscription.

-

Your account remains active for now. Please update your payment method before the grace period ends to avoid any service interruption.

-

Update your card on your billing portal.

-
-

The {{appName}} Team.

-
- Please do not reply to this email, you can contact us here. - + + + + + + +

Hello,

+

We were unable to process the latest payment for your {{appName}} subscription.

+

Your account remains active for now. Please update your payment method before the grace period ends to avoid any service interruption.

+

Update your card on your billing portal.

+
+

The {{appName}} Team.

+
+ Please do not reply to this email, you can contact us here. + + diff --git a/config/templates/billing-quota-reached-100.html b/config/templates/billing-quota-reached-100.html index 5c8ffc328..3d2fceff6 100644 --- a/config/templates/billing-quota-reached-100.html +++ b/config/templates/billing-quota-reached-100.html @@ -1,12 +1,19 @@ - - - -

Hello,

-

Your {{appName}} weekly quota is now fully used ({{meterUsed}} / {{meterQuota}} units).

-

If you have an extras pack, your usage continues drawing from its remaining balance until it's used up. Otherwise it pauses until you add an extras pack or upgrade your plan — nothing is charged automatically.

-

Check your consumption or add an extras pack on your billing dashboard.

-
-

The {{appName}} Team.

-
- Please do not reply to this email, you can contact us here. - + + + + + + +

Hello,

+

Your {{appName}} weekly quota is now fully used ({{meterUsed}} / {{meterQuota}} units).

+

+ If you have an extras pack, your usage continues drawing from its remaining balance until it's used up. Otherwise it pauses until you add an + extras pack or upgrade your plan — nothing is charged automatically. +

+

Check your consumption or add an extras pack on your billing dashboard.

+
+

The {{appName}} Team.

+
+ Please do not reply to this email, you can contact us here. + + diff --git a/config/templates/billing-quota-warning-80.html b/config/templates/billing-quota-warning-80.html index 842c53e15..5ad4872ba 100644 --- a/config/templates/billing-quota-warning-80.html +++ b/config/templates/billing-quota-warning-80.html @@ -1,12 +1,19 @@ - - - -

Hello,

-

Your {{appName}} plan has reached {{threshold}}% of its weekly quota ({{meterUsed}} / {{meterQuota}} units used).

-

You still have room to keep going. Once you reach 100%, runs continue only while you have an extras-pack balance; without one they pause until you add an extras pack or upgrade your plan — nothing is charged automatically.

-

Review your usage, add an extras pack, or upgrade your plan on your billing dashboard.

-
-

The {{appName}} Team.

-
- Please do not reply to this email, you can contact us here. - + + + + + + +

Hello,

+

Your {{appName}} plan has reached {{threshold}}% of its weekly quota ({{meterUsed}} / {{meterQuota}} units used).

+

+ You still have room to keep going. Once you reach 100%, runs continue only while you have an extras-pack balance; without one they pause until + you add an extras pack or upgrade your plan — nothing is charged automatically. +

+

Review your usage, add an extras pack, or upgrade your plan on your billing dashboard.

+
+

The {{appName}} Team.

+
+ Please do not reply to this email, you can contact us here. + + diff --git a/config/templates/org-member-added.html b/config/templates/org-member-added.html index 006bdb3e2..0b00ab255 100644 --- a/config/templates/org-member-added.html +++ b/config/templates/org-member-added.html @@ -13,6 +13,7 @@ - -

Hello {{displayName}},

-

Your request to join {{orgName}} on {{appName}} has been approved.

-

You can now access the organization.

-

The {{appName}} Team.

- + + + + + + +

Hello {{displayName}},

+

Your request to join {{orgName}} on {{appName}} has been approved.

+

You can now access the organization.

+

The {{appName}} Team.

+ + diff --git a/config/templates/org-request-new.html b/config/templates/org-request-new.html index 59f837c18..d7c5a4b07 100644 --- a/config/templates/org-request-new.html +++ b/config/templates/org-request-new.html @@ -13,6 +13,7 @@
- -

Hello {{displayName}},

-

Your request to join {{orgName}} on {{appName}} has been declined.

-

The {{appName}} Team.

- + + + + + + +

Hello {{displayName}},

+

Your request to join {{orgName}} on {{appName}} has been declined.

+

The {{appName}} Team.

+ + diff --git a/config/templates/reset-password-confirm-email.html b/config/templates/reset-password-confirm-email.html index 12d397e3b..6df17b307 100644 --- a/config/templates/reset-password-confirm-email.html +++ b/config/templates/reset-password-confirm-email.html @@ -1,4 +1,4 @@ - + @@ -6,15 +6,9 @@

Dear {{displayName}},

-

- This is a confirmation that the password for your account has just been - changed -

+

This is a confirmation that the password for your account has just been changed

The {{appName}} Support Team.


- Please do not reply to this email, you can contact us -
here. + Please do not reply to this email, you can contact us here. diff --git a/config/templates/reset-password-email.html b/config/templates/reset-password-email.html index 4454c0cea..71ca5c26d 100644 --- a/config/templates/reset-password-email.html +++ b/config/templates/reset-password-email.html @@ -1,4 +1,4 @@ - + @@ -14,20 +14,28 @@ Reset password -

- Button not working? {{url}} -

+

Button not working? {{url}}

The {{appName}} Support Team.


If you didn't make this request, you can ignore this email. Please do not - reply to this email, you can contact us + >If you didn't make this request, you can ignore this email. Please do not reply to this email, you can contact us here. diff --git a/config/templates/signup-invite.html b/config/templates/signup-invite.html index d0dabd4c2..e40fce3ac 100644 --- a/config/templates/signup-invite.html +++ b/config/templates/signup-invite.html @@ -1,4 +1,4 @@ - + You've been invited — complete your signup @@ -13,20 +13,28 @@ Create your account -

- Button not working? {{url}} -

+

Button not working? {{url}}

The {{appName}} Team.


If you weren't expecting this invitation you can ignore this email. Please - do not reply to this email, you can contact us + >If you weren't expecting this invitation you can ignore this email. Please do not reply to this email, you can contact us here. diff --git a/config/templates/verify-email.html b/config/templates/verify-email.html index fea61893c..3dfe7de17 100644 --- a/config/templates/verify-email.html +++ b/config/templates/verify-email.html @@ -1,4 +1,4 @@ - + @@ -14,21 +14,29 @@ Verify email address -

- Button not working? {{url}} -

+

Button not working? {{url}}

This link will expire in 24 hours.

The {{appName}} Support Team.


If you didn't create this account, you can ignore this email. Please do - not reply to this email, you can contact us + >If you didn't create this account, you can ignore this email. Please do not reply to this email, you can contact us here.