Skip to content

Commit d51d098

Browse files
authored
Email template revamp (#847)
* ➕ Basic email template revamp * 🔨 Fix email language * ➕ Add missing email content * 💚 Improve file size and appearance * 🔨 Fix test results
1 parent 660e5a8 commit d51d098

File tree

12 files changed

+182
-108
lines changed

12 files changed

+182
-108
lines changed

backend/src/appointment/controller/mailer.py

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,18 @@ def text(self):
7979
return self.body_plain if self.body_plain != '' else escape(self.body_html)
8080

8181
def _attachments(self):
82-
"""provide all attachments as list"""
83-
return self.attachments
82+
"""provide all attachments as list, add tbpro logo to every mail"""
83+
with open('src/appointment/templates/assets/img/tbpro_logo.png', 'rb') as fh:
84+
tbpro_logo = fh.read()
85+
86+
return [
87+
Attachment(
88+
mime=('image', 'png'),
89+
filename='tbpro_logo.png',
90+
data=tbpro_logo,
91+
),
92+
*self.attachments,
93+
]
8494

8595
def build(self):
8696
"""build email header, body and attachments"""
@@ -204,6 +214,7 @@ def _attachments(self):
204214
clock_icon = fh.read()
205215

206216
return [
217+
*super()._attachments(),
207218
Attachment(
208219
mime=('image', 'png'),
209220
filename='calendar.png',
@@ -214,7 +225,6 @@ def _attachments(self):
214225
filename='clock.png',
215226
data=clock_icon,
216227
),
217-
*self.attachments,
218228
]
219229

220230

@@ -239,9 +249,10 @@ def html(self):
239249
timezone=self.timezone,
240250
day=self.day,
241251
duration=self.duration,
242-
# Icon cids
243-
calendar_icon_cid=self._attachments()[0].filename,
244-
clock_icon_cid=self._attachments()[1].filename,
252+
# Image cids
253+
tbpro_logo_cid=self._attachments()[0].filename,
254+
calendar_icon_cid=self._attachments()[1].filename,
255+
clock_icon_cid=self._attachments()[2].filename,
245256
)
246257

247258

@@ -256,7 +267,10 @@ def __init__(self, appointment_title, *args, **kwargs):
256267
self.appointment_title = appointment_title
257268

258269
def html(self):
259-
return get_template('errors/zoom_invite_failed.jinja2').render(title=self.appointment_title)
270+
return get_template('errors/zoom_invite_failed.jinja2').render(
271+
title=self.appointment_title,
272+
tbpro_logo_cid=self._attachments()[0].filename,
273+
)
260274

261275
def text(self):
262276
return l10n('zoom-invite-failed-plain', {'title': self.appointment_title})
@@ -301,9 +315,10 @@ def html(self):
301315
deny=self.denyUrl,
302316
schedule_name=self.schedule_name,
303317
lang=self.lang,
304-
# Icon cids
305-
calendar_icon_cid=self._attachments()[0].filename,
306-
clock_icon_cid=self._attachments()[1].filename,
318+
# Image cids
319+
tbpro_logo_cid=self._attachments()[0].filename,
320+
calendar_icon_cid=self._attachments()[1].filename,
321+
clock_icon_cid=self._attachments()[2].filename,
307322
)
308323

309324

@@ -323,7 +338,11 @@ def text(self):
323338
return l10n('reject-mail-plain', {'owner_name': self.owner_name, 'date': self.date})
324339

325340
def html(self):
326-
return get_template('rejected.jinja2').render(owner_name=self.owner_name, date=self.date)
341+
return get_template('rejected.jinja2').render(
342+
owner_name=self.owner_name,
343+
date=self.date,
344+
tbpro_logo_cid=self._attachments()[0].filename,
345+
)
327346

328347

329348
class PendingRequestMail(Mailer):
@@ -340,7 +359,11 @@ def text(self):
340359
return l10n('pending-mail-plain', {'owner_name': self.owner_name, 'date': self.date})
341360

