diff --git a/MIGRATIONS.md b/MIGRATIONS.md index 2c1f8c142..0a7f21d75 100644 --- a/MIGRATIONS.md +++ b/MIGRATIONS.md @@ -4,6 +4,24 @@ Breaking changes and upgrade notes for downstream projects. --- +## mailer: the 15 shared `config/templates/*.html` mails are now body fragments on the default layout (2026-10-04) + +#4133: `billing-*`, `org-*`, `referral-reward-earned`, `reset-password-*`, `signup-invite`, `verify-email` and `welcome` are no longer full HTML documents — each is now a body fragment rendered inside the default layout (`_layout/layout.html` + its header/footer/button partials, #4132/#4166), the same mechanism #4164–#4171 already added for a downstream's own templates. + +- Each template's old hardcoded sign-off ("The `{{appName}}` Team." / "... Support Team.") is gone — replaced by `brand.signature`, which has **no fallback**: unset, none of the 15 sign off any more. +- Each template's old inline "Please do not reply…" footer is gone, replaced by the shared footer's standardized contact line, which now reaches all 15 (5 of them — the 4 `org-*` mails and `referral-reward-earned` — never rendered a contact line before). +- The 6 CTA templates render their button via `{{> button url=url label="..."}}` instead of a copy-pasted table. +- `appName`/`appContact` no longer come from a caller-passed param — both are now resolved from the brand (#4177). `appContact` is gone from every body (10 of the 15 used to render it inline, now 0 do; it's footer-only, `brand.contact`). `appName` still renders inline in 14 of the 15 bodies (now brand-sourced, `brand.name`, and also used by the header partial); `reset-password-confirm-email` is the one template with none left — its only use was the removed sign-off. Each template's own `` is gone too — 4 of them (`org-member-added`, `referral-reward-earned`, `signup-invite`, `welcome`) had a non-empty one (the other 11 were empty); the layout now has a single `<title>`, always the mail's `subject`. +- Unchanged otherwise: every remaining variable each template renders, its CTA `url`, its "Button not working?" fallback link, and the three mail-specific caveat sentences (reset-password-email, signup-invite, verify-email). + +### Action for downstream + +Set `config.mailer.brand.signature` (and `brand.logoUrl`/`brand.primaryColor`/etc., #4165) if you want these mails to keep a sign-off or look branded — nothing renders by default. No other action needed: a downstream's own full-document template (source starting with `<!doctype`/`<html>`) is unaffected, it still bypasses the layout entirely. + +**If a downstream already edited one of these 15 shared templates in place** (your copy of `config/templates/<name>.html` no longer matches upstream): move your edited copy to `config/templates/<project>/<name>.html`, add one `config.mailer.templates` entry pointing `<name>` at it (`{ templates: { '<name>': 'config/templates/<project>/<name>.html' } }`), then restore the shared file to upstream's version — explicitly (`git checkout devkit-node/master -- config/templates/<name>.html`) before your next `/update-stack`, or let that run's own ISO merge do it (`config/templates/*.html` is ISO-merge scope, same as `modules/`/`lib/`) — in that order, override first, so the ISO merge's overwrite never touches the only copy of your edits. Until that restore happens, your moved copy is still a full HTML document, so it keeps rendering unwrapped (the legacy-full-document path) the whole time — nothing breaks mid-migration, and converting it to a fragment on the shared layout afterward is optional. + +--- + ## skills: thin service re-export, zero-logic unit-test exception, `create-module` `crud-only` option (2026-10-03) `.claude/skills/{naming,feature,create-module,verify}/SKILL.md` updated — no runtime code changed: @@ -111,11 +129,15 @@ rejected send can never break or delay the signup / OAuth redirect response. **What you will see:** every new signup receives a welcome email once the mailer is configured. To opt out, set `organizations.welcomeEmail.enabled: false` in the project config (default `true`, fail-open — read with `?? true` -so an absent key never silently disables it). To customize the copy, edit the -same-named template at `config/templates/welcome.html` (`{{displayName}}`, -`{{appName}}`, `{{url}}`, `{{appContact}}`, and an optional -`{{#if orgName}}` block, omitted in B2C mode). No schema change, no migration -to run. +so an absent key never silently disables it). To customize the copy, don't +edit `config/templates/welcome.html` in place — it's a Devkit-owned shared +template (#4133) and `/update-stack`'s ISO merge will overwrite it. Add a +`config.mailer.templates` entry instead (`{ templates: { welcome: +'config/templates/<project>/welcome.html' } }`) pointing at your own copy, +seeded from the shared one (`{{displayName}}`, `{{appName}}`, `{{url}}`, and +an optional `{{#if orgName}}` block, omitted in B2C mode — `appContact` is no +longer a body variable, it's the brand-driven footer's contact line, #4133). +No schema change, no migration to run. ## Billing: public plans listing now requires a plan tag (2026-09-25) diff --git a/config/templates/billing-credit-exhausted.html b/config/templates/billing-credit-exhausted.html index 36723d202..ca28e1213 100644 --- a/config/templates/billing-credit-exhausted.html +++ b/config/templates/billing-credit-exhausted.html @@ -1,18 +1,6 @@ -<!doctype html> -<html lang="en"> - <head> - <title> - - -

