Skip to content

Commit 6f259c9

Browse files
committed
feat(payments-next): Update subscription reminder emails
Because: * Subscription reminder emails are not compatible with timing requirements from legal This commit: * Updates the reminder timing, and the subscription renewal reminder email templates * Updates tests * Fixes l10n output to match grunt generation * Updates storybook email renders Closes #PAY-2262
1 parent f881ee4 commit 6f259c9

File tree

23 files changed

+543
-54
lines changed

23 files changed

+543
-54
lines changed

libs/accounts/email-renderer/project.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"build-storybook": {
6060
"executor": "nx:run-commands",
6161
"outputs": ["{projectRoot}/storybook-static"],
62-
"dependsOn": ["build-css", "build-ts"],
62+
"dependsOn": ["l10n-merge", "build-css", "build-ts"],
6363
"options": {
6464
"commands": [
6565
"nx run accounts-email-renderer:build-storybook-only",
@@ -116,7 +116,7 @@
116116
"cwd": "."
117117
},
118118
"inputs": ["{projectRoot}/gruntfile.js", "{projectRoot}/src/**/en.ftl"],
119-
"outputs": ["{projectRoot}/public/en/auth.ftl"]
119+
"outputs": ["{projectRoot}/public/locales/en/branding.ftl", "{projectRoot}/public/locales/en/emails.ftl"]
120120
},
121121
"l10n-merge": {
122122
"executor": "nx:run-commands",
@@ -125,7 +125,7 @@
125125
"cwd": "."
126126
},
127127
"inputs": ["{projectRoot}/gruntfile.js", "{projectRoot}/src/**/en.ftl"],
128-
"outputs": ["{projectRoot}/public/en/auth.ftl"]
128+
"outputs": ["{projectRoot}/public/locales/en/branding.ftl", "{projectRoot}/public/locales/en/emails.ftl"]
129129
},
130130
"l10n-copy": {
131131
"executor": "nx:run-commands",

libs/accounts/email-renderer/src/renderer/subplat-email-renderer.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as SubscriptionAccountReminderFirst from '../templates/subscriptionAcco
88
import * as SubscriptionAccountReminderSecond from '../templates/subscriptionAccountReminderSecond';
99
import * as SubscriptionCancellation from '../templates/subscriptionCancellation';
1010
import * as SubscriptionDowngrade from '../templates/subscriptionDowngrade';
11+
import * as SubscriptionEndingReminder from '../templates/subscriptionEndingReminder';
1112
import * as SubscriptionFailedPaymentsCancellation from '../templates/subscriptionFailedPaymentsCancellation';
1213
import * as SubscriptionFirstInvoice from '../templates/subscriptionFirstInvoice';
1314
import * as SubscriptionPaymentExpired from '../templates/subscriptionPaymentExpired';
@@ -134,6 +135,20 @@ export class SubplatEmailRender extends EmailRenderer {
134135
});
135136
}
136137