342361
def html(self):
343-
return get_template('pending.jinja2').render(owner_name=self.owner_name, date=self.date)
362+
return get_template('pending.jinja2').render(
363+
owner_name=self.owner_name,
364+
date=self.date,
365+
tbpro_logo_cid=self._attachments()[0].filename,
366+
)
344367

345368

346369
class NewBookingMail(BaseBookingMail):
@@ -382,9 +405,10 @@ def html(self):
382405
day=self.day,
383406
duration=self.duration,
384407
schedule_name=self.schedule_name,
385-
# Icon cids
386-
calendar_icon_cid=self._attachments()[0].filename,
387-
clock_icon_cid=self._attachments()[1].filename,
408+
# Image cids
409+
tbpro_logo_cid=self._attachments()[0].filename,
410+
calendar_icon_cid=self._attachments()[1].filename,
411+
clock_icon_cid=self._attachments()[2].filename,
388412
)
389413

390414

@@ -424,12 +448,15 @@ def html(self):
424448
requestee_email=self.requestee_email,
425449
topic=self.topic,
426450
details=self.details,
451+
tbpro_logo_cid=self._attachments()[0].filename,
427452
)
428453

429454

430455
class InviteAccountMail(Mailer):
431-
def __init__(self, *args, **kwargs):
432-
default_kwargs = {'subject': l10n('new-account-mail-subject')}
456+
def __init__(self, date, *args, **kwargs):
457+
self.date = date
458+
lang = kwargs['lang'] if 'lang' in kwargs else None
459+
default_kwargs = {'subject': l10n('new-account-mail-subject', lang=lang)}
433460
super(InviteAccountMail, self).__init__(*args, **default_kwargs, **kwargs)
434461

435462
def text(self):
@@ -441,7 +468,12 @@ def text(self):
441468
)
442469

443470
def html(self):
444-
return get_template('new_account.jinja2').render(homepage_url=os.getenv('FRONTEND_URL'))
471+
return get_template('new_account.jinja2').render(
472+
date=self.date,
473+
lang=self.lang,
474+
homepage_url=os.getenv('FRONTEND_URL'),
475+
tbpro_logo_cid=self._attachments()[0].filename,
476+
)
445477

446478

447479
class ConfirmYourEmailMail(Mailer):
@@ -463,5 +495,6 @@ def text(self):
463495
def html(self):
464496
return get_template('confirm_email.jinja2').render(
465497
confirm_email_url=self.confirm_url,
466-
decline_email_url=self.decline_url
498+
decline_email_url=self.decline_url,
499+
tbpro_logo_cid=self._attachments()[0].filename,
467500
)

backend/src/appointment/l10n/de/email.ftl

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@
66
-brand-slogan = Weniger planen, mehr schaffen.
77
-brand-sign-up-with-url = Registrieren auf appointment.day
88
-brand-sign-up-with-no-url = Registrieren auf
9-
-brand-footer = Diese Nachricht wurde gesendet von:
10-
{-brand-name}
11-
{-brand-slogan} {-brand-sign-up-with-url}
9+
-brand-footer = Du erhälst diese E-Mail, weil Du dich auf unserer Website für Thunderbird Appointment Beta angemeldet hast.
10+
11+
Copyright © 2025 MZLA Technologies. All rights reserved.
12+
MZLA Technologies 149 New Montgomery St., 4th Floor San Francisco, CA 94501 USA
1213
1314
mail-brand-contact-form = Kontaktformular
14-
mail-brand-support-hint = Du hast Fragen? Wir helfen gern. Nutze unser { $contact_form_link }, oder antworte einfach auf diese E-Mail für Support.
15+
mail-brand-support-hint = Du hast Fragen? Wir helfen gern. Antworte einfach auf diese E-Mail für Support.
1516
mail-brand-reply-hint = Du hast Fragen? Wir helfen gern, Du erreichst uns über unser { $contact_form_link }.
1617
mail-brand-reply-hint-attendee-info = Du möchtest { $name } kontaktieren? Antworte einfach auf diese E-Mail.
1718
18-
mail-brand-footer = Diese Nachricht wurde gesendet von:
19-
{-brand-name}
20-
{-brand-slogan} {-brand-sign-up-with-no-url}
19+
mail-brand-footer = Du erhälst diese E-Mail, weil Du dich auf unserer Website für Thunderbird Appointment Beta angemeldet hast.
20+
21+
Copyright © 2025 MZLA Technologies. All rights reserved.
22+
MZLA Technologies 149 New Montgomery St., 4th Floor San Francisco, CA 94501 USA
2123
2224
## Invitation
2325

