feat: email design & copy upgrade for all 9 transactional emails
Redesigned _email_wrap(): lowercase wordmark header matching website, 3px blue accent border, preheader text support, HR separators. _email_button() now full-width block for mobile tap targets. Rewrote copy: improved subject lines, urgency cues, quick-start links in welcome, styled project recap in quote verify, heat badges on lead forward, "what happens next" in lead matched, secondary CTAs. ~30 new/updated translation keys in both EN and DE. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
translated footer; ~70 new translation keys (EN + DE); all task payloads now
|
translated footer; ~70 new translation keys (EN + DE); all task payloads now
|
||||||
carry `lang` from request context at enqueue time; payloads without `lang`
|
carry `lang` from request context at enqueue time; payloads without `lang`
|
||||||
gracefully default to English
|
gracefully default to English
|
||||||
|
- **Email design & copy upgrade** — redesigned `_email_wrap()`: replaced monogram
|
||||||
|
header with lowercase wordmark matching website, added 3px blue accent border,
|
||||||
|
preheader text support (hidden preview in email clients), HR separators between
|
||||||
|
heading and body; `_email_button()` now full-width block for mobile tap targets;
|
||||||
|
rewrote copy for all 9 emails with improved subject lines, urgency cues,
|
||||||
|
quick-start links in welcome email, styled project recap cards in quote
|
||||||
|
verification, heat badges on lead forward emails, "what happens next" section
|
||||||
|
in lead matched notifications, and secondary CTAs; ~30 new/updated translation
|
||||||
|
keys in both EN and DE
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- **Resend audiences restructured** — replaced dynamic `waitlist-{blueprint}`
|
- **Resend audiences restructured** — replaced dynamic `waitlist-{blueprint}`
|
||||||
|
|||||||
@@ -1539,44 +1539,60 @@
|
|||||||
"bp_lbl_disclaimer": "<strong>Haftungsausschluss:</strong> Dieser Businessplan wurde auf Basis benutzerdefinierter Annahmen mit dem Padelnomics-Finanzmodell erstellt. Alle Prognosen sind Sch\u00e4tzungen und stellen keine Finanzberatung dar. Die tats\u00e4chlichen Ergebnisse k\u00f6nnen je nach Marktbedingungen, Umsetzung und anderen Faktoren erheblich abweichen. Konsultiere Finanzberater, bevor du Investitionsentscheidungen triffst. \u00a9 Padelnomics \u2014 padelnomics.io",
|
"bp_lbl_disclaimer": "<strong>Haftungsausschluss:</strong> Dieser Businessplan wurde auf Basis benutzerdefinierter Annahmen mit dem Padelnomics-Finanzmodell erstellt. Alle Prognosen sind Sch\u00e4tzungen und stellen keine Finanzberatung dar. Die tats\u00e4chlichen Ergebnisse k\u00f6nnen je nach Marktbedingungen, Umsetzung und anderen Faktoren erheblich abweichen. Konsultiere Finanzberater, bevor du Investitionsentscheidungen triffst. \u00a9 Padelnomics \u2014 padelnomics.io",
|
||||||
|
|
||||||
"email_magic_link_heading": "Bei {app_name} anmelden",
|
"email_magic_link_heading": "Bei {app_name} anmelden",
|
||||||
"email_magic_link_body": "Klicke auf den Button unten, um dich anzumelden. Dieser Link l\u00e4uft in {expiry_minutes} Minuten ab.",
|
"email_magic_link_body": "Hier ist dein Anmeldelink. Er l\u00e4uft in {expiry_minutes} Minuten ab.",
|
||||||
"email_magic_link_btn": "Anmelden",
|
"email_magic_link_btn": "Anmelden \u2192",
|
||||||
"email_magic_link_fallback": "Wenn der Button nicht funktioniert, kopiere diese URL in deinen Browser:",
|
"email_magic_link_fallback": "Wenn der Button nicht funktioniert, kopiere diese URL in deinen Browser:",
|
||||||
"email_magic_link_ignore": "Wenn du das nicht angefordert hast, kannst du diese E-Mail ignorieren.",
|
"email_magic_link_ignore": "Wenn du das nicht angefordert hast, kannst du diese E-Mail ignorieren.",
|
||||||
"email_magic_link_subject": "Bei {app_name} anmelden",
|
"email_magic_link_subject": "Dein Anmeldelink f\u00fcr {app_name}",
|
||||||
|
"email_magic_link_preheader": "Dieser Link l\u00e4uft in {expiry_minutes} Minuten ab",
|
||||||
|
|
||||||
"email_quote_verify_heading": "Best\u00e4tige deine E-Mail f\u00fcr Anbieter-Angebote",
|
"email_quote_verify_heading": "Best\u00e4tige deine E-Mail f\u00fcr Angebote",
|
||||||
"email_quote_verify_greeting": "Hallo {first_name},",
|
"email_quote_verify_greeting": "Hallo {first_name},",
|
||||||
"email_quote_verify_body": "Danke f\u00fcr deine Angebotsanfrage{project_desc}. Klicke auf den Button unten, um deine E-Mail zu best\u00e4tigen und deine Anfrage zu aktivieren. Dabei wird auch dein {app_name}-Konto erstellt, damit du dein Projekt verfolgen kannst.",
|
"email_quote_verify_body": "Danke f\u00fcr deine Angebotsanfrage. Best\u00e4tige deine E-Mail, um deine Anfrage zu aktivieren und dein {app_name}-Konto zu erstellen.",
|
||||||
"email_quote_verify_btn": "Best\u00e4tigen & Angebot aktivieren",
|
"email_quote_verify_project_label": "Dein Projekt:",
|
||||||
|
"email_quote_verify_urgency": "Verifizierte Anfragen werden von unserem Anbieternetzwerk bevorzugt behandelt.",
|
||||||
|
"email_quote_verify_btn": "Best\u00e4tigen & Aktivieren \u2192",
|
||||||
"email_quote_verify_expires": "Dieser Link l\u00e4uft in 60 Minuten ab.",
|
"email_quote_verify_expires": "Dieser Link l\u00e4uft in 60 Minuten ab.",
|
||||||
"email_quote_verify_fallback": "Wenn der Button nicht funktioniert, kopiere diese URL in deinen Browser:",
|
"email_quote_verify_fallback": "Wenn der Button nicht funktioniert, kopiere diese URL in deinen Browser:",
|
||||||
"email_quote_verify_ignore": "Wenn du das nicht angefordert hast, kannst du diese E-Mail ignorieren.",
|
"email_quote_verify_ignore": "Wenn du das nicht angefordert hast, kannst du diese E-Mail ignorieren.",
|
||||||
"email_quote_verify_subject": "Best\u00e4tige deine E-Mail f\u00fcr Anbieter-Angebote",
|
"email_quote_verify_subject": "Best\u00e4tige deine E-Mail \u2014 Anbieter sind bereit f\u00fcr Angebote",
|
||||||
|
"email_quote_verify_preheader": "Ein Klick, um deine Angebotsanfrage zu aktivieren",
|
||||||
|
"email_quote_verify_preheader_courts": "Ein Klick, um dein {court_count}-Court-Projekt zu aktivieren",
|
||||||
|
|
||||||
"email_welcome_heading": "Willkommen bei {app_name}!",
|
"email_welcome_heading": "Willkommen bei {app_name}",
|
||||||
"email_welcome_body": "Danke f\u00fcr deine Anmeldung. Du kannst jetzt mit der Planung deines Padel-Gesch\u00e4fts loslegen.",
|
"email_welcome_greeting": "Hallo {first_name},",
|
||||||
"email_welcome_btn": "Zum Dashboard",
|
"email_welcome_body": "Du hast jetzt Zugang zum Finanzplaner, Marktdaten und dem Anbieterverzeichnis \u2014 alles, was du f\u00fcr die Planung deines Padel-Gesch\u00e4fts brauchst.",
|
||||||
"email_welcome_subject": "Willkommen bei {app_name}",
|
"email_welcome_quickstart_heading": "Schnellstart:",
|
||||||
|
"email_welcome_link_planner": "Finanzplaner \u2014 modelliere deine Investition",
|
||||||
|
"email_welcome_link_markets": "Marktdaten \u2014 erkunde die Padel-Nachfrage nach Stadt",
|
||||||
|
"email_welcome_link_quotes": "Angebote einholen \u2014 verbinde dich mit verifizierten Anbietern",
|
||||||
|
"email_welcome_btn": "Jetzt planen \u2192",
|
||||||
|
"email_welcome_subject": "Du bist dabei \u2014 so f\u00e4ngst du an",
|
||||||
|
"email_welcome_preheader": "Dein Padel-Planungstoolkit ist bereit",
|
||||||
|
|
||||||
"email_waitlist_supplier_heading": "Du stehst auf der Anbieter-Warteliste",
|
"email_waitlist_supplier_heading": "Du stehst auf der Anbieter-Warteliste",
|
||||||
"email_waitlist_supplier_body": "Danke f\u00fcr dein Interesse am <strong>{plan_name}</strong>-Plan. Wir bauen die ultimative Anbieter-Plattform f\u00fcr Padel-Unternehmer.",
|
"email_waitlist_supplier_body": "Danke f\u00fcr dein Interesse am <strong>{plan_name}</strong>-Plan. Wir bauen eine Plattform, die dich mit qualifizierten Leads von Padel-Unternehmern verbindet, die aktiv Projekte planen.",
|
||||||
"email_waitlist_supplier_perks": "Du erf\u00e4hrst als Erster, wenn wir starten. Wir senden dir fr\u00fchen Zugang, exklusive Launch-Preise und Onboarding-Unterst\u00fctzung.",
|
"email_waitlist_supplier_perks_intro": "Als fr\u00fches Wartelisten-Mitglied erh\u00e4ltst du:",
|
||||||
|
"email_waitlist_supplier_perk_1": "Fr\u00fchen Zugang vor dem \u00f6ffentlichen Launch",
|
||||||
|
"email_waitlist_supplier_perk_2": "Exklusive Launch-Preise (gesichert)",
|
||||||
|
"email_waitlist_supplier_perk_3": "Pers\u00f6nliches Onboarding-Gespr\u00e4ch",
|
||||||
"email_waitlist_supplier_meanwhile": "In der Zwischenzeit erkunde unsere kostenlosen Ressourcen:",
|
"email_waitlist_supplier_meanwhile": "In der Zwischenzeit erkunde unsere kostenlosen Ressourcen:",
|
||||||
"email_waitlist_supplier_link_planner": "Finanzplanungstool \u2014 plane deine Padel-Anlage",
|
"email_waitlist_supplier_link_planner": "Finanzplanungstool \u2014 plane deine Padel-Anlage",
|
||||||
"email_waitlist_supplier_link_directory": "Anbieterverzeichnis \u2014 verifizierte Anbieter durchsuchen",
|
"email_waitlist_supplier_link_directory": "Anbieterverzeichnis \u2014 verifizierte Anbieter durchsuchen",
|
||||||
"email_waitlist_supplier_subject": "Du stehst auf der Liste \u2014 {app_name} {plan_name} startet bald",
|
"email_waitlist_supplier_subject": "Du bist dabei \u2014 {plan_name} fr\u00fcher Zugang kommt",
|
||||||
|
"email_waitlist_supplier_preheader": "Exklusive Launch-Preise + bevorzugtes Onboarding",
|
||||||
"email_waitlist_general_heading": "Du stehst auf der Warteliste",
|
"email_waitlist_general_heading": "Du stehst auf der Warteliste",
|
||||||
"email_waitlist_general_body": "Danke, dass du dich auf die Warteliste eingetragen hast. Wir bereiten den Start der ultimativen Planungsplattform f\u00fcr Padel-Unternehmer vor.",
|
"email_waitlist_general_body": "Danke f\u00fcr deine Anmeldung. Wir bauen die Planungsplattform f\u00fcr Padel-Unternehmer \u2014 Finanzmodellierung, Marktdaten und Anbietervernetzung an einem Ort.",
|
||||||
"email_waitlist_general_perks_intro": "Du bist unter den Ersten, die Zugang erhalten. Wir senden dir:",
|
"email_waitlist_general_perks_intro": "Als fr\u00fches Wartelisten-Mitglied erh\u00e4ltst du:",
|
||||||
"email_waitlist_general_perk_1": "Fr\u00fchen Zugang zur gesamten Plattform",
|
"email_waitlist_general_perk_1": "Fr\u00fchen Zugang vor dem \u00f6ffentlichen Launch",
|
||||||
"email_waitlist_general_perk_2": "Exklusive Launch-Boni",
|
"email_waitlist_general_perk_2": "Exklusive Launch-Preise",
|
||||||
"email_waitlist_general_perk_3": "Priorit\u00e4ts-Onboarding und Support",
|
"email_waitlist_general_perk_3": "Priorit\u00e4ts-Onboarding und Support",
|
||||||
"email_waitlist_general_outro": "Wir melden uns bald.",
|
"email_waitlist_general_outro": "Wir melden uns bald.",
|
||||||
"email_waitlist_general_subject": "Du stehst auf der Liste \u2014 {app_name} startet bald",
|
"email_waitlist_general_subject": "Du stehst auf der Liste \u2014 wir benachrichtigen dich zum Launch",
|
||||||
|
"email_waitlist_general_preheader": "Fr\u00fcher Zugang + exklusive Launch-Preise",
|
||||||
|
|
||||||
"email_lead_forward_heading": "Neues Projekt-Lead",
|
"email_lead_forward_heading": "Neues Projekt-Lead",
|
||||||
"email_lead_forward_subheading": "Ein neues Padel-Projekt passt zu deinen Leistungen.",
|
"email_lead_forward_urgency": "Dieses Lead wurde gerade freigeschaltet. Anbieter, die innerhalb von 24 Stunden antworten, gewinnen 3x h\u00e4ufiger das Projekt.",
|
||||||
"email_lead_forward_section_brief": "Projektbeschreibung",
|
"email_lead_forward_section_brief": "Projektbeschreibung",
|
||||||
"email_lead_forward_section_contact": "Kontakt",
|
"email_lead_forward_section_contact": "Kontakt",
|
||||||
"email_lead_forward_lbl_facility": "Anlage",
|
"email_lead_forward_lbl_facility": "Anlage",
|
||||||
@@ -1591,27 +1607,38 @@
|
|||||||
"email_lead_forward_lbl_phone": "Telefon",
|
"email_lead_forward_lbl_phone": "Telefon",
|
||||||
"email_lead_forward_lbl_company": "Unternehmen",
|
"email_lead_forward_lbl_company": "Unternehmen",
|
||||||
"email_lead_forward_lbl_role": "Rolle",
|
"email_lead_forward_lbl_role": "Rolle",
|
||||||
"email_lead_forward_btn": "Im Lead-Feed ansehen",
|
"email_lead_forward_btn": "Im Lead-Feed ansehen \u2192",
|
||||||
|
"email_lead_forward_reply_direct": "oder <a href=\"mailto:{contact_email}\" style=\"color:#1D4ED8;font-weight:500;\">direkt an {contact_email} antworten</a>",
|
||||||
|
"email_lead_forward_preheader_suffix": "Kontaktdaten enthalten",
|
||||||
|
|
||||||
"email_lead_matched_heading": "Ein Anbieter pr\u00fcft dein Projekt",
|
"email_lead_matched_heading": "Ein Anbieter m\u00f6chte dein Projekt besprechen",
|
||||||
"email_lead_matched_greeting": "Hallo {first_name},",
|
"email_lead_matched_greeting": "Hallo {first_name},",
|
||||||
"email_lead_matched_body": "Gute Nachrichten \u2014 ein verifizierter Anbieter wurde mit deinem Padel-Projekt abgeglichen. Er hat deine Projektbeschreibung und wird sich direkt bei dir melden.",
|
"email_lead_matched_body": "Gute Nachrichten \u2014 ein verifizierter Anbieter wurde mit deinem Padel-Projekt abgeglichen. Er hat deine Projektbeschreibung und Kontaktdaten.",
|
||||||
"email_lead_matched_context": "Du hast eine Angebotsanfrage f\u00fcr eine {facility_type}-Anlage mit {court_count} Pl\u00e4tzen in {country} eingereicht.",
|
"email_lead_matched_context": "Du hast eine Angebotsanfrage f\u00fcr eine {facility_type}-Anlage mit {court_count} Pl\u00e4tzen in {country} eingereicht.",
|
||||||
"email_lead_matched_btn": "Zum Dashboard",
|
"email_lead_matched_next_heading": "Was passiert als N\u00e4chstes",
|
||||||
|
"email_lead_matched_next_body": "Der Anbieter hat deine Projektbeschreibung und Kontaktdaten erhalten. Die meisten Anbieter melden sich innerhalb von 24\u201348 Stunden per E-Mail oder Telefon.",
|
||||||
|
"email_lead_matched_tip": "Tipp: Schnelles Reagieren auf Anbieter-Kontaktaufnahmen erh\u00f6ht deine Chance auf wettbewerbsf\u00e4hige Angebote.",
|
||||||
|
"email_lead_matched_btn": "Zum Dashboard \u2192",
|
||||||
"email_lead_matched_note": "Du erh\u00e4ltst diese Benachrichtigung jedes Mal, wenn ein neuer Anbieter deine Projektdetails freischaltet.",
|
"email_lead_matched_note": "Du erh\u00e4ltst diese Benachrichtigung jedes Mal, wenn ein neuer Anbieter deine Projektdetails freischaltet.",
|
||||||
"email_lead_matched_subject": "Ein Anbieter pr\u00fcft dein Padel-Projekt",
|
"email_lead_matched_subject": "{first_name}, ein Anbieter m\u00f6chte dein Projekt besprechen",
|
||||||
|
"email_lead_matched_preheader": "Der Anbieter wird sich direkt bei dir melden \u2014 das erwartet dich",
|
||||||
|
|
||||||
"email_enquiry_heading": "Neue Anfrage \u00fcber {app_name}",
|
"email_enquiry_heading": "Neue Anfrage von {contact_name}",
|
||||||
"email_enquiry_body": "Du hast eine neue Verzeichnisanfrage f\u00fcr <strong>{supplier_name}</strong>.",
|
"email_enquiry_body": "Du hast eine neue Anfrage \u00fcber deinen <strong>{supplier_name}</strong>-Verzeichniseintrag.",
|
||||||
"email_enquiry_lbl_from": "Von",
|
"email_enquiry_lbl_from": "Von",
|
||||||
"email_enquiry_lbl_message": "Nachricht",
|
"email_enquiry_lbl_message": "Nachricht",
|
||||||
"email_enquiry_reply": "Antworte direkt an <a href=\"mailto:{contact_email}\">{contact_email}</a>.",
|
"email_enquiry_respond_fast": "Antworte innerhalb von 24 Stunden f\u00fcr den besten Eindruck.",
|
||||||
"email_enquiry_subject": "Neue Anfrage \u00fcber {app_name}: {contact_name}",
|
"email_enquiry_reply": "Antworte direkt an <a href=\"mailto:{contact_email}\" style=\"color:#1D4ED8;\">{contact_email}</a>.",
|
||||||
|
"email_enquiry_subject": "Neue Anfrage von {contact_name} \u00fcber deinen Verzeichniseintrag",
|
||||||
|
"email_enquiry_preheader": "Antworte, um mit diesem potenziellen Kunden in Kontakt zu treten",
|
||||||
|
|
||||||
"email_business_plan_heading": "Dein Businessplan ist fertig",
|
"email_business_plan_heading": "Dein Businessplan ist fertig",
|
||||||
"email_business_plan_body": "Dein Padel-Businessplan wurde als PDF erstellt und steht zum Download bereit.",
|
"email_business_plan_body": "Dein Padel-Businessplan wurde als PDF erstellt und steht zum Download bereit.",
|
||||||
"email_business_plan_btn": "PDF herunterladen",
|
"email_business_plan_includes": "Dein Plan enth\u00e4lt Investitions\u00fcbersicht, Umsatzprognosen und Break-Even-Analyse.",
|
||||||
"email_business_plan_subject": "Dein Padel-Businessplan ist fertig",
|
"email_business_plan_btn": "PDF herunterladen \u2192",
|
||||||
|
"email_business_plan_quote_cta": "Bereit f\u00fcr den n\u00e4chsten Schritt? <a href=\"{quote_url}\" style=\"color:#1D4ED8;font-weight:500;\">Angebote von Anbietern einholen \u2192</a>",
|
||||||
|
"email_business_plan_subject": "Dein Businessplan-PDF steht zum Download bereit",
|
||||||
|
"email_business_plan_preheader": "Professioneller Padel-Finanzplan \u2014 jetzt herunterladen",
|
||||||
|
|
||||||
"email_footer_tagline": "Die Planungsplattform f\u00fcr Padel-Unternehmer",
|
"email_footer_tagline": "Die Planungsplattform f\u00fcr Padel-Unternehmer",
|
||||||
"email_footer_copyright": "\u00a9 {year} {app_name}. Du erh\u00e4ltst diese E-Mail, weil du ein Konto hast oder eine Anfrage gestellt hast."
|
"email_footer_copyright": "\u00a9 {year} {app_name}. Du erh\u00e4ltst diese E-Mail, weil du ein Konto hast oder eine Anfrage gestellt hast."
|
||||||
|
|||||||
@@ -1539,44 +1539,60 @@
|
|||||||
"bp_lbl_disclaimer": "<strong>Disclaimer:</strong> This business plan is generated from user-provided assumptions using the Padelnomics financial model. All projections are estimates and do not constitute financial advice. Actual results may vary significantly based on market conditions, execution, and other factors. Consult with financial advisors before making investment decisions. \u00a9 Padelnomics \u2014 padelnomics.io",
|
"bp_lbl_disclaimer": "<strong>Disclaimer:</strong> This business plan is generated from user-provided assumptions using the Padelnomics financial model. All projections are estimates and do not constitute financial advice. Actual results may vary significantly based on market conditions, execution, and other factors. Consult with financial advisors before making investment decisions. \u00a9 Padelnomics \u2014 padelnomics.io",
|
||||||
|
|
||||||
"email_magic_link_heading": "Sign in to {app_name}",
|
"email_magic_link_heading": "Sign in to {app_name}",
|
||||||
"email_magic_link_body": "Click the button below to sign in. This link expires in {expiry_minutes} minutes.",
|
"email_magic_link_body": "Here's your sign-in link. It expires in {expiry_minutes} minutes.",
|
||||||
"email_magic_link_btn": "Sign In",
|
"email_magic_link_btn": "Sign In \u2192",
|
||||||
"email_magic_link_fallback": "If the button doesn't work, copy and paste this URL into your browser:",
|
"email_magic_link_fallback": "If the button doesn't work, copy and paste this URL into your browser:",
|
||||||
"email_magic_link_ignore": "If you didn't request this, you can safely ignore this email.",
|
"email_magic_link_ignore": "If you didn't request this, you can safely ignore this email.",
|
||||||
"email_magic_link_subject": "Sign in to {app_name}",
|
"email_magic_link_subject": "Your sign-in link for {app_name}",
|
||||||
|
"email_magic_link_preheader": "This link expires in {expiry_minutes} minutes",
|
||||||
|
|
||||||
"email_quote_verify_heading": "Verify your email to get supplier quotes",
|
"email_quote_verify_heading": "Verify your email to get quotes",
|
||||||
"email_quote_verify_greeting": "Hi {first_name},",
|
"email_quote_verify_greeting": "Hi {first_name},",
|
||||||
"email_quote_verify_body": "Thanks for requesting quotes{project_desc}. Click the button below to verify your email and activate your quote request. This will also create your {app_name} account so you can track your project.",
|
"email_quote_verify_body": "Thanks for requesting quotes. Verify your email to activate your quote request and create your {app_name} account.",
|
||||||
"email_quote_verify_btn": "Verify & Activate Quote",
|
"email_quote_verify_project_label": "Your project:",
|
||||||
|
"email_quote_verify_urgency": "Verified requests get prioritized by our supplier network.",
|
||||||
|
"email_quote_verify_btn": "Verify & Activate \u2192",
|
||||||
"email_quote_verify_expires": "This link expires in 60 minutes.",
|
"email_quote_verify_expires": "This link expires in 60 minutes.",
|
||||||
"email_quote_verify_fallback": "If the button doesn't work, copy and paste this URL into your browser:",
|
"email_quote_verify_fallback": "If the button doesn't work, copy and paste this URL into your browser:",
|
||||||
"email_quote_verify_ignore": "If you didn't request this, you can safely ignore this email.",
|
"email_quote_verify_ignore": "If you didn't request this, you can safely ignore this email.",
|
||||||
"email_quote_verify_subject": "Verify your email to get supplier quotes",
|
"email_quote_verify_subject": "Verify your email \u2014 suppliers are ready to quote",
|
||||||
|
"email_quote_verify_preheader": "One click to activate your quote request",
|
||||||
|
"email_quote_verify_preheader_courts": "One click to activate your {court_count}-court project",
|
||||||
|
|
||||||
"email_welcome_heading": "Welcome to {app_name}!",
|
"email_welcome_heading": "Welcome to {app_name}",
|
||||||
"email_welcome_body": "Thanks for signing up. You're all set to start planning your padel business.",
|
"email_welcome_greeting": "Hi {first_name},",
|
||||||
"email_welcome_btn": "Go to Dashboard",
|
"email_welcome_body": "You now have access to the financial planner, market data, and supplier directory \u2014 everything you need to plan your padel business.",
|
||||||
"email_welcome_subject": "Welcome to {app_name}",
|
"email_welcome_quickstart_heading": "Quick start:",
|
||||||
|
"email_welcome_link_planner": "Financial Planner \u2014 model your investment",
|
||||||
|
"email_welcome_link_markets": "Market Data \u2014 explore padel demand by city",
|
||||||
|
"email_welcome_link_quotes": "Get Quotes \u2014 connect with verified suppliers",
|
||||||
|
"email_welcome_btn": "Start Planning \u2192",
|
||||||
|
"email_welcome_subject": "You're in \u2014 here's how to start planning",
|
||||||
|
"email_welcome_preheader": "Your padel business planning toolkit is ready",
|
||||||
|
|
||||||
"email_waitlist_supplier_heading": "You're on the Supplier Waitlist",
|
"email_waitlist_supplier_heading": "You're on the Supplier Waitlist",
|
||||||
"email_waitlist_supplier_body": "Thanks for your interest in the <strong>{plan_name}</strong> plan. We're building the ultimate supplier platform for padel entrepreneurs.",
|
"email_waitlist_supplier_body": "Thanks for your interest in the <strong>{plan_name}</strong> plan. We're building a platform to connect you with qualified leads from padel entrepreneurs actively planning projects.",
|
||||||
"email_waitlist_supplier_perks": "You'll be among the first to know when we launch. We'll send you early access, exclusive launch pricing, and onboarding support.",
|
"email_waitlist_supplier_perks_intro": "As an early waitlist member, you'll get:",
|
||||||
|
"email_waitlist_supplier_perk_1": "Early access before public launch",
|
||||||
|
"email_waitlist_supplier_perk_2": "Exclusive launch pricing (locked in)",
|
||||||
|
"email_waitlist_supplier_perk_3": "Dedicated onboarding call",
|
||||||
"email_waitlist_supplier_meanwhile": "In the meantime, explore our free resources:",
|
"email_waitlist_supplier_meanwhile": "In the meantime, explore our free resources:",
|
||||||
"email_waitlist_supplier_link_planner": "Financial Planning Tool \u2014 model your padel facility",
|
"email_waitlist_supplier_link_planner": "Financial Planning Tool \u2014 model your padel facility",
|
||||||
"email_waitlist_supplier_link_directory": "Supplier Directory \u2014 browse verified suppliers",
|
"email_waitlist_supplier_link_directory": "Supplier Directory \u2014 browse verified suppliers",
|
||||||
"email_waitlist_supplier_subject": "You're on the list \u2014 {app_name} {plan_name} is launching soon",
|
"email_waitlist_supplier_subject": "You're in \u2014 {plan_name} early access is coming",
|
||||||
|
"email_waitlist_supplier_preheader": "Exclusive launch pricing + priority onboarding",
|
||||||
"email_waitlist_general_heading": "You're on the Waitlist",
|
"email_waitlist_general_heading": "You're on the Waitlist",
|
||||||
"email_waitlist_general_body": "Thanks for joining the waitlist. We're preparing to launch the ultimate planning platform for padel entrepreneurs.",
|
"email_waitlist_general_body": "Thanks for joining. We're building the planning platform for padel entrepreneurs \u2014 financial modelling, market data, and supplier connections in one place.",
|
||||||
"email_waitlist_general_perks_intro": "You'll be among the first to get access when we open. We'll send you:",
|
"email_waitlist_general_perks_intro": "As an early waitlist member, you'll get:",
|
||||||
"email_waitlist_general_perk_1": "Early access to the full platform",
|
"email_waitlist_general_perk_1": "Early access before public launch",
|
||||||
"email_waitlist_general_perk_2": "Exclusive launch bonuses",
|
"email_waitlist_general_perk_2": "Exclusive launch pricing",
|
||||||
"email_waitlist_general_perk_3": "Priority onboarding and support",
|
"email_waitlist_general_perk_3": "Priority onboarding and support",
|
||||||
"email_waitlist_general_outro": "We'll be in touch soon.",
|
"email_waitlist_general_outro": "We'll be in touch soon.",
|
||||||
"email_waitlist_general_subject": "You're on the list \u2014 {app_name} is launching soon",
|
"email_waitlist_general_subject": "You're on the list \u2014 we'll notify you at launch",
|
||||||
|
"email_waitlist_general_preheader": "Early access + exclusive launch pricing",
|
||||||
|
|
||||||
"email_lead_forward_heading": "New Project Lead",
|
"email_lead_forward_heading": "New Project Lead",
|
||||||
"email_lead_forward_subheading": "A new padel project matches your services.",
|
"email_lead_forward_urgency": "This lead was just unlocked. Suppliers who respond within 24 hours are 3x more likely to win the project.",
|
||||||
"email_lead_forward_section_brief": "Project Brief",
|
"email_lead_forward_section_brief": "Project Brief",
|
||||||
"email_lead_forward_section_contact": "Contact",
|
"email_lead_forward_section_contact": "Contact",
|
||||||
"email_lead_forward_lbl_facility": "Facility",
|
"email_lead_forward_lbl_facility": "Facility",
|
||||||
@@ -1591,27 +1607,38 @@
|
|||||||
"email_lead_forward_lbl_phone": "Phone",
|
"email_lead_forward_lbl_phone": "Phone",
|
||||||
"email_lead_forward_lbl_company": "Company",
|
"email_lead_forward_lbl_company": "Company",
|
||||||
"email_lead_forward_lbl_role": "Role",
|
"email_lead_forward_lbl_role": "Role",
|
||||||
"email_lead_forward_btn": "View in Lead Feed",
|
"email_lead_forward_btn": "View in Lead Feed \u2192",
|
||||||
|
"email_lead_forward_reply_direct": "or <a href=\"mailto:{contact_email}\" style=\"color:#1D4ED8;font-weight:500;\">reply directly to {contact_email}</a>",
|
||||||
|
"email_lead_forward_preheader_suffix": "contact details inside",
|
||||||
|
|
||||||
"email_lead_matched_heading": "A supplier is reviewing your project",
|
"email_lead_matched_heading": "A supplier wants to discuss your project",
|
||||||
"email_lead_matched_greeting": "Hi {first_name},",
|
"email_lead_matched_greeting": "Hi {first_name},",
|
||||||
"email_lead_matched_body": "Great news \u2014 a verified supplier has been matched with your padel project. They have your project brief and will reach out to you directly.",
|
"email_lead_matched_body": "Great news \u2014 a verified supplier has been matched with your padel project. They have your project brief and contact details.",
|
||||||
"email_lead_matched_context": "You submitted a quote request for a {facility_type} facility with {court_count} courts in {country}.",
|
"email_lead_matched_context": "You submitted a quote request for a {facility_type} facility with {court_count} courts in {country}.",
|
||||||
"email_lead_matched_btn": "View Your Dashboard",
|
"email_lead_matched_next_heading": "What happens next",
|
||||||
|
"email_lead_matched_next_body": "The supplier has received your project brief and contact details. Most suppliers respond within 24\u201348 hours via email or phone.",
|
||||||
|
"email_lead_matched_tip": "Tip: Responding quickly to supplier outreach increases your chance of getting competitive quotes.",
|
||||||
|
"email_lead_matched_btn": "View Your Dashboard \u2192",
|
||||||
"email_lead_matched_note": "You'll receive this notification each time a new supplier unlocks your project details.",
|
"email_lead_matched_note": "You'll receive this notification each time a new supplier unlocks your project details.",
|
||||||
"email_lead_matched_subject": "A supplier is reviewing your padel project",
|
"email_lead_matched_subject": "{first_name}, a supplier wants to discuss your project",
|
||||||
|
"email_lead_matched_preheader": "They'll reach out to you directly \u2014 here's what to expect",
|
||||||
|
|
||||||
"email_enquiry_heading": "New enquiry via {app_name}",
|
"email_enquiry_heading": "New enquiry from {contact_name}",
|
||||||
"email_enquiry_body": "You have a new directory enquiry for <strong>{supplier_name}</strong>.",
|
"email_enquiry_body": "You have a new enquiry via your <strong>{supplier_name}</strong> directory listing.",
|
||||||
"email_enquiry_lbl_from": "From",
|
"email_enquiry_lbl_from": "From",
|
||||||
"email_enquiry_lbl_message": "Message",
|
"email_enquiry_lbl_message": "Message",
|
||||||
"email_enquiry_reply": "Reply directly to <a href=\"mailto:{contact_email}\">{contact_email}</a> to respond.",
|
"email_enquiry_respond_fast": "Respond within 24 hours for the best impression.",
|
||||||
"email_enquiry_subject": "New enquiry via {app_name}: {contact_name}",
|
"email_enquiry_reply": "Reply directly to <a href=\"mailto:{contact_email}\" style=\"color:#1D4ED8;\">{contact_email}</a> to connect.",
|
||||||
|
"email_enquiry_subject": "New enquiry from {contact_name} via your directory listing",
|
||||||
|
"email_enquiry_preheader": "Reply to connect with this potential client",
|
||||||
|
|
||||||
"email_business_plan_heading": "Your Business Plan is Ready",
|
"email_business_plan_heading": "Your business plan is ready",
|
||||||
"email_business_plan_body": "Your padel business plan PDF has been generated and is ready for download.",
|
"email_business_plan_body": "Your padel business plan PDF has been generated and is ready for download.",
|
||||||
"email_business_plan_btn": "Download PDF",
|
"email_business_plan_includes": "Your plan includes investment breakdown, revenue projections, and break-even analysis.",
|
||||||
"email_business_plan_subject": "Your Padel Business Plan PDF is Ready",
|
"email_business_plan_btn": "Download PDF \u2192",
|
||||||
|
"email_business_plan_quote_cta": "Ready for the next step? <a href=\"{quote_url}\" style=\"color:#1D4ED8;font-weight:500;\">Get quotes from suppliers \u2192</a>",
|
||||||
|
"email_business_plan_subject": "Your business plan PDF is ready to download",
|
||||||
|
"email_business_plan_preheader": "Professional padel facility financial plan \u2014 download now",
|
||||||
|
|
||||||
"email_footer_tagline": "The padel business planning platform",
|
"email_footer_tagline": "The padel business planning platform",
|
||||||
"email_footer_copyright": "\u00a9 {year} {app_name}. You received this email because you have an account or submitted a request."
|
"email_footer_copyright": "\u00a9 {year} {app_name}. You received this email because you have an account or submitted a request."
|
||||||
|
|||||||
@@ -24,11 +24,23 @@ def _t(key: str, lang: str = "en", **kwargs) -> str:
|
|||||||
return raw.format(**kwargs) if kwargs else raw
|
return raw.format(**kwargs) if kwargs else raw
|
||||||
|
|
||||||
|
|
||||||
def _email_wrap(body: str, lang: str = "en") -> str:
|
def _email_wrap(body: str, lang: str = "en", preheader: str = "") -> str:
|
||||||
"""Wrap email body in a branded layout with inline CSS."""
|
"""Wrap email body in a branded layout with inline CSS.
|
||||||
|
|
||||||
|
preheader: hidden preview text shown in email client list views.
|
||||||
|
"""
|
||||||
year = datetime.utcnow().year
|
year = datetime.utcnow().year
|
||||||
tagline = _t("email_footer_tagline", lang)
|
tagline = _t("email_footer_tagline", lang)
|
||||||
copyright_text = _t("email_footer_copyright", lang, year=year, app_name=config.APP_NAME)
|
copyright_text = _t("email_footer_copyright", lang, year=year, app_name=config.APP_NAME)
|
||||||
|
# Hidden preheader trick: visible text + invisible padding to prevent
|
||||||
|
# email clients from pulling body text into the preview.
|
||||||
|
preheader_html = ""
|
||||||
|
if preheader:
|
||||||
|
preheader_html = (
|
||||||
|
f'<span style="display:none;font-size:1px;color:#F1F5F9;line-height:1px;'
|
||||||
|
f'max-height:0;max-width:0;opacity:0;overflow:hidden;">'
|
||||||
|
f'{preheader}{"͏ ‌ " * 30}</span>'
|
||||||
|
)
|
||||||
return f"""\
|
return f"""\
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{lang}">
|
<html lang="{lang}">
|
||||||
@@ -38,23 +50,19 @@ def _email_wrap(body: str, lang: str = "en") -> str:
|
|||||||
<title>{config.APP_NAME}</title>
|
<title>{config.APP_NAME}</title>
|
||||||
</head>
|
</head>
|
||||||
<body style="margin:0;padding:0;background-color:#F1F5F9;font-family:Helvetica,Arial,sans-serif;">
|
<body style="margin:0;padding:0;background-color:#F1F5F9;font-family:Helvetica,Arial,sans-serif;">
|
||||||
|
{preheader_html}
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color:#F1F5F9;padding:40px 16px;">
|
<table width="100%" cellpadding="0" cellspacing="0" style="background-color:#F1F5F9;padding:40px 16px;">
|
||||||
<tr><td align="center">
|
<tr><td align="center">
|
||||||
<table width="520" cellpadding="0" cellspacing="0" style="max-width:520px;width:100%;background-color:#FFFFFF;border-radius:10px;border:1px solid #E2E8F0;overflow:hidden;">
|
<table width="520" cellpadding="0" cellspacing="0" style="max-width:520px;width:100%;background-color:#FFFFFF;border-radius:10px;border:1px solid #E2E8F0;overflow:hidden;">
|
||||||
|
|
||||||
<!-- Logo header -->
|
<!-- Blue accent border -->
|
||||||
<tr><td style="background-color:#0F172A;padding:28px 36px 24px;">
|
<tr><td style="height:3px;background-color:#1D4ED8;font-size:0;line-height:0;"> </td></tr>
|
||||||
<table cellpadding="0" cellspacing="0">
|
|
||||||
<tr>
|
<!-- Wordmark header -->
|
||||||
<td style="vertical-align:middle;">
|
<tr><td style="background-color:#0F172A;padding:24px 36px;">
|
||||||
<!-- Padel racket monogram -->
|
<a href="{config.BASE_URL}" style="text-decoration:none;">
|
||||||
<span style="display:inline-block;width:32px;height:32px;background-color:#1D4ED8;border-radius:6px;text-align:center;line-height:32px;font-size:17px;font-weight:800;color:#fff;font-family:Helvetica,Arial,sans-serif;margin-right:10px;vertical-align:middle;">P</span>
|
<span style="color:#FFFFFF;font-size:18px;font-weight:800;letter-spacing:-0.02em;font-family:'Bricolage Grotesque',Georgia,'Times New Roman',serif;">padelnomics</span>
|
||||||
</td>
|
</a>
|
||||||
<td style="vertical-align:middle;">
|
|
||||||
<span style="color:#FFFFFF;font-size:18px;font-weight:700;letter-spacing:-0.03em;vertical-align:middle;">{config.APP_NAME}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td></tr>
|
</td></tr>
|
||||||
|
|
||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
@@ -68,7 +76,7 @@ def _email_wrap(body: str, lang: str = "en") -> str:
|
|||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<tr><td style="padding:20px 36px;background-color:#F8FAFC;">
|
<tr><td style="padding:20px 36px;background-color:#F8FAFC;">
|
||||||
<p style="margin:0 0 6px;font-size:12px;color:#94A3B8;text-align:center;">
|
<p style="margin:0 0 6px;font-size:12px;color:#94A3B8;text-align:center;">
|
||||||
<a href="{config.BASE_URL}" style="color:#64748B;text-decoration:none;font-weight:500;">{config.APP_NAME}</a>
|
<a href="{config.BASE_URL}" style="color:#64748B;text-decoration:none;font-weight:500;">padelnomics.io</a>
|
||||||
·
|
·
|
||||||
{tagline}
|
{tagline}
|
||||||
</p>
|
</p>
|
||||||
@@ -85,12 +93,16 @@ def _email_wrap(body: str, lang: str = "en") -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _email_button(url: str, label: str) -> str:
|
def _email_button(url: str, label: str) -> str:
|
||||||
"""Render a branded CTA button for email."""
|
"""Render a branded CTA button for email.
|
||||||
|
|
||||||
|
Uses display:block for full-width tap target on mobile.
|
||||||
|
"""
|
||||||
return (
|
return (
|
||||||
f'<table cellpadding="0" cellspacing="0" style="margin:28px 0 8px;">'
|
f'<table cellpadding="0" cellspacing="0" width="100%" style="margin:28px 0 8px;">'
|
||||||
f'<tr><td style="background-color:#1D4ED8;border-radius:7px;text-align:center;">'
|
f'<tr><td style="background-color:#1D4ED8;border-radius:8px;text-align:center;">'
|
||||||
f'<a href="{url}" style="display:inline-block;padding:13px 30px;'
|
f'<a href="{url}" style="display:block;padding:14px 32px;'
|
||||||
f'color:#FFFFFF;font-size:15px;font-weight:600;text-decoration:none;letter-spacing:-0.01em;">'
|
f'color:#FFFFFF;font-size:15px;font-weight:600;text-decoration:none;'
|
||||||
|
f'letter-spacing:-0.01em;">'
|
||||||
f"{label}</a></td></tr></table>"
|
f"{label}</a></td></tr></table>"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -200,9 +212,11 @@ async def handle_send_magic_link(payload: dict) -> None:
|
|||||||
print(f" {link}")
|
print(f" {link}")
|
||||||
print(f"{'=' * 60}\n")
|
print(f"{'=' * 60}\n")
|
||||||
|
|
||||||
|
expiry_minutes = config.MAGIC_LINK_EXPIRY_MINUTES
|
||||||
body = (
|
body = (
|
||||||
f'<h2 style="margin:0 0 16px;color:#0F172A;font-size:20px;">{_t("email_magic_link_heading", lang, app_name=config.APP_NAME)}</h2>'
|
f'<h2 style="margin:0 0 8px;color:#0F172A;font-size:22px;">{_t("email_magic_link_heading", lang, app_name=config.APP_NAME)}</h2>'
|
||||||
f'<p>{_t("email_magic_link_body", lang, expiry_minutes=config.MAGIC_LINK_EXPIRY_MINUTES)}</p>'
|
f'<hr style="border:none;border-top:1px solid #E2E8F0;margin:0 0 16px;">'
|
||||||
|
f'<p>{_t("email_magic_link_body", lang, expiry_minutes=expiry_minutes)}</p>'
|
||||||
f'{_email_button(link, _t("email_magic_link_btn", lang))}'
|
f'{_email_button(link, _t("email_magic_link_btn", lang))}'
|
||||||
f'<p style="font-size:13px;color:#94A3B8;">{_t("email_magic_link_fallback", lang)}</p>'
|
f'<p style="font-size:13px;color:#94A3B8;">{_t("email_magic_link_fallback", lang)}</p>'
|
||||||
f'<p style="font-size:13px;color:#94A3B8;word-break:break-all;">{link}</p>'
|
f'<p style="font-size:13px;color:#94A3B8;word-break:break-all;">{link}</p>'
|
||||||
@@ -212,7 +226,7 @@ async def handle_send_magic_link(payload: dict) -> None:
|
|||||||
await send_email(
|
await send_email(
|
||||||
to=payload["email"],
|
to=payload["email"],
|
||||||
subject=_t("email_magic_link_subject", lang, app_name=config.APP_NAME),
|
subject=_t("email_magic_link_subject", lang, app_name=config.APP_NAME),
|
||||||
html=_email_wrap(body, lang),
|
html=_email_wrap(body, lang, preheader=_t("email_magic_link_preheader", lang, expiry_minutes=expiry_minutes)),
|
||||||
from_addr=EMAIL_ADDRESSES["transactional"],
|
from_addr=EMAIL_ADDRESSES["transactional"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -235,21 +249,36 @@ async def handle_send_quote_verification(payload: dict) -> None:
|
|||||||
first_name = (
|
first_name = (
|
||||||
payload.get("contact_name", "").split()[0] if payload.get("contact_name") else "there"
|
payload.get("contact_name", "").split()[0] if payload.get("contact_name") else "there"
|
||||||
)
|
)
|
||||||
project_desc = ""
|
court_count = payload.get("court_count", "")
|
||||||
parts = []
|
facility_type = payload.get("facility_type", "")
|
||||||
if payload.get("court_count"):
|
country = payload.get("country", "")
|
||||||
parts.append(f"{payload['court_count']}-court")
|
|
||||||
if payload.get("facility_type"):
|
# Project recap card
|
||||||
parts.append(payload["facility_type"])
|
project_card = ""
|
||||||
if payload.get("country"):
|
recap_parts = []
|
||||||
parts.append(f"in {payload['country']}")
|
if court_count:
|
||||||
if parts:
|
recap_parts.append(f"{court_count} courts")
|
||||||
project_desc = f" for your {' '.join(parts)} project"
|
if facility_type:
|
||||||
|
recap_parts.append(facility_type)
|
||||||
|
if country:
|
||||||
|
recap_parts.append(country)
|
||||||
|
if recap_parts:
|
||||||
|
project_card = (
|
||||||
|
f'<table cellpadding="0" cellspacing="0" width="100%" style="margin:16px 0;border:1px solid #E2E8F0;border-radius:8px;overflow:hidden;">'
|
||||||
|
f'<tr><td style="padding:14px 18px;background-color:#F8FAFC;font-size:13px;color:#64748B;">'
|
||||||
|
f'<strong style="color:#0F172A;">{_t("email_quote_verify_project_label", lang)}</strong> {" · ".join(recap_parts)}'
|
||||||
|
f'</td></tr></table>'
|
||||||
|
)
|
||||||
|
|
||||||
|
preheader = _t("email_quote_verify_preheader_courts", lang, court_count=court_count) if court_count else _t("email_quote_verify_preheader", lang)
|
||||||
|
|
||||||
body = (
|
body = (
|
||||||
f'<h2 style="margin:0 0 16px;color:#0F172A;font-size:20px;">{_t("email_quote_verify_heading", lang)}</h2>'
|
f'<h2 style="margin:0 0 8px;color:#0F172A;font-size:22px;">{_t("email_quote_verify_heading", lang)}</h2>'
|
||||||
|
f'<hr style="border:none;border-top:1px solid #E2E8F0;margin:0 0 16px;">'
|
||||||
f'<p>{_t("email_quote_verify_greeting", lang, first_name=first_name)}</p>'
|
f'<p>{_t("email_quote_verify_greeting", lang, first_name=first_name)}</p>'
|
||||||
f'<p>{_t("email_quote_verify_body", lang, project_desc=project_desc, app_name=config.APP_NAME)}</p>'
|
f'<p>{_t("email_quote_verify_body", lang, app_name=config.APP_NAME)}</p>'
|
||||||
|
f'{project_card}'
|
||||||
|
f'<p style="font-size:13px;color:#334155;">{_t("email_quote_verify_urgency", lang)}</p>'
|
||||||
f'{_email_button(link, _t("email_quote_verify_btn", lang))}'
|
f'{_email_button(link, _t("email_quote_verify_btn", lang))}'
|
||||||
f'<p style="font-size:13px;color:#94A3B8;">{_t("email_quote_verify_expires", lang)}</p>'
|
f'<p style="font-size:13px;color:#94A3B8;">{_t("email_quote_verify_expires", lang)}</p>'
|
||||||
f'<p style="font-size:13px;color:#94A3B8;">{_t("email_quote_verify_fallback", lang)}</p>'
|
f'<p style="font-size:13px;color:#94A3B8;">{_t("email_quote_verify_fallback", lang)}</p>'
|
||||||
@@ -260,7 +289,7 @@ async def handle_send_quote_verification(payload: dict) -> None:
|
|||||||
await send_email(
|
await send_email(
|
||||||
to=payload["email"],
|
to=payload["email"],
|
||||||
subject=_t("email_quote_verify_subject", lang),
|
subject=_t("email_quote_verify_subject", lang),
|
||||||
html=_email_wrap(body, lang),
|
html=_email_wrap(body, lang, preheader=preheader),
|
||||||
from_addr=EMAIL_ADDRESSES["transactional"],
|
from_addr=EMAIL_ADDRESSES["transactional"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -269,16 +298,29 @@ async def handle_send_quote_verification(payload: dict) -> None:
|
|||||||
async def handle_send_welcome(payload: dict) -> None:
|
async def handle_send_welcome(payload: dict) -> None:
|
||||||
"""Send welcome email to new user."""
|
"""Send welcome email to new user."""
|
||||||
lang = payload.get("lang", "en")
|
lang = payload.get("lang", "en")
|
||||||
|
first_name = (payload.get("name") or "").split()[0] or "there"
|
||||||
|
|
||||||
body = (
|
body = (
|
||||||
f'<h2 style="margin:0 0 16px;color:#0F172A;font-size:20px;">{_t("email_welcome_heading", lang, app_name=config.APP_NAME)}</h2>'
|
f'<h2 style="margin:0 0 8px;color:#0F172A;font-size:22px;">{_t("email_welcome_heading", lang, app_name=config.APP_NAME)}</h2>'
|
||||||
|
f'<hr style="border:none;border-top:1px solid #E2E8F0;margin:0 0 16px;">'
|
||||||
|
f'<p>{_t("email_welcome_greeting", lang, first_name=first_name)}</p>'
|
||||||
f'<p>{_t("email_welcome_body", lang)}</p>'
|
f'<p>{_t("email_welcome_body", lang)}</p>'
|
||||||
f'{_email_button(f"{config.BASE_URL}/dashboard", _t("email_welcome_btn", lang))}'
|
f'<p style="font-size:14px;font-weight:600;color:#0F172A;margin:20px 0 8px;">{_t("email_welcome_quickstart_heading", lang)}</p>'
|
||||||
|
f'<table cellpadding="0" cellspacing="0" style="margin:0 0 20px;font-size:14px;">'
|
||||||
|
f'<tr><td style="padding:4px 10px 4px 0;color:#1D4ED8;">●</td>'
|
||||||
|
f'<td style="padding:4px 0;"><a href="{config.BASE_URL}/planner" style="color:#1D4ED8;text-decoration:none;font-weight:500;">{_t("email_welcome_link_planner", lang)}</a></td></tr>'
|
||||||
|
f'<tr><td style="padding:4px 10px 4px 0;color:#1D4ED8;">●</td>'
|
||||||
|
f'<td style="padding:4px 0;"><a href="{config.BASE_URL}/{lang}/markets" style="color:#1D4ED8;text-decoration:none;font-weight:500;">{_t("email_welcome_link_markets", lang)}</a></td></tr>'
|
||||||
|
f'<tr><td style="padding:4px 10px 4px 0;color:#1D4ED8;">●</td>'
|
||||||
|
f'<td style="padding:4px 0;"><a href="{config.BASE_URL}/{lang}/leads/quote" style="color:#1D4ED8;text-decoration:none;font-weight:500;">{_t("email_welcome_link_quotes", lang)}</a></td></tr>'
|
||||||
|
f'</table>'
|
||||||
|
f'{_email_button(f"{config.BASE_URL}/planner", _t("email_welcome_btn", lang))}'
|
||||||
)
|
)
|
||||||
|
|
||||||
await send_email(
|
await send_email(
|
||||||
to=payload["email"],
|
to=payload["email"],
|
||||||
subject=_t("email_welcome_subject", lang, app_name=config.APP_NAME),
|
subject=_t("email_welcome_subject", lang),
|
||||||
html=_email_wrap(body, lang),
|
html=_email_wrap(body, lang, preheader=_t("email_welcome_preheader", lang)),
|
||||||
from_addr=EMAIL_ADDRESSES["transactional"],
|
from_addr=EMAIL_ADDRESSES["transactional"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -292,21 +334,30 @@ async def handle_send_waitlist_confirmation(payload: dict) -> None:
|
|||||||
|
|
||||||
if intent.startswith("supplier_"):
|
if intent.startswith("supplier_"):
|
||||||
plan_name = intent.replace("supplier_", "").title()
|
plan_name = intent.replace("supplier_", "").title()
|
||||||
subject = _t("email_waitlist_supplier_subject", lang, app_name=config.APP_NAME, plan_name=plan_name)
|
subject = _t("email_waitlist_supplier_subject", lang, plan_name=plan_name)
|
||||||
|
preheader = _t("email_waitlist_supplier_preheader", lang)
|
||||||
body = (
|
body = (
|
||||||
f'<h2 style="margin:0 0 16px;color:#0F172A;font-size:20px;">{_t("email_waitlist_supplier_heading", lang)}</h2>'
|
f'<h2 style="margin:0 0 8px;color:#0F172A;font-size:22px;">{_t("email_waitlist_supplier_heading", lang)}</h2>'
|
||||||
|
f'<hr style="border:none;border-top:1px solid #E2E8F0;margin:0 0 16px;">'
|
||||||
f'<p>{_t("email_waitlist_supplier_body", lang, plan_name=plan_name)}</p>'
|
f'<p>{_t("email_waitlist_supplier_body", lang, plan_name=plan_name)}</p>'
|
||||||
f'<p>{_t("email_waitlist_supplier_perks", lang)}</p>'
|
f'<p>{_t("email_waitlist_supplier_perks_intro", lang)}</p>'
|
||||||
|
f'<ul style="font-size:14px;color:#1E293B;margin:16px 0;">'
|
||||||
|
f'<li>{_t("email_waitlist_supplier_perk_1", lang)}</li>'
|
||||||
|
f'<li>{_t("email_waitlist_supplier_perk_2", lang)}</li>'
|
||||||
|
f'<li>{_t("email_waitlist_supplier_perk_3", lang)}</li>'
|
||||||
|
f'</ul>'
|
||||||
f'<p style="font-size:13px;color:#64748B;">{_t("email_waitlist_supplier_meanwhile", lang)}</p>'
|
f'<p style="font-size:13px;color:#64748B;">{_t("email_waitlist_supplier_meanwhile", lang)}</p>'
|
||||||
f'<ul style="font-size:13px;color:#64748B;">'
|
f'<ul style="font-size:13px;color:#64748B;">'
|
||||||
f'<li><a href="{config.BASE_URL}/planner">{_t("email_waitlist_supplier_link_planner", lang)}</a></li>'
|
f'<li><a href="{config.BASE_URL}/planner" style="color:#1D4ED8;">{_t("email_waitlist_supplier_link_planner", lang)}</a></li>'
|
||||||
f'<li><a href="{config.BASE_URL}/directory">{_t("email_waitlist_supplier_link_directory", lang)}</a></li>'
|
f'<li><a href="{config.BASE_URL}/directory" style="color:#1D4ED8;">{_t("email_waitlist_supplier_link_directory", lang)}</a></li>'
|
||||||
f'</ul>'
|
f'</ul>'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
subject = _t("email_waitlist_general_subject", lang, app_name=config.APP_NAME)
|
subject = _t("email_waitlist_general_subject", lang)
|
||||||
|
preheader = _t("email_waitlist_general_preheader", lang)
|
||||||
body = (
|
body = (
|
||||||
f'<h2 style="margin:0 0 16px;color:#0F172A;font-size:20px;">{_t("email_waitlist_general_heading", lang)}</h2>'
|
f'<h2 style="margin:0 0 8px;color:#0F172A;font-size:22px;">{_t("email_waitlist_general_heading", lang)}</h2>'
|
||||||
|
f'<hr style="border:none;border-top:1px solid #E2E8F0;margin:0 0 16px;">'
|
||||||
f'<p>{_t("email_waitlist_general_body", lang)}</p>'
|
f'<p>{_t("email_waitlist_general_body", lang)}</p>'
|
||||||
f'<p>{_t("email_waitlist_general_perks_intro", lang)}</p>'
|
f'<p>{_t("email_waitlist_general_perks_intro", lang)}</p>'
|
||||||
f'<ul style="font-size:14px;color:#1E293B;margin:16px 0;">'
|
f'<ul style="font-size:14px;color:#1E293B;margin:16px 0;">'
|
||||||
@@ -320,7 +371,7 @@ async def handle_send_waitlist_confirmation(payload: dict) -> None:
|
|||||||
await send_email(
|
await send_email(
|
||||||
to=email,
|
to=email,
|
||||||
subject=subject,
|
subject=subject,
|
||||||
html=_email_wrap(body, lang),
|
html=_email_wrap(body, lang, preheader=preheader),
|
||||||
from_addr=EMAIL_ADDRESSES["transactional"],
|
from_addr=EMAIL_ADDRESSES["transactional"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -354,19 +405,31 @@ async def handle_send_lead_forward_email(payload: dict) -> None:
|
|||||||
country = lead["country"] or "Unknown"
|
country = lead["country"] or "Unknown"
|
||||||
courts = lead["court_count"] or "?"
|
courts = lead["court_count"] or "?"
|
||||||
budget = lead["budget_estimate"] or "?"
|
budget = lead["budget_estimate"] or "?"
|
||||||
|
facility_type = lead["facility_type"] or "padel"
|
||||||
|
timeline = lead["timeline"] or ""
|
||||||
|
contact_email = lead["contact_email"] or ""
|
||||||
|
|
||||||
subject = f"[{heat}] New padel project in {country} — {courts} courts, €{budget}"
|
subject = f"[{heat}] New padel project in {country} \u2014 {courts} courts, \u20ac{budget}"
|
||||||
|
|
||||||
t = lambda key: _t(key, lang) # noqa: E731
|
# Heat badge color
|
||||||
|
heat_colors = {"HOT": "#DC2626", "WARM": "#EA580C", "COOL": "#2563EB"}
|
||||||
|
heat_bg = heat_colors.get(heat, "#2563EB")
|
||||||
|
heat_badge = (
|
||||||
|
f'<span style="display:inline-block;padding:2px 8px;border-radius:4px;'
|
||||||
|
f'background-color:{heat_bg};color:#FFFFFF;font-size:11px;font-weight:700;'
|
||||||
|
f'letter-spacing:0.04em;vertical-align:middle;margin-left:8px;">{heat}</span>'
|
||||||
|
)
|
||||||
|
|
||||||
|
tl = lambda key: _t(key, lang) # noqa: E731
|
||||||
|
|
||||||
brief_rows = [
|
brief_rows = [
|
||||||
(t("email_lead_forward_lbl_facility"), f"{lead['facility_type'] or '-'} ({lead['build_context'] or '-'})"),
|
(tl("email_lead_forward_lbl_facility"), f"{facility_type} ({lead['build_context'] or '-'})"),
|
||||||
(t("email_lead_forward_lbl_courts"), f"{courts} | Glass: {lead['glass_type'] or '-'} | Lighting: {lead['lighting_type'] or '-'}"),
|
(tl("email_lead_forward_lbl_courts"), f"{courts} | Glass: {lead['glass_type'] or '-'} | Lighting: {lead['lighting_type'] or '-'}"),
|
||||||
(t("email_lead_forward_lbl_location"), f"{lead['location'] or '-'}, {country}"),
|
(tl("email_lead_forward_lbl_location"), f"{lead['location'] or '-'}, {country}"),
|
||||||
(t("email_lead_forward_lbl_timeline"), f"{lead['timeline'] or '-'} | Budget: \u20ac{budget}"),
|
(tl("email_lead_forward_lbl_timeline"), f"{timeline or '-'} | Budget: \u20ac{budget}"),
|
||||||
(t("email_lead_forward_lbl_phase"), f"{lead['location_status'] or '-'} | Financing: {lead['financing_status'] or '-'}"),
|
(tl("email_lead_forward_lbl_phase"), f"{lead['location_status'] or '-'} | Financing: {lead['financing_status'] or '-'}"),
|
||||||
(t("email_lead_forward_lbl_services"), lead["services_needed"] or "-"),
|
(tl("email_lead_forward_lbl_services"), lead["services_needed"] or "-"),
|
||||||
(t("email_lead_forward_lbl_additional"), lead["additional_info"] or "-"),
|
(tl("email_lead_forward_lbl_additional"), lead["additional_info"] or "-"),
|
||||||
]
|
]
|
||||||
|
|
||||||
brief_html = ""
|
brief_html = ""
|
||||||
@@ -376,29 +439,41 @@ async def handle_send_lead_forward_email(payload: dict) -> None:
|
|||||||
f'<td style="padding:4px 0;font-size:13px;color:#1E293B">{value}</td></tr>'
|
f'<td style="padding:4px 0;font-size:13px;color:#1E293B">{value}</td></tr>'
|
||||||
)
|
)
|
||||||
|
|
||||||
contact_rows = [
|
contact_name = lead["contact_name"] or "-"
|
||||||
(t("email_lead_forward_lbl_name"), lead["contact_name"] or "-"),
|
contact_phone = lead["contact_phone"] or "-"
|
||||||
(t("email_lead_forward_lbl_email"), lead["contact_email"] or "-"),
|
|
||||||
(t("email_lead_forward_lbl_phone"), lead["contact_phone"] or "-"),
|
|
||||||
(t("email_lead_forward_lbl_company"), lead["contact_company"] or "-"),
|
|
||||||
(t("email_lead_forward_lbl_role"), lead["stakeholder_type"] or "-"),
|
|
||||||
]
|
|
||||||
|
|
||||||
contact_html = ""
|
# Contact section with prominent email
|
||||||
for label, value in contact_rows:
|
contact_html = (
|
||||||
contact_html += (
|
f'<tr><td style="padding:4px 12px 4px 0;color:#94A3B8;font-size:13px">{tl("email_lead_forward_lbl_name")}</td>'
|
||||||
f'<tr><td style="padding:4px 12px 4px 0;color:#94A3B8;font-size:13px">{label}</td>'
|
f'<td style="padding:4px 0;font-size:14px;color:#0F172A;font-weight:600">{contact_name}</td></tr>'
|
||||||
f'<td style="padding:4px 0;font-size:13px;color:#1E293B">{value}</td></tr>'
|
f'<tr><td style="padding:4px 12px 4px 0;color:#94A3B8;font-size:13px">{tl("email_lead_forward_lbl_email")}</td>'
|
||||||
|
f'<td style="padding:4px 0;font-size:14px;"><a href="mailto:{contact_email}" style="color:#1D4ED8;font-weight:600;text-decoration:none;">{contact_email}</a></td></tr>'
|
||||||
|
f'<tr><td style="padding:4px 12px 4px 0;color:#94A3B8;font-size:13px">{tl("email_lead_forward_lbl_phone")}</td>'
|
||||||
|
f'<td style="padding:4px 0;font-size:13px;color:#1E293B">{contact_phone}</td></tr>'
|
||||||
|
f'<tr><td style="padding:4px 12px 4px 0;color:#94A3B8;font-size:13px">{tl("email_lead_forward_lbl_company")}</td>'
|
||||||
|
f'<td style="padding:4px 0;font-size:13px;color:#1E293B">{lead["contact_company"] or "-"}</td></tr>'
|
||||||
|
f'<tr><td style="padding:4px 12px 4px 0;color:#94A3B8;font-size:13px">{tl("email_lead_forward_lbl_role")}</td>'
|
||||||
|
f'<td style="padding:4px 0;font-size:13px;color:#1E293B">{lead["stakeholder_type"] or "-"}</td></tr>'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
preheader_parts = [f"{facility_type} project"]
|
||||||
|
if timeline:
|
||||||
|
preheader_parts.append(f"{timeline} timeline")
|
||||||
|
preheader_parts.append(_t("email_lead_forward_preheader_suffix", lang))
|
||||||
|
|
||||||
body = (
|
body = (
|
||||||
f'<h2 style="margin:0 0 4px;color:#0F172A;font-size:18px;">{t("email_lead_forward_heading")}</h2>'
|
f'<p style="font-size:13px;color:#334155;margin:0 0 16px;padding:10px 14px;'
|
||||||
f'<p style="font-size:13px;color:#64748B;margin:0 0 16px">{t("email_lead_forward_subheading")}</p>'
|
f'background-color:#FEF3C7;border-radius:6px;border-left:3px solid #F59E0B;">'
|
||||||
f'<h3 style="font-size:11px;text-transform:uppercase;letter-spacing:0.06em;color:#94A3B8;margin:0 0 8px">{t("email_lead_forward_section_brief")}</h3>'
|
f'{_t("email_lead_forward_urgency", lang)}</p>'
|
||||||
|
f'<h2 style="margin:0 0 4px;color:#0F172A;font-size:18px;">{tl("email_lead_forward_heading")} {heat_badge}</h2>'
|
||||||
|
f'<hr style="border:none;border-top:1px solid #E2E8F0;margin:8px 0 16px;">'
|
||||||
|
f'<h3 style="font-size:11px;text-transform:uppercase;letter-spacing:0.06em;color:#94A3B8;margin:0 0 8px">{tl("email_lead_forward_section_brief")}</h3>'
|
||||||
f'<table cellpadding="0" cellspacing="0" style="margin-bottom:20px">{brief_html}</table>'
|
f'<table cellpadding="0" cellspacing="0" style="margin-bottom:20px">{brief_html}</table>'
|
||||||
f'<h3 style="font-size:11px;text-transform:uppercase;letter-spacing:0.06em;color:#94A3B8;margin:0 0 8px">{t("email_lead_forward_section_contact")}</h3>'
|
f'<h3 style="font-size:11px;text-transform:uppercase;letter-spacing:0.06em;color:#94A3B8;margin:0 0 8px">{tl("email_lead_forward_section_contact")}</h3>'
|
||||||
f'<table cellpadding="0" cellspacing="0" style="margin-bottom:20px">{contact_html}</table>'
|
f'<table cellpadding="0" cellspacing="0" style="margin-bottom:20px">{contact_html}</table>'
|
||||||
f'{_email_button(f"{config.BASE_URL}/suppliers/leads", t("email_lead_forward_btn"))}'
|
f'{_email_button(f"{config.BASE_URL}/suppliers/leads", tl("email_lead_forward_btn"))}'
|
||||||
|
f'<p style="font-size:13px;color:#64748B;text-align:center;margin:8px 0 0;">'
|
||||||
|
f'{_t("email_lead_forward_reply_direct", lang, contact_email=contact_email)}</p>'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Send to supplier contact email or general contact
|
# Send to supplier contact email or general contact
|
||||||
@@ -410,7 +485,7 @@ async def handle_send_lead_forward_email(payload: dict) -> None:
|
|||||||
await send_email(
|
await send_email(
|
||||||
to=to_email,
|
to=to_email,
|
||||||
subject=subject,
|
subject=subject,
|
||||||
html=_email_wrap(body, lang),
|
html=_email_wrap(body, lang, preheader=", ".join(preheader_parts)),
|
||||||
from_addr=EMAIL_ADDRESSES["leads"],
|
from_addr=EMAIL_ADDRESSES["leads"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -434,18 +509,25 @@ 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"
|
first_name = (lead["contact_name"] or "").split()[0] if lead.get("contact_name") else "there"
|
||||||
|
|
||||||
body = (
|
body = (
|
||||||
f'<h2 style="margin:0 0 16px;color:#0F172A;font-size:20px;">{_t("email_lead_matched_heading", lang)}</h2>'
|
f'<h2 style="margin:0 0 8px;color:#0F172A;font-size:22px;">{_t("email_lead_matched_heading", lang)}</h2>'
|
||||||
|
f'<hr style="border:none;border-top:1px solid #E2E8F0;margin:0 0 16px;">'
|
||||||
f'<p>{_t("email_lead_matched_greeting", lang, first_name=first_name)}</p>'
|
f'<p>{_t("email_lead_matched_greeting", lang, first_name=first_name)}</p>'
|
||||||
f'<p>{_t("email_lead_matched_body", lang)}</p>'
|
f'<p>{_t("email_lead_matched_body", lang)}</p>'
|
||||||
f'<p style="font-size:13px;color:#64748B;">{_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")}</p>'
|
f'<p style="font-size:13px;color:#64748B;">{_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")}</p>'
|
||||||
|
# What happens next
|
||||||
|
f'<p style="font-size:14px;font-weight:600;color:#0F172A;margin:20px 0 8px;">{_t("email_lead_matched_next_heading", lang)}</p>'
|
||||||
|
f'<p style="font-size:14px;color:#334155;">{_t("email_lead_matched_next_body", lang)}</p>'
|
||||||
|
f'<p style="font-size:13px;color:#64748B;padding:10px 14px;background-color:#F0F9FF;'
|
||||||
|
f'border-radius:6px;border-left:3px solid #1D4ED8;">'
|
||||||
|
f'{_t("email_lead_matched_tip", lang)}</p>'
|
||||||
f'{_email_button(f"{config.BASE_URL}/dashboard", _t("email_lead_matched_btn", lang))}'
|
f'{_email_button(f"{config.BASE_URL}/dashboard", _t("email_lead_matched_btn", lang))}'
|
||||||
f'<p style="font-size:12px;color:#94A3B8;">{_t("email_lead_matched_note", lang)}</p>'
|
f'<p style="font-size:12px;color:#94A3B8;">{_t("email_lead_matched_note", lang)}</p>'
|
||||||
)
|
)
|
||||||
|
|
||||||
await send_email(
|
await send_email(
|
||||||
to=lead["contact_email"],
|
to=lead["contact_email"],
|
||||||
subject=_t("email_lead_matched_subject", lang),
|
subject=_t("email_lead_matched_subject", lang, first_name=first_name),
|
||||||
html=_email_wrap(body, lang),
|
html=_email_wrap(body, lang, preheader=_t("email_lead_matched_preheader", lang)),
|
||||||
from_addr=EMAIL_ADDRESSES["leads"],
|
from_addr=EMAIL_ADDRESSES["leads"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -464,8 +546,9 @@ async def handle_send_supplier_enquiry_email(payload: dict) -> None:
|
|||||||
message = payload.get("message", "")
|
message = payload.get("message", "")
|
||||||
|
|
||||||
body = (
|
body = (
|
||||||
f'<h2 style="margin:0 0 16px;color:#0F172A;font-size:20px;">'
|
f'<h2 style="margin:0 0 8px;color:#0F172A;font-size:22px;">'
|
||||||
f'{_t("email_enquiry_heading", lang, app_name=config.APP_NAME)}</h2>'
|
f'{_t("email_enquiry_heading", lang, contact_name=contact_name)}</h2>'
|
||||||
|
f'<hr style="border:none;border-top:1px solid #E2E8F0;margin:0 0 16px;">'
|
||||||
f'<p>{_t("email_enquiry_body", lang, supplier_name=supplier_name)}</p>'
|
f'<p>{_t("email_enquiry_body", lang, supplier_name=supplier_name)}</p>'
|
||||||
f'<table style="width:100%;border-collapse:collapse;margin:16px 0;font-size:14px">'
|
f'<table style="width:100%;border-collapse:collapse;margin:16px 0;font-size:14px">'
|
||||||
f'<tr><td style="padding:6px 0;color:#64748B;width:120px">{_t("email_enquiry_lbl_from", lang)}</td>'
|
f'<tr><td style="padding:6px 0;color:#64748B;width:120px">{_t("email_enquiry_lbl_from", lang)}</td>'
|
||||||
@@ -473,13 +556,14 @@ async def handle_send_supplier_enquiry_email(payload: dict) -> None:
|
|||||||
f'<tr><td style="padding:6px 0;color:#64748B;vertical-align:top">{_t("email_enquiry_lbl_message", lang)}</td>'
|
f'<tr><td style="padding:6px 0;color:#64748B;vertical-align:top">{_t("email_enquiry_lbl_message", lang)}</td>'
|
||||||
f'<td style="padding:6px 0;white-space:pre-wrap">{message}</td></tr>'
|
f'<td style="padding:6px 0;white-space:pre-wrap">{message}</td></tr>'
|
||||||
f'</table>'
|
f'</table>'
|
||||||
|
f'<p style="font-size:13px;color:#64748B;">{_t("email_enquiry_respond_fast", lang)}</p>'
|
||||||
f'<p style="font-size:13px;color:#64748B;">{_t("email_enquiry_reply", lang, contact_email=contact_email)}</p>'
|
f'<p style="font-size:13px;color:#64748B;">{_t("email_enquiry_reply", lang, contact_email=contact_email)}</p>'
|
||||||
)
|
)
|
||||||
|
|
||||||
await send_email(
|
await send_email(
|
||||||
to=supplier_email,
|
to=supplier_email,
|
||||||
subject=_t("email_enquiry_subject", lang, app_name=config.APP_NAME, contact_name=contact_name),
|
subject=_t("email_enquiry_subject", lang, contact_name=contact_name),
|
||||||
html=_email_wrap(body, lang),
|
html=_email_wrap(body, lang, preheader=_t("email_enquiry_preheader", lang)),
|
||||||
from_addr=EMAIL_ADDRESSES["transactional"],
|
from_addr=EMAIL_ADDRESSES["transactional"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -542,14 +626,18 @@ async def handle_generate_business_plan(payload: dict) -> None:
|
|||||||
user = await fetch_one("SELECT email FROM users WHERE id = ?", (user_id,))
|
user = await fetch_one("SELECT email FROM users WHERE id = ?", (user_id,))
|
||||||
if user:
|
if user:
|
||||||
body = (
|
body = (
|
||||||
f'<h2 style="margin:0 0 16px;color:#0F172A;font-size:20px;">{_t("email_business_plan_heading", language)}</h2>'
|
f'<h2 style="margin:0 0 8px;color:#0F172A;font-size:22px;">{_t("email_business_plan_heading", language)}</h2>'
|
||||||
|
f'<hr style="border:none;border-top:1px solid #E2E8F0;margin:0 0 16px;">'
|
||||||
f'<p>{_t("email_business_plan_body", language)}</p>'
|
f'<p>{_t("email_business_plan_body", language)}</p>'
|
||||||
|
f'<p style="font-size:14px;color:#334155;">{_t("email_business_plan_includes", language)}</p>'
|
||||||
f'{_email_button(f"{config.BASE_URL}/planner/export/{export_token}", _t("email_business_plan_btn", language))}'
|
f'{_email_button(f"{config.BASE_URL}/planner/export/{export_token}", _t("email_business_plan_btn", language))}'
|
||||||
|
f'<p style="font-size:13px;color:#64748B;text-align:center;margin:12px 0 0;">'
|
||||||
|
f'{_t("email_business_plan_quote_cta", language, quote_url=f"{config.BASE_URL}/{language}/leads/quote")}</p>'
|
||||||
)
|
)
|
||||||
await send_email(
|
await send_email(
|
||||||
to=user["email"],
|
to=user["email"],
|
||||||
subject=_t("email_business_plan_subject", language),
|
subject=_t("email_business_plan_subject", language),
|
||||||
html=_email_wrap(body, language),
|
html=_email_wrap(body, language, preheader=_t("email_business_plan_preheader", language)),
|
||||||
from_addr=EMAIL_ADDRESSES["transactional"],
|
from_addr=EMAIL_ADDRESSES["transactional"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ _IDENTICAL_VALUE_ALLOWLIST = {
|
|||||||
# "Budget", "Name", "Phase", "Investor" — same in both languages
|
# "Budget", "Name", "Phase", "Investor" — same in both languages
|
||||||
"sd_leads_budget", "sd_card_budget", "sd_unlocked_label_budget",
|
"sd_leads_budget", "sd_card_budget", "sd_unlocked_label_budget",
|
||||||
"sd_unlocked_label_name", "sd_unlocked_label_phase", "sd_stakeholder_investor",
|
"sd_unlocked_label_name", "sd_unlocked_label_phase", "sd_stakeholder_investor",
|
||||||
|
# Email lead forward labels — "Phase" and "Name" are identical in DE
|
||||||
|
"email_lead_forward_lbl_phase", "email_lead_forward_lbl_name",
|
||||||
# Listing form labels that are English brand terms / same in DE
|
# Listing form labels that are English brand terms / same in DE
|
||||||
"sd_lst_logo", "sd_lst_website",
|
"sd_lst_logo", "sd_lst_website",
|
||||||
# Boost option name — "Logo" is the same in DE
|
# Boost option name — "Logo" is the same in DE
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ class TestWorkerTask:
|
|||||||
mock_send.assert_called_once()
|
mock_send.assert_called_once()
|
||||||
call_args = mock_send.call_args
|
call_args = mock_send.call_args
|
||||||
assert call_args.kwargs["to"] == "entrepreneur@example.com"
|
assert call_args.kwargs["to"] == "entrepreneur@example.com"
|
||||||
assert "launching soon" in call_args.kwargs["subject"].lower()
|
assert "notify you at launch" in call_args.kwargs["subject"].lower()
|
||||||
assert "waitlist" in call_args.kwargs["html"].lower()
|
assert "waitlist" in call_args.kwargs["html"].lower()
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
Reference in New Issue
Block a user