Hello,

-

Your {{appName}} account is out of credits.

-

- Usage is paused until you add more credits or upgrade your plan — nothing is charged automatically. -

-

Add credits 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}} account is out of credits.

+

+ Usage is paused until you add more credits or upgrade your plan — nothing is charged automatically. +

+

Add credits or upgrade your plan on your billing dashboard.

diff --git a/config/templates/billing-credit-warning.html b/config/templates/billing-credit-warning.html index 1680f0ab8..834075ec3 100644 --- a/config/templates/billing-credit-warning.html +++ b/config/templates/billing-credit-warning.html @@ -1,18 +1,6 @@ - - - - - - -

Hello,

-

Your {{appName}} account has {{remaining}} credits left.

-

- Usage keeps working while credits remain. Once they run out, usage is paused until you add credits — nothing is charged automatically. -

-

Add more credits 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}} account has {{remaining}} credits left.

+

+ Usage keeps working while credits remain. Once they run out, usage is paused until you add credits — nothing is charged automatically. +

+

Add more credits or upgrade your plan on your billing dashboard.

diff --git a/config/templates/billing-payment-failed.html b/config/templates/billing-payment-failed.html index a91468af0..446246163 100644 --- a/config/templates/billing-payment-failed.html +++ b/config/templates/billing-payment-failed.html @@ -1,16 +1,4 @@ - - - - - - -

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.

diff --git a/config/templates/billing-quota-reached-100.html b/config/templates/billing-quota-reached-100.html index 3d2fceff6..8baf3d89e 100644 --- a/config/templates/billing-quota-reached-100.html +++ b/config/templates/billing-quota-reached-100.html @@ -1,19 +1,7 @@ - - - - - - -

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.

diff --git a/config/templates/billing-quota-warning-80.html b/config/templates/billing-quota-warning-80.html index 5ad4872ba..bf4b78535 100644 --- a/config/templates/billing-quota-warning-80.html +++ b/config/templates/billing-quota-warning-80.html @@ -1,19 +1,7 @@ - - - - - - -

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.