@@ -164,15 +166,20 @@ support-mail-plain = { $requestee_name } ({ $requestee_email }) hat folgende Sup
164166
165167
## New/Invited Account Email
166168
new-account-mail-subject = Du wurdest zu Thunderbird Appointment eingeladen
167-
new-account-mail-action = Weiter zu Thunderbird Appointment
168-
new-account-mail-html-heading = Du wurdest zu Thunderbird Appointment eingeladen.
169-
new-account-mail-html-body = Logge dich mit dieser E-Mail-Adresse ein um fortzufahren.
169+
new-account-mail-action = Registrieren
170+
new-account-mail-html-heading = Vielen Dank, dass Du dich als einer der ersten Tester von Thunderbird Appointment Beta registrierst.
171+
Wir freuen uns, dass Du dabei bist!
172+
new-account-mail-html-body = Deine E-Mail-Adresse wurde am { $date } auf unsere Beta-Warteliste gesetzt.
173+
new-account-mail-html-body-2 = Melde Dich an und beginne mit der Nutzung von Appointment, indem Du auf die Schaltfläche unten klickst oder diesen Link in Deinen Browser einfügst:
174+
170175
# Variables:
171176
# $homepage_url (String) - URL to Thunderbird Appointment
172-
new-account-mail-plain = Du wurdest zu Thunderbird Appointment eingeladen.
173-
Logge dich mit dieser E-Mail-Adresse ein um fortzufahren.
174-
{ $homepage_url }
175-
{-brand-footer}
177+
new-account-mail-plain = Vielen Dank, dass Du dich als einer der ersten Tester von Thunderbird Appointment Beta registrierst.
178+
Wir freuen uns, dass Du dabei bist!
179+
Deine E-Mail-Adresse wurde am { $date } auf unsere Beta-Warteliste gesetzt.
180+
Melde Dich an und beginne mit der Nutzung von Appointment, indem Du auf die Schaltfläche unten klickst oder diesen Link in Deinen Browser einfügst:
181+
{ $homepage_url }
182+
{-brand-footer}
176183
177184
## Confirm Email for waiting list
178185
confirm-email-mail-subject = Bestätige deine E-Mail-Adresse um der Warteliste beizutreten!

backend/src/appointment/l10n/en/email.ftl

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@
66
-brand-slogan = Plan less, do more.
77
-brand-sign-up-with-url = Sign up on appointment.day
88
-brand-sign-up-with-no-url = Sign up on
9-
-brand-footer = This message was sent from:
10-
{-brand-name}
11-
{-brand-slogan} {-brand-sign-up-with-url}
9+
-brand-footer = You are receiving this email because you signed up on our website for the Thunderbird Appointment Beta.
10+
11+
Copyright © 2025 MZLA Technologies. All rights reserved.
12+
MZLA Technologies 149 New Montgomery St., 4th Floor San Francisco, CA 94501 USA
1213
1314
mail-brand-contact-form = contact form
14-
mail-brand-support-hint = Got questions? We're here to help. Fill out our { $contact_form_link }, or simply reply to this email for support.
15+
mail-brand-support-hint = Got questions? We're here to help. Simply reply to this email for support.
1516
mail-brand-reply-hint = Got questions? We're here to help, you can reach us via our { $contact_form_link }.
1617
mail-brand-reply-hint-attendee-info = Need to get in touch with { $name }? Simply reply to this email.
1718
18-
mail-brand-footer = This message was sent from:
19-
{-brand-name}
20-
{-brand-slogan} {-brand-sign-up-with-no-url}
19+
mail-brand-footer = You are receiving this email because you signed up on our website for the Thunderbird Appointment Beta.
20+
21+
Copyright © 2025 MZLA Technologies. All rights reserved.
22+
MZLA Technologies 149 New Montgomery St., 4th Floor San Francisco, CA 94501 USA
23+
mail-brand-footer-privacy = Privacy
24+
mail-brand-footer-legal = Legal
2125
2226
## Invitation
2327

