From 1c7cdc42f28c74fc3d63d75e59bfc863eee4e715 Mon Sep 17 00:00:00 2001 From: Deeman Date: Wed, 25 Feb 2026 12:05:20 +0100 Subject: [PATCH] =?UTF-8?q?feat(emails):=20subtask=203=20=E2=80=94=204=20m?= =?UTF-8?q?edium=20templates=20(quote=5Fverification,=20waitlist,=20lead?= =?UTF-8?q?=5Fmatched)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add quote_verification.html (with optional project recap card) - Add waitlist_supplier.html, waitlist_general.html - Add lead_matched.html (with next-steps section + tip box) - Refactor 3 handlers in worker.py: send_quote_verification, send_waitlist_confirmation, send_lead_matched_notification Co-Authored-By: Claude Sonnet 4.6 --- .../templates/emails/lead_matched.html | 17 ++++ .../templates/emails/quote_verification.html | 24 +++++ .../templates/emails/waitlist_general.html | 14 +++ .../templates/emails/waitlist_supplier.html | 18 ++++ web/src/padelnomics/worker.py | 93 ++++++------------- 5 files changed, 100 insertions(+), 66 deletions(-) create mode 100644 web/src/padelnomics/templates/emails/lead_matched.html create mode 100644 web/src/padelnomics/templates/emails/quote_verification.html create mode 100644 web/src/padelnomics/templates/emails/waitlist_general.html create mode 100644 web/src/padelnomics/templates/emails/waitlist_supplier.html diff --git a/web/src/padelnomics/templates/emails/lead_matched.html b/web/src/padelnomics/templates/emails/lead_matched.html new file mode 100644 index 0000000..ec934b5 --- /dev/null +++ b/web/src/padelnomics/templates/emails/lead_matched.html @@ -0,0 +1,17 @@ +{% extends "emails/_base.html" %} +{% from "emails/_macros.html" import email_button %} + +{% block body %} +

{{ t.email_lead_matched_heading }}

+
+

{{ t.email_lead_matched_greeting | tformat(first_name=first_name) }}

+

{{ t.email_lead_matched_body }}

+

{{ t.email_lead_matched_context | tformat(facility_type=facility_type, court_count=court_count, country=country) }}

+ +

{{ t.email_lead_matched_next_heading }}

+

{{ t.email_lead_matched_next_body }}

+

{{ t.email_lead_matched_tip }}

+ +{{ email_button(base_url ~ "/dashboard", t.email_lead_matched_btn) }} +

{{ t.email_lead_matched_note }}

+{% endblock %} diff --git a/web/src/padelnomics/templates/emails/quote_verification.html b/web/src/padelnomics/templates/emails/quote_verification.html new file mode 100644 index 0000000..c48e7f0 --- /dev/null +++ b/web/src/padelnomics/templates/emails/quote_verification.html @@ -0,0 +1,24 @@ +{% extends "emails/_base.html" %} +{% from "emails/_macros.html" import email_button %} + +{% block body %} +

{{ t.email_quote_verify_heading }}

+
+

{{ t.email_quote_verify_greeting | tformat(first_name=first_name) }}

+

{{ t.email_quote_verify_body | tformat(app_name=app_name) }}

+ +{%- if recap_parts %} + + +
+ {{ t.email_quote_verify_project_label }} {{ recap_parts | join(" · ") | safe }} +
+{%- endif %} + +

{{ t.email_quote_verify_urgency }}

+{{ email_button(link, t.email_quote_verify_btn) }} +

{{ t.email_quote_verify_expires }}

+

{{ t.email_quote_verify_fallback }}

+

{{ link }}

+

{{ t.email_quote_verify_ignore }}

+{% endblock %} diff --git a/web/src/padelnomics/templates/emails/waitlist_general.html b/web/src/padelnomics/templates/emails/waitlist_general.html new file mode 100644 index 0000000..6546027 --- /dev/null +++ b/web/src/padelnomics/templates/emails/waitlist_general.html @@ -0,0 +1,14 @@ +{% extends "emails/_base.html" %} + +{% block body %} +

{{ t.email_waitlist_general_heading }}

+
+

{{ t.email_waitlist_general_body }}

+

{{ t.email_waitlist_general_perks_intro }}

+ +

{{ t.email_waitlist_general_outro }}

+{% endblock %} diff --git a/web/src/padelnomics/templates/emails/waitlist_supplier.html b/web/src/padelnomics/templates/emails/waitlist_supplier.html new file mode 100644 index 0000000..689868e --- /dev/null +++ b/web/src/padelnomics/templates/emails/waitlist_supplier.html @@ -0,0 +1,18 @@ +{% extends "emails/_base.html" %} + +{% block body %} +

{{ t.email_waitlist_supplier_heading }}

+
+

{{ t.email_waitlist_supplier_body | tformat(plan_name=plan_name) }}

+

{{ t.email_waitlist_supplier_perks_intro }}

+ +

{{ t.email_waitlist_supplier_meanwhile }}