diff --git a/config/templates/org-member-added.html b/config/templates/org-member-added.html index 0b00ab255..b749da766 100644 --- a/config/templates/org-member-added.html +++ b/config/templates/org-member-added.html @@ -1,36 +1,5 @@ - - - - {{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.

- - +

Hello {{displayName}},

+

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

+

Review and accept the invitation:

+{{> button url=url label="Accept invitation"}} +

Button not working? {{url}}

diff --git a/config/templates/org-request-approved.html b/config/templates/org-request-approved.html index 5422a6600..911b5d4d6 100644 --- a/config/templates/org-request-approved.html +++ b/config/templates/org-request-approved.html @@ -1,12 +1,3 @@ - - - - - - -

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.

diff --git a/config/templates/org-request-new.html b/config/templates/org-request-new.html index d7c5a4b07..851d823cb 100644 --- a/config/templates/org-request-new.html +++ b/config/templates/org-request-new.html @@ -1,36 +1,5 @@ - - - - - - -

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.

- - +

Hello,

+

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

+

Review this request in your dashboard:

+{{> button url=url label="Review request"}} +

Button not working? {{url}}

diff --git a/config/templates/org-request-rejected.html b/config/templates/org-request-rejected.html index 79fcc9390..c69031300 100644 --- a/config/templates/org-request-rejected.html +++ b/config/templates/org-request-rejected.html @@ -1,11 +1,2 @@ - - - - - - -

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.

diff --git a/config/templates/referral-reward-earned.html b/config/templates/referral-reward-earned.html index 71b49828a..2a4958d8e 100644 --- a/config/templates/referral-reward-earned.html +++ b/config/templates/referral-reward-earned.html @@ -1,12 +1,3 @@ - - - - {{appName}} referral reward - - -

Hello {{displayName}},

-

Great news — someone you invited just joined {{appName}}, and you've earned a referral reward of {{units}} units.

-

Thanks for helping grow {{appName}}.

-

The {{appName}} Team.

- - +

Hello {{displayName}},

+

Great news — someone you invited just joined {{appName}}, and you've earned a referral reward of {{units}} units.

+

Thanks for helping grow {{appName}}.

diff --git a/config/templates/reset-password-confirm-email.html b/config/templates/reset-password-confirm-email.html index 6df17b307..3ef200a01 100644 --- a/config/templates/reset-password-confirm-email.html +++ b/config/templates/reset-password-confirm-email.html @@ -1,14 +1,2 @@ - - - - - - - -

Dear {{displayName}},

-

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. - - +

Dear {{displayName}},

+

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

diff --git a/config/templates/reset-password-email.html b/config/templates/reset-password-email.html index 71ca5c26d..1313e7290 100644 --- a/config/templates/reset-password-email.html +++ b/config/templates/reset-password-email.html @@ -1,42 +1,6 @@ - - - - - - - -

Dear {{displayName}},

-

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

-

Click the button below to choose a new password:

- - - - -
- Reset password -
-

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 - here. - - +

Dear {{displayName}},

+

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

+

Click the button below to choose a new password:

+{{> button url=url label="Reset password"}} +

Button not working? {{url}}

+

If you didn't make this request, you can ignore this email.

diff --git a/config/templates/signup-invite.html b/config/templates/signup-invite.html index e40fce3ac..4f58ec4f8 100644 --- a/config/templates/signup-invite.html +++ b/config/templates/signup-invite.html @@ -1,41 +1,6 @@ - - - - You've been invited — complete your signup - - -

Hello,

-

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

-

Click the button below to create your account:

- - - - -
- Create your account -
-

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 - here. - - +

Hello,

+

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

+

Click the button below to create your account:

+{{> button url=url label="Create your account"}} +

Button not working? {{url}}

+

If you weren't expecting this invitation you can ignore this email.

diff --git a/config/templates/verify-email.html b/config/templates/verify-email.html index 3dfe7de17..ba2d2aa47 100644 --- a/config/templates/verify-email.html +++ b/config/templates/verify-email.html @@ -1,43 +1,7 @@ - - - - - - - -

Dear {{displayName}},

-

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

-

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.

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

Dear {{displayName}},

+

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

+

Please verify your email address by clicking the button below:

+{{> button url=url label="Verify email address"}} +

Button not working? {{url}}

+

This link will expire in 24 hours.

+

If you didn't create this account, you can ignore this email.

diff --git a/config/templates/welcome.html b/config/templates/welcome.html index 856bd3f21..e27da3ad4 100644 --- a/config/templates/welcome.html +++ b/config/templates/welcome.html @@ -1,43 +1,7 @@ - - - - {{appName}} - - - -

Hello {{displayName}},

-

- Welcome to {{appName}}{{#if orgName}} — your workspace {{orgName}} is ready{{/if}}. -

-

Here's where to pick things up:

- - - - -
- Get started -
-

Button not working? {{url}}

-

The {{appName}} Team.

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

Hello {{displayName}},

+

+ Welcome to {{appName}}{{#if orgName}} — your workspace {{orgName}} is ready{{/if}}. +

+

Here's where to pick things up:

+{{> button url=url label="Get started"}} +

Button not working? {{url}}

diff --git a/lib/helpers/mailer/index.js b/lib/helpers/mailer/index.js index fca8ffc08..8f5de0bde 100644 --- a/lib/helpers/mailer/index.js +++ b/lib/helpers/mailer/index.js @@ -103,8 +103,10 @@ const hbs = handlebars.create(); * returns before this is ever called (epic-audit follow-up on * #4160/#4127) and is compiled on its own pristine `handlebars.create()` * instance instead, so `{{> partial}}` is NOT available to a legacy full - * document, deliberately (none of the 15 shipped legacy templates use - * one today). The 4 reads are independent, so they run in parallel. + * document, deliberately (none of today's shared Devkit templates are + * full documents any more — #4133 — so only a downstream's own + * full-document template takes this path). The 4 reads are independent, + * so they run in parallel. * Re-read and re-registered on every render, same no-cache behavior as * the template/layout file reads below. * @returns {Promise} @@ -141,8 +143,9 @@ const registerPartials = async () => { * inherit a `{{> partial}}` registration left over from a prior fragment * render in the same process — a legacy document that references a partial * now always throws, deterministically, instead of sometimes resolving - * depending on render order. None of the 15 shipped legacy templates - * reference a partial today. + * depending on render order. None of today's shared Devkit templates + * take this path any more (#4133); only a downstream's own full-document + * template does. * * Every other template is a body fragment: it is wrapped in `mailer.layout` * (or the Devkit default), which receives `{{{body}}}` (the only unescaped diff --git a/lib/helpers/mailer/layout.js b/lib/helpers/mailer/layout.js index d0ef55fb8..cccca778e 100644 --- a/lib/helpers/mailer/layout.js +++ b/lib/helpers/mailer/layout.js @@ -19,10 +19,11 @@ export const PARTIAL_NAMES = Object.keys(DEFAULT_PARTIAL_PATHS); const LEGACY_FULL_DOCUMENT_RE = /^\s*<(!doctype|html)\b/i; /** - * @desc Detect a legacy full-document template (today's 15 shared mails, - * and any full-document template a downstream owns): it is already a - * complete HTML document, so it is sent unwrapped instead of through the - * layout. + * @desc Detect a legacy full-document template (a downstream's own full + * document, e.g. its own branded email a project ships outside Devkit's + * `config/templates/`): it is already a complete HTML document, so it is + * sent unwrapped instead of through the layout. The 15 shared Devkit + * mails (#4133) are body fragments and never match this. * @param {string} source - Raw (uncompiled) template source * @returns {boolean} True when the source is a full document */ diff --git a/lib/helpers/mailer/tests/__snapshots__/mailer.templates.unit.tests.js.snap b/lib/helpers/mailer/tests/__snapshots__/mailer.templates.unit.tests.js.snap index 06d0ed473..59ae0bc07 100644 --- a/lib/helpers/mailer/tests/__snapshots__/mailer.templates.unit.tests.js.snap +++ b/lib/helpers/mailer/tests/__snapshots__/mailer.templates.unit.tests.js.snap @@ -1,31 +1,31 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing -exports[`mailer baseline render — all shared templates: renders billing-credit-exhausted with every referenced variable present in its fixture 1`] = `"Hello, Your Acme account is out of credits . Usage is paused until you add more credits or upgrade your plan — nothing is charged automatically. Add credits or upgrade your plan on your billing dashboard . The Acme Team. Please do not reply to this email, you can contact us here ."`; +exports[`mailer baseline render — all shared templates: renders billing-credit-exhausted with every referenced variable present in its fixture 1`] = `"Acme Hello, Your Acme account is out of credits . Usage is paused until you add more credits or upgrade your plan — nothing is charged automatically. Add credits or upgrade your plan on your billing dashboard . Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders billing-credit-warning with every referenced variable present in its fixture 1`] = `"Hello, Your Acme account has 12 credits left . Usage keeps working while credits remain. Once they run out, usage is paused until you add credits — nothing is charged automatically. Add more credits or upgrade your plan on your billing dashboard . The Acme Team. Please do not reply to this email, you can contact us here ."`; +exports[`mailer baseline render — all shared templates: renders billing-credit-warning with every referenced variable present in its fixture 1`] = `"Acme Hello, Your Acme account has 12 credits left . Usage keeps working while credits remain. Once they run out, usage is paused until you add credits — nothing is charged automatically. Add more credits or upgrade your plan on your billing dashboard . Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders billing-payment-failed with every referenced variable present in its fixture 1`] = `"Hello, We were unable to process the latest payment for your Acme 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 Acme Team. Please do not reply to this email, you can contact us here ."`; +exports[`mailer baseline render — all shared templates: renders billing-payment-failed with every referenced variable present in its fixture 1`] = `"Acme Hello, We were unable to process the latest payment for your Acme 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 . Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders billing-quota-reached-100 with every referenced variable present in its fixture 1`] = `"Hello, Your Acme weekly quota is now fully used (1000 / 1000 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 Acme Team. Please do not reply to this email, you can contact us here ."`; +exports[`mailer baseline render — all shared templates: renders billing-quota-reached-100 with every referenced variable present in its fixture 1`] = `"Acme Hello, Your Acme weekly quota is now fully used (1000 / 1000 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 . Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders billing-quota-warning-80 with every referenced variable present in its fixture 1`] = `"Hello, Your Acme plan has reached 80% of its weekly quota (800 / 1000 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 Acme Team. Please do not reply to this email, you can contact us here ."`; +exports[`mailer baseline render — all shared templates: renders billing-quota-warning-80 with every referenced variable present in its fixture 1`] = `"Acme Hello, Your Acme plan has reached 80% of its weekly quota (800 / 1000 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 . Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders org-member-added with every referenced variable present in its fixture 1`] = `"Acme invitation Hello Alice, You have been invited to join Acme Org on Acme. Review and accept the invitation: Accept invitation Button not working? https://acme.test/org The Acme Team."`; +exports[`mailer baseline render — all shared templates: renders org-member-added with every referenced variable present in its fixture 1`] = `"Acme Hello Alice, You have been invited to join Acme Org on Acme. Review and accept the invitation: Accept invitation Button not working? https://acme.test/org Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders org-request-approved with every referenced variable present in its fixture 1`] = `"Hello Alice, Your request to join Acme Org on Acme has been approved. You can now access the organization. The Acme Team."`; +exports[`mailer baseline render — all shared templates: renders org-request-approved with every referenced variable present in its fixture 1`] = `"Acme Hello Alice, Your request to join Acme Org on Acme has been approved. You can now access the organization. Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders org-request-new with every referenced variable present in its fixture 1`] = `"Hello, Bob (bob@example.com) has requested to join your organization Acme Org on Acme. Review this request in your dashboard: Review request Button not working? https://acme.test/request The Acme Team."`; +exports[`mailer baseline render — all shared templates: renders org-request-new with every referenced variable present in its fixture 1`] = `"Acme Hello, Bob (bob@example.com) has requested to join your organization Acme Org on Acme. Review this request in your dashboard: Review request Button not working? https://acme.test/request Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders org-request-rejected with every referenced variable present in its fixture 1`] = `"Hello Alice, Your request to join Acme Org on Acme has been declined. The Acme Team."`; +exports[`mailer baseline render — all shared templates: renders org-request-rejected with every referenced variable present in its fixture 1`] = `"Acme Hello Alice, Your request to join Acme Org on Acme has been declined. Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders referral-reward-earned with every referenced variable present in its fixture 1`] = `"Acme referral reward Hello Alice, Great news — someone you invited just joined Acme, and you've earned a referral reward of 5 units. Thanks for helping grow Acme. The Acme Team."`; +exports[`mailer baseline render — all shared templates: renders referral-reward-earned with every referenced variable present in its fixture 1`] = `"Acme Hello Alice, Great news — someone you invited just joined Acme, and you've earned a referral reward of 5 units. Thanks for helping grow Acme. Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders reset-password-confirm-email with every referenced variable present in its fixture 1`] = `"Dear Alice, This is a confirmation that the password for your account has just been changed The Acme Support Team. Please do not reply to this email, you can contact us here ."`; +exports[`mailer baseline render — all shared templates: renders reset-password-confirm-email with every referenced variable present in its fixture 1`] = `"Acme Dear Alice, This is a confirmation that the password for your account has just been changed Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders reset-password-email with every referenced variable present in its fixture 1`] = `"Dear Alice, You have requested to reset your password on Acme Click the button below to choose a new password: Reset password Button not working? https://acme.test/reset The Acme 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 here ."`; +exports[`mailer baseline render — all shared templates: renders reset-password-email with every referenced variable present in its fixture 1`] = `"Acme Dear Alice, You have requested to reset your password on Acme Click the button below to choose a new password: Reset password Button not working? https://acme.test/reset If you didn't make this request, you can ignore this email. Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders signup-invite with every referenced variable present in its fixture 1`] = `"You've been invited — complete your signup Hello, You've been invited to join Acme . Click the button below to create your account: Create your account Button not working? https://acme.test/invite The Acme Team. If you weren't expecting this invitation you can ignore this email. Please do not reply to this email, you can contact us here ."`; +exports[`mailer baseline render — all shared templates: renders signup-invite with every referenced variable present in its fixture 1`] = `"Acme Hello, You've been invited to join Acme . Click the button below to create your account: Create your account Button not working? https://acme.test/invite If you weren't expecting this invitation you can ignore this email. Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders verify-email with every referenced variable present in its fixture 1`] = `"Dear Alice, Thank you for signing up on Acme. Please verify your email address by clicking the button below: Verify email address Button not working? https://acme.test/verify This link will expire in 24 hours. The Acme 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 here ."`; +exports[`mailer baseline render — all shared templates: renders verify-email with every referenced variable present in its fixture 1`] = `"Acme Dear Alice, Thank you for signing up on Acme. Please verify your email address by clicking the button below: Verify email address Button not working? https://acme.test/verify This link will expire in 24 hours. If you didn't create this account, you can ignore this email. Questions? help@example.com https://acme.test"`; -exports[`mailer baseline render — all shared templates: renders welcome with every referenced variable present in its fixture 1`] = `"Acme Hello Alice, Welcome to Acme — your workspace Acme Org is ready. Here's where to pick things up: Get started Button not working? https://acme.test/dashboard The Acme Team. Please do not reply to this email, you can contact us here ."`; +exports[`mailer baseline render — all shared templates: renders welcome with every referenced variable present in its fixture 1`] = `"Acme Hello Alice, Welcome to Acme — your workspace Acme Org is ready. Here's where to pick things up: Get started Button not working? https://acme.test/dashboard Questions? help@example.com https://acme.test"`; diff --git a/lib/helpers/mailer/tests/fixtures/legacy-full-document.html b/lib/helpers/mailer/tests/fixtures/legacy-full-document.html new file mode 100644 index 000000000..da56c60a9 --- /dev/null +++ b/lib/helpers/mailer/tests/fixtures/legacy-full-document.html @@ -0,0 +1,9 @@ + + + + Legacy document + + +

Hi {{name}}, this is a full document a downstream project ships on its own, outside the shared layout.

+ + diff --git a/lib/helpers/mailer/tests/mailer.layout.real.unit.tests.js b/lib/helpers/mailer/tests/mailer.layout.real.unit.tests.js index f43bd4c2d..7b2193de2 100644 --- a/lib/helpers/mailer/tests/mailer.layout.real.unit.tests.js +++ b/lib/helpers/mailer/tests/mailer.layout.real.unit.tests.js @@ -31,6 +31,11 @@ const mockConfig = { mailer: { templates: { 'layout-smoke-fragment': 'lib/helpers/mailer/tests/fixtures/layout-smoke-fragment.html', + // A downstream's own full document (#4133: none of the 15 shared + // Devkit templates are full documents any more, so the legacy + // passthrough path needs its own fixture to stay exercised against + // real, unmocked files). + 'legacy-full-document': 'lib/helpers/mailer/tests/fixtures/legacy-full-document.html', }, layout: undefined, partials: {}, @@ -131,4 +136,23 @@ describe('mailer layout — real shipped defaults (unmocked files):', () => { expect(styleBlock).not.toContain('''); expect(styleBlock).not.toContain('#112233'); // brand.primaryColor — also left