@@ -164,15 +168,20 @@ support-mail-plain = { $requestee_name } ({ $requestee_email }) sent the followi
164168
165169
## New/Invited Account Email
166170
new-account-mail-subject = You've been invited to Thunderbird Appointment
167-
new-account-mail-action = Log In
168-
new-account-mail-html-heading = You've been invited to Thunderbird Appointment.
169-
new-account-mail-html-body = Login with this email address to continue.
171+
new-account-mail-action = Sign Up
172+
new-account-mail-html-heading = Thank you for signing up to be one of the earliest testers of Thunderbird Appointment Beta.
173+
We’re excited to have you on board!
174+
new-account-mail-html-body = You added your email to our beta wait-list on { $date }.
175+
new-account-mail-html-body-2 = Sign in and start using Appointment by clicking the button below or pasting this link into your browser:
176+
170177
# Variables:
171178
# $homepage_url (String) - URL to Thunderbird Appointment
172-
new-account-mail-plain = You've been invited to Thunderbird Appointment.
173-
Login with this email address to continue.
174-
{ $homepage_url }
175-
{-brand-footer}
179+
new-account-mail-plain = Thank you for signing up to be one of the earliest testers of Thunderbird Appointment Beta.
180+
We’re excited to have you on board!
181+
You added your email to our beta wait-list on { $date }.
182+
Sign in and start using Appointment by clicking the button below or pasting this link into your browser:
183+
{ $homepage_url }
184+
{-brand-footer}
176185
177186
## Confirm Email for waiting list
178187
confirm-email-mail-subject = Confirm your email to join the waiting list!