138+
async renderSubscriptionEndingReminder(
139+
templateValues: SubscriptionEndingReminder.TemplateData,
140+
layoutTemplateValues: SubscriptionLayouts.TemplateData
141+
) {
142+
return this.renderEmail({
143+
template: SubscriptionEndingReminder.template,
144+
version: SubscriptionEndingReminder.version,
145+
layout: SubscriptionEndingReminder.layout,
146+
includes: SubscriptionEndingReminder.includes,
147+
...templateValues,
148+
...layoutTemplateValues,
149+
});
150+
}
151+
137152
async renderSubscriptionFailedPaymentsCancellation(
138153
templateValues: SubscriptionFailedPaymentsCancellation.TemplateData,
139154
layoutTemplateValues: SubscriptionLayouts.TemplateData
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Variables
2+
# $productName (String) - The name of the subscribed product, e.g. Mozilla VPN
3+
subscriptionEndingReminder-subject = Your { $productName } subscription will expire soon
4+
subscriptionEndingReminder-title = Your { $productName } subscription will expire soon
5+
6+
# Variables:
7+
# $productName (String) - The name of the subscribed product, e.g. Mozilla VPN
8+
# $serviceLastActiveDateOnly (String) - The date of last active service, e.g. 01/20/2016
9+
subscriptionEndingReminder-content-line1 = Your access to { $productName } will end on <strong>{ $serviceLastActiveDateOnly }</strong>.
10+
subscriptionEndingReminder-content-line2 = If you’d like to continue using { $productName }, you can reactivate your subscription in <a data-l10n-name="subscriptionEndingReminder-account-settings">Account Settings</a> before <strong>{ $serviceLastActiveDateOnly }</strong>. If you need assistance, <a data-l10n-name="subscriptionEndingReminder-contact-support">contact our Support Team</a>.
11+
subscriptionEndingReminder-content-line1-plaintext = Your access to { $productName } will end on { $serviceLastActiveDateOnly }.
12+
subscriptionEndingReminder-content-line2-plaintext = If you’d like to continue using { $productName }, you can reactivate your subscription in Account Settings before { $serviceLastActiveDateOnly }. If you need assistance, contact our Support Team.
13+
14+
subscriptionEndingReminder-content-closing = Thanks for being a valued subscriber!
15+
16+
subscriptionEndingReminder-churn-title = Want to keep access?
17+
subscriptionEndingReminder-churn-terms = <a data-l10n-name="subscriptionEndingReminder-churn-terms">Limited terms and restrictions apply</a>
18+
19+
# Variables:
20+
# $churnTermsUrlWithUtm (String) - URL to the terms and restrictions page applied to this promotion
21+
subscriptionEndingReminder-churn-terms-plaintext = Limited terms and restrictions apply: { $churnTermsUrlWithUtm }
22+
23+
# Variables:
24+
# $subscriptionSupportUrlWithUtm (String) - URL to the subscription products support page
25+
subscriptionEndingReminder-content-support-plaintext = Contact our Support Team: { $subscriptionSupportUrlWithUtm }
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<%# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/. %>
4+
5+
<%- include ('/partials/icon/index.mjml') %>
6+
7+
<mj-section>
8+
<mj-column>
9+
<mj-text css-class="text-header">
10+
<span data-l10n-id="subscriptionEndingReminder-title" data-l10n-args="<%= JSON.stringify({ productName }) %>">Your <%- productName %> subscription will expire soon</span>
11+
</mj-text>
12+
13+
<mj-text css-class="text-body">
14+
<span data-l10n-id="subscriptionEndingReminder-content-line1" data-l10n-args="<%= JSON.stringify({productName, serviceLastActiveDateOnly }) %>">
15+
Your access to <%- productName %> will end on <strong><%- serviceLastActiveDateOnly %></strong>.
16+
</span>
17+
</mj-text>
18+
19+
<mj-text css-class="text-body">
20+
<span data-l10n-id="subscriptionEndingReminder-content-line2" data-l10n-args="<%= JSON.stringify({productName, serviceLastActiveDateOnly, accountSettingsUrl, subscriptionSupportUrlWithUtm }) %>">
21+
If you’d like to continue using <%- productName %>, you can reactivate your subscription in <a href="<%- accountSettingsUrl %>" class="link-blue" data-l10n-name="subscriptionEndingReminder-account-settings">Account Settings</a> before <strong><%- serviceLastActiveDateOnly %></strong>. If you need assistance, <a href="<%- subscriptionSupportUrlWithUtm %>" class="link-blue" data-l10n-name="subscriptionEndingReminder-contact-support">contact our Support Team</a>.
22+
</span>
23+
</mj-text>
24+
25+
<mj-text css-class="text-body">
26+
<span data-l10n-id="subscriptionEndingReminder-content-closing">Thanks for being a valued subscriber!</span>
27+
</mj-text>
28+
</mj-column>
29+
</mj-section>
30+
31+
32+
<% if (locals.showChurn) { %>
33+
<mj-section css-class="container-grey">
34+
<mj-column>
35+
<mj-text css-class="text-header-two">
36+
<span data-l10n-id="subscriptionEndingReminder-churn-title">Want to keep access?</span>
37+
</mj-text>
38+
<mj-button css-class="primary-button-sm-subplat" href="<%- ctaButtonUrlWithUtm %>">
39+
<span><%- ctaButtonLabel %></span>
40+
</mj-button>
41+
<mj-text css-class="text-container-link">
42+
<span data-l10n-id="subscriptionEndingReminder-churn-terms" data-l10n-args="<%= JSON.stringify({churnTermsUrlWithUtm}) %>">
43+
<a href="<%- churnTermsUrlWithUtm %>" class="link-blue" data-l10n-name="subscriptionEndingReminder-churn-terms">Limited terms and restrictions apply</a>
44+
</span>
45+
</mj-text>
46+
</mj-column>
47+
</mj-section>
48+
<% } %>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
import { Meta } from '@storybook/html';
6+
import { subplatStoryWithProps } from '../../storybook-email';
7+
import { includes, TemplateData } from './index';
8+
9+
export default {
10+
title: 'SubPlat Emails/Templates/subscriptionEndingReminder',
11+
} as Meta;
12+
13+
const data = {
14+
productName: 'Firefox Fortress',
15+
serviceLastActiveDateOnly: 'July 15, 2025',
16+
accountSettingsUrl: 'http://localhost:3030/settings',
17+
subscriptionSupportUrlWithUtm: 'http://localhost:3030/support',
18+
churnTermsUrlWithUtm: 'http://localhost:3030/support',
19+
productIconURLNew:
20+
'https://cdn.accounts.firefox.com/product-icons/mozilla-vpn-email.png',
21+
ctaButtonLabel: 'Stay subscribed and save 20%',
22+
ctaButtonUrlWithUtm: 'http://localhost:3030/renew',
23+
showChurn: true,
24+
};
25+
26+
const createStory = subplatStoryWithProps<TemplateData>(
27+
'subscriptionEndingReminder',
28+
'Sent to remind a user their subscriptions is expiring soon.',
29+
data,
30+
includes
31+
);
32+
33+
export const SubscriptionEndingReminderWithoutChurn = createStory(
34+
{
35+
showChurn: false,
36+
},
37+
'Without Churn Prompt'
38+
);
39+
40+
export const SubscriptionEndingReminderWithChurn = createStory(
41+
{
42+
showChurn: true,
43+
},
44+
'With Churn Prompt'
45+
);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
export type TemplateData = {
6+
productName: string;
7+
serviceLastActiveDateOnly: string;
8+
accountSettingsUrl: string;
9+
subscriptionSupportUrlWithUtm: string;
10+
churnTermsUrlWithUtm: string;
11+
productIconURLNew: string;
12+
ctaButtonLabel?: string;
13+
ctaButtonUrlWithUtm?: string;
14+
showChurn: boolean;
15+
};
16+
17+
export const template = 'subscriptionEndingReminder';
18+
export const version = 1;
19+
export const layout = 'subscription';
20+
export const includes = {
21+
subject: {
22+
id: 'subscriptionEndingReminder-subject',
23+
message: 'Your <%- productName %> subscription will expire soon',
24+
},
25+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
subscriptionEndingReminder-subject = "Your <%- productName %> subscription will expire soon"
2+
3+
subscriptionEndingReminder-title = "Your <%- productName %> subscription will expire soon"
4+
5+
subscriptionEndingReminder-content-line1-plaintext = "Your access to <%- productName %> will end on <%- serviceLastActiveDateOnly %>."
6+
7+
subscriptionEndingReminder-content-line2-plaintext = "If you’d like to continue using <%- productName %>, you can reactivate your subscription in Account Settings before <%- serviceLastActiveDateOnly %>. If you need assistance, contact our Support Team."
8+
9+
subscriptionEndingReminder-content-closing = "Thanks for being a valued subscriber!"
10+
11+
<% if (locals.showChurn) { %>
12+
subscriptionEndingReminder-churn-title = "Want to keep access?"
13+
14+
<%- ctaButtonLabel %>: <%- ctaButtonUrlWithUtm %>
15+
16+
subscriptionEndingReminder-churn-terms-plaintext = "Limited terms and restrictions apply: <%- churnTermsUrlWithUtm %>"
17+
<% } %>
18+
19+
subscriptionEndingReminder-content-support-plaintext = "Contact our Support Team: <%- subscriptionSupportUrlWithUtm %>"

libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/en.ftl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ subscriptionRenewalReminder-title = Your subscription will be renewed soon
66
# $productName (String) - The name of the subscribed product, e.g. Mozilla VPN
77
subscriptionRenewalReminder-content-greeting = Dear { $productName } customer,
88
# Variables
9+
# $reminderLength (String) - The number of days until the current subscription is set to automatically renew, e.g. 14
10+
subscriptionRenewalReminder-content-intro = Your current subscription is set to automatically renew in { $reminderLength } days.
11+
subscriptionRenewalReminder-content-discount-ending = Because a previous discount has ended, your subscription will renew at the standard price.
12+
# Variables
913
# $invoiceTotal (String) - The amount of the subscription invoice, including currency, e.g. $10.00
1014
# $planIntervalCount (String) - The interval count of subscription plan, e.g. 2
1115
# $planInterval (String) - The interval of time of the subscription plan, e.g. week
12-
# $reminderLength (String) - The number of days until the current subscription is set to automatically renew, e.g. 14
13-
subscriptionRenewalReminder-content-current = Your current subscription is set to automatically renew in { $reminderLength } days. At that time, { -brand-mozilla } will renew your { $planIntervalCount } { $planInterval } subscription and a charge of { $invoiceTotal } will be applied to the payment method on your account.
16+
subscriptionRenewalReminder-content-charge = At that time, { -brand-mozilla } will renew your { $planIntervalCount } { $planInterval } subscription and a charge of { $invoiceTotal } will be applied to the payment method on your account.
1417
subscriptionRenewalReminder-content-closing = Sincerely,
1518
# Variables
1619
# $productName (String) - The name of the subscribed product, e.g. Mozilla VPN

libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.mjml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,22 @@
1313
</mj-text>
1414

1515
<mj-text css-class="text-body">
16-
<span data-l10n-id="subscriptionRenewalReminder-content-current" data-l10n-args="<%= JSON.stringify({invoiceTotal, planInterval, planIntervalCount, reminderLength}) %>">
17-
Your current subscription is set to automatically renew in <%- reminderLength %> days. At that time, Mozilla will renew your <%- planIntervalCount%> <%- planInterval%> subscription and a charge of <%- invoiceTotal%> will be applied to the payment method on your account.
16+
<span data-l10n-id="subscriptionRenewalReminder-content-intro" data-l10n-args="<%= JSON.stringify({reminderLength}) %>">
17+
Your current subscription is set to automatically renew in <%- reminderLength %> days.
18+
</span>
19+
</mj-text>
20+
21+
<% if (locals.hadDiscount) { %>
22+
<mj-text css-class="text-body">
23+
<span data-l10n-id="subscriptionRenewalReminder-content-discount-ending">
24+
Because a previous discount has ended, your subscription will renew at the standard price.
25+
</span>
26+
</mj-text>
27+
<% } %>
28+
29+
<mj-text css-class="text-body">
30+
<span data-l10n-id="subscriptionRenewalReminder-content-charge" data-l10n-args="<%= JSON.stringify({invoiceTotal, planInterval, planIntervalCount}) %>">
31+
At that time, Mozilla will renew your <%- planIntervalCount%> <%- planInterval%> subscription and a charge of <%- invoiceTotal%> will be applied to the payment method on your account.
1832
</span>
1933
</mj-text>
2034

libs/accounts/email-renderer/src/templates/subscriptionRenewalReminder/index.stories.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,51 @@ export default {
1313
const data = {
1414
productName: 'Firefox Fortress',
1515
invoiceTotal: '$20.00',
16-
planInterval: 'week',
17-
planIntervalCount: '2',
18-
reminderLength: '14',
16+
planInterval: 'month',
17+
planIntervalCount: '1',
18+
reminderLength: '7',
1919
subscriptionSupportUrl: 'http://localhost:3030/support',
2020
updateBillingUrl: 'http://localhost:3030/subscriptions',
21+
hadDiscount: false,
2122
};
2223

2324
const createStory = subplatStoryWithProps<TemplateData>(
2425
'subscriptionRenewalReminder',
25-
'Sent to remind a user of an upcoming automatic subscription renewal X days out from charge (X being what is set in the Stripe dashboard)',
26+
'Sent to remind a user of an upcoming automatic subscription renewal X days out from charge',
2627
data,
2728
includes
2829
);
2930

30-
export const SubscriptionReactivation = createStory();
31+
export const MonthlyPlanNoDiscount = createStory(
32+
{},
33+
'Monthly Plan - No Discount'
34+
);
35+
36+
export const MonthlyPlanDiscountEnding = createStory(
37+
{
38+
hadDiscount: true,
39+
},
40+
'Monthly Plan - Discount Ending'
41+
);
42+
43+
export const YearlyPlanNoDiscount = createStory(
44+
{
45+
planInterval: 'year',
46+
planIntervalCount: '1',
47+
reminderLength: '15',
48+
invoiceTotal: '$199.99',
49+
hadDiscount: false,
50+
},
51+
'Yearly Plan - No Discount'
52+
);
53+
54+
export const YearlyPlanDiscountEnding = createStory(
55+
{
56+
planInterval: 'year',
57+
planIntervalCount: '1',
58+
reminderLength: '15',
59+
invoiceTotal: '$199.99',
60+
hadDiscount: true,
61+
},
62+
'Yearly Plan - Discount Ending'
63+
);

0 commit comments

Comments
 (0)