+ +{% endblock %} diff --git a/web/src/padelnomics/worker.py b/web/src/padelnomics/worker.py index fe872c1..f9db18e 100644 --- a/web/src/padelnomics/worker.py +++ b/web/src/padelnomics/worker.py @@ -265,8 +265,6 @@ async def handle_send_quote_verification(payload: dict) -> None: facility_type = payload.get("facility_type", "") country = payload.get("country", "") - # Project recap card - project_card = "" recap_parts = [] if court_count: recap_parts.append(f"{court_count} courts") @@ -274,34 +272,22 @@ async def handle_send_quote_verification(payload: dict) -> None: recap_parts.append(facility_type) if country: recap_parts.append(country) - if recap_parts: - project_card = ( - f'' - f'
' - f'{_t("email_quote_verify_project_label", lang)} {" · ".join(recap_parts)}' - f'
' - ) preheader = _t("email_quote_verify_preheader_courts", lang, court_count=court_count) if court_count else _t("email_quote_verify_preheader", lang) - body = ( - f'

{_t("email_quote_verify_heading", lang)}

' - f'
' - f'

{_t("email_quote_verify_greeting", lang, first_name=first_name)}

' - f'

{_t("email_quote_verify_body", lang, app_name=config.APP_NAME)}

' - f'{project_card}' - f'

{_t("email_quote_verify_urgency", lang)}

' - f'{_email_button(link, _t("email_quote_verify_btn", lang))}' - f'

{_t("email_quote_verify_expires", lang)}

' - f'

{_t("email_quote_verify_fallback", lang)}

' - f'

{link}

' - f'

{_t("email_quote_verify_ignore", lang)}

' + html = render_email_template( + "emails/quote_verification.html", + lang=lang, + link=link, + first_name=first_name, + recap_parts=recap_parts, + preheader=preheader, ) await send_email( to=payload["email"], subject=_t("email_quote_verify_subject", lang), - html=_email_wrap(body, lang, preheader=preheader), + html=html, from_addr=EMAIL_ADDRESSES["transactional"], email_type="quote_verification", ) @@ -340,43 +326,24 @@ async def handle_send_waitlist_confirmation(payload: dict) -> None: if intent.startswith("supplier_"): plan_name = intent.replace("supplier_", "").title() subject = _t("email_waitlist_supplier_subject", lang, plan_name=plan_name) - preheader = _t("email_waitlist_supplier_preheader", lang) - body = ( - f'

{_t("email_waitlist_supplier_heading", lang)}

' - f'
' - f'

{_t("email_waitlist_supplier_body", lang, plan_name=plan_name)}

' - f'

{_t("email_waitlist_supplier_perks_intro", lang)}

' - f'' - f'

{_t("email_waitlist_supplier_meanwhile", lang)}

' - f'' + html = render_email_template( + "emails/waitlist_supplier.html", + lang=lang, + plan_name=plan_name, + preheader=_t("email_waitlist_supplier_preheader", lang), ) else: subject = _t("email_waitlist_general_subject", lang) - preheader = _t("email_waitlist_general_preheader", lang) - body = ( - f'

{_t("email_waitlist_general_heading", lang)}

' - f'
' - f'

{_t("email_waitlist_general_body", lang)}

' - f'

{_t("email_waitlist_general_perks_intro", lang)}

' - f'' - f'

{_t("email_waitlist_general_outro", lang)}

' + html = render_email_template( + "emails/waitlist_general.html", + lang=lang, + preheader=_t("email_waitlist_general_preheader", lang), ) await send_email( to=email, subject=subject, - html=_email_wrap(body, lang, preheader=preheader), + html=html, from_addr=EMAIL_ADDRESSES["transactional"], email_type="waitlist", ) @@ -524,26 +491,20 @@ async def handle_send_lead_matched_notification(payload: dict) -> None: first_name = (lead["contact_name"] or "").split()[0] if lead.get("contact_name") else "there" - body = ( - f'

{_t("email_lead_matched_heading", lang)}

' - f'
' - f'

{_t("email_lead_matched_greeting", lang, first_name=first_name)}

' - f'

{_t("email_lead_matched_body", lang)}

' - f'

{_t("email_lead_matched_context", lang, facility_type=lead["facility_type"] or "padel", court_count=lead["court_count"] or "?", country=lead["country"] or "your area")}

' - # What happens next - f'

{_t("email_lead_matched_next_heading", lang)}

' - f'

{_t("email_lead_matched_next_body", lang)}

' - f'

' - f'{_t("email_lead_matched_tip", lang)}

' - f'{_email_button(f"{config.BASE_URL}/dashboard", _t("email_lead_matched_btn", lang))}' - f'

{_t("email_lead_matched_note", lang)}

' + html = render_email_template( + "emails/lead_matched.html", + lang=lang, + first_name=first_name, + facility_type=lead["facility_type"] or "padel", + court_count=lead["court_count"] or "?", + country=lead["country"] or "your area", + preheader=_t("email_lead_matched_preheader", lang), ) await send_email( to=lead["contact_email"], subject=_t("email_lead_matched_subject", lang, first_name=first_name), - html=_email_wrap(body, lang, preheader=_t("email_lead_matched_preheader", lang)), + html=html, from_addr=EMAIL_ADDRESSES["leads"], email_type="lead_matched", )