backend/src/appointment/routes/invite.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,6 @@ def send_invite_email(
7474
db.add(invite_code)
7575
db.commit()
7676

77-
background_tasks.add_task(send_invite_account_email, to=email)
77+
background_tasks.add_task(send_invite_account_email, to=email, lang=subscriber.language)
7878

7979
return invite_code

backend/src/appointment/routes/waiting_list.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def act_on_waiting_list(data: schemas.TokenForWaitingList, db: Session = Depends
100100
}
101101

102102

103-
""" ADMIN ROUTES
103+
""" ADMIN ROUTES
104104
These require get_admin_subscriber!
105105
"""
106106

@@ -172,7 +172,12 @@ def invite_waiting_list_users(
172172
db.add(invite_code)
173173
db.commit()
174174

175-
background_tasks.add_task(send_invite_account_email, to=subscriber.email)
175+
background_tasks.add_task(
176+
send_invite_account_email,
177+
date=waiting_list_user.time_created,
178+
to=subscriber.email,
179+
lang=subscriber.language
180+
)
176181
accepted.append(waiting_list_user.id)
177182

178183
if posthog:

backend/src/appointment/tasks/emails.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ def send_support_email(requestee_name, requestee_email, topic, details):
113113
sentry_sdk.capture_exception(e)
114114

115115

116-
def send_invite_account_email(to):
116+
def send_invite_account_email(date, to, lang):
117117
try:
118-
mail = InviteAccountMail(to=to)
118+
mail = InviteAccountMail(date=date, to=to, lang=lang)
119119
mail.send()
120120
except Exception as e:
121121
if os.getenv('APP_ENV') == APP_ENV_DEV:
15.4 KB
Loading
Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
{# Helper vars! These are accessible from any template that extends base #}
22
{% set colour_surface_base = '#FEFFFF' %}
33
{% set colour_surface_raised = '#FEFFFF' %}
4-
{% set colour_text_base = '#18181B' %}
4+
{% set colour_text_base = '#1A202C' %}
55
{% set colour_text_muted = '#737584' %}
66
{% set colour_icon_secondary = '#4C4D58' %}
77
{% set colour_neutral = '#FEFFFF' %}
88
{% set colour_tbpro_apmt_primary = '#008080' %}
99
{% set colour_tbpro_apmt_primary_hover = '#066769' %}
1010
{% set colour_tbpro_apmt_secondary = '#81D4B5' %}
11+
{% set colour_tbpro_apmt_soft = '#F3F9FC' %}
12+
{% set logo_image = '<img style="width: 256px;" alt="" src="cid:%(cid)s" />'|format(cid=tbpro_logo_cid) %}
13+
1114
<html lang="{{ l10n('locale', lang=lang if lang else None) }}" style="
1215
font-size: 13px;
1316
color: {{ colour_text_base }};
@@ -16,6 +19,7 @@
1619
margin: 24px 0;
1720
">
1821
<body>
22+
<div style="text-align: center; margin-bottom: 16px;">{{ logo_image|safe }}</div>
1923
{% if self.introduction()|trim %}
2024
<div style="
2125
margin-left: auto;
@@ -27,14 +31,15 @@
2731
{% endif %}
2832
{% if self.information()|trim %}
2933
<div style="
34+
box-sizing: border-box;
3035
margin-left: auto;
3136
margin-right: auto;
32-
margin-bottom: 24px;
33-
max-width: 310px;
37+
margin-bottom: 16px;
38+
max-width: 576px;
3439
border: 1px solid {{ colour_tbpro_apmt_primary }};
35-
border-radius: 6px;
36-
padding: 12px;
37-
background-color: {{ colour_surface_raised }}
40+
border-radius: 12px;
41+
padding: 16px 46px;
42+
background-color: {{ colour_tbpro_apmt_soft }}
3843
">
3944
{% block information %}{% endblock %}
4045
</div>
@@ -45,9 +50,8 @@
4550
</div>
4651
{% endif %}
4752
{% if show_contact_form_support_hint %}
48-
{% set link = '<a href="%(url)s">%(label)s</a>'|format(url=homepage_url + '/contact', label=l10n('mail-brand-contact-form', lang=lang if lang else None)) %}
4953
<div style="text-align: center; margin-left: auto; margin-right: auto; margin-bottom: 24px; padding: 12px; max-width: 310px;">
50-
{{ l10n('mail-brand-support-hint', {'contact_form_link': link}, lang if lang else None)|safe }}
54+
{{ l10n('mail-brand-support-hint', lang=lang if lang else None)|safe }}
5155
</div>
5256
{% endif %}
5357
{% if show_contact_form_reply_hint %}
@@ -58,15 +62,6 @@
5862
{{ l10n('mail-brand-reply-hint', {'contact_form_link': link}, lang if lang else None)|safe }}
5963
</div>
6064
{% endif %}
61-
<div style="
62-
display: block;
63-
width: 100%;
64-
padding-bottom: 1px;
65-
border-radius: unset;
66-
margin-top: 12px;
67-
margin-bottom: 12px;
68-
background: linear-gradient(90deg, rgba(21, 66, 124, 0) 20.5%, rgba(21, 66, 124, 0.2) 50%, rgba(21, 66, 124, 0) 79.5%);
69-
"></div>
7065
{% include './includes/footer.jinja2' %}
7166
</body>
7267
</html>

0 commit comments

Comments
 (0)