July 7 - Update invitation with Brand Name, based on hostname
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
app/utils/mail_utils.py
|
||||
-----------------------
|
||||
Helpers for constructing outbound email sender identities.
|
||||
|
||||
`branded_sender()` returns a Flask-Mail sender as a ``(display_name, address)``
|
||||
tuple so outbound branded email (currently the customer invitation) shows a
|
||||
per-domain identity that tracks the host the user is on — WITHOUT risking
|
||||
deliverability.
|
||||
|
||||
Two independent things vary by domain:
|
||||
|
||||
1. DISPLAY NAME (what the recipient sees, e.g. "Gov Services QC")
|
||||
Always applied. Costs nothing, needs no DNS. This is the safe default.
|
||||
|
||||
2. FROM ADDRESS (the actual @domain, e.g. jqc.noreply@govservicesinc.com)
|
||||
Only used for domains that are KNOWN to authorize this mail server
|
||||
(the authenticated sender's own domain, plus anything you add to
|
||||
SENDER_AUTHORIZED_DOMAINS). Every other domain keeps the authenticated
|
||||
address so the message still passes SPF/DMARC and delivers.
|
||||
|
||||
Result, with no DNS work:
|
||||
Invited from jqc.govservicesinc.com ->
|
||||
From: "Gov Services QC" <jqc.noreply@ltservicesinc.com>
|
||||
(branded NAME, deliverable authenticated ADDRESS)
|
||||
|
||||
Once govservicesinc.com's DNS authorizes this mail server (SPF include + DKIM),
|
||||
add it to SENDER_AUTHORIZED_DOMAINS and it upgrades to:
|
||||
From: "Gov Services QC" <jqc.noreply@govservicesinc.com>
|
||||
|
||||
See CLAUDE.md rule 64.
|
||||
"""
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from flask import current_app
|
||||
|
||||
# ── Per-domain display names (edit as new brands launch) ─────────────────────
|
||||
# Keyed by registrable domain. A domain not listed here falls back to
|
||||
# DEFAULT_BRAND_NAME so the From never renders an ugly auto-derived label.
|
||||
BRAND_NAMES = {
|
||||
'ltservicesinc.com': 'LT Services QC',
|
||||
'govservicesinc.com': 'Gov Services QC',
|
||||
'efs.com': 'EFS QC',
|
||||
}
|
||||
DEFAULT_BRAND_NAME = 'Janitorial QC'
|
||||
|
||||
# ── Domains cleared to use a BRANDED FROM ADDRESS ────────────────────────────
|
||||
# Add a registrable domain here ONLY after its DNS authorizes this mail server
|
||||
# (SPF `include:` + DKIM). Until then the domain still gets its branded display
|
||||
# NAME but sends from the authenticated address, so it always delivers.
|
||||
# The authenticated sender's own domain is always treated as authorized and does
|
||||
# NOT need to be listed here.
|
||||
SENDER_AUTHORIZED_DOMAINS = {
|
||||
# 'govservicesinc.com', # ← uncomment once SPF+DKIM are live for it
|
||||
# 'efs.com',
|
||||
}
|
||||
|
||||
|
||||
def _authenticated_sender() -> str:
|
||||
"""The SMTP identity we authenticate as — always deliverable."""
|
||||
return (current_app.config.get('MAIL_DEFAULT_SENDER')
|
||||
or current_app.config.get('MAIL_USERNAME')
|
||||
or 'jqc.noreply@janitorialqc.local')
|
||||
|
||||
|
||||
def _host_domain(base_url: str | None) -> str | None:
|
||||
"""Registrable domain (last two labels) of base_url, or None if unparseable."""
|
||||
base = (base_url or current_app.config.get('APP_BASE_URL', '') or '').strip().rstrip('/')
|
||||
if not base:
|
||||
return None
|
||||
netloc = urlparse(base).netloc or urlparse('//' + base).netloc
|
||||
host = netloc.split('@')[-1].split(':')[0].strip().lower() # drop userinfo/port
|
||||
if not host or '.' not in host:
|
||||
return None
|
||||
labels = [l for l in host.split('.') if l]
|
||||
return '.'.join(labels[-2:]) if len(labels) >= 2 else host
|
||||
|
||||
|
||||
def branded_sender(base_url: str | None = None):
|
||||
"""Return a ``(display_name, address)`` sender tuple for branded email.
|
||||
|
||||
The display name always tracks the host's domain; the address is branded
|
||||
only for the authenticated domain and any SENDER_AUTHORIZED_DOMAINS, and
|
||||
otherwise stays the authenticated address so the mail still delivers.
|
||||
|
||||
Falls back to the bare authenticated sender string whenever the host cannot
|
||||
be parsed (localhost, empty, no dot), so this can never yield an invalid From.
|
||||
"""
|
||||
auth_sender = _authenticated_sender()
|
||||
if '@' not in auth_sender:
|
||||
return auth_sender
|
||||
local, auth_domain = auth_sender.rsplit('@', 1)
|
||||
auth_domain = auth_domain.lower()
|
||||
|
||||
domain = _host_domain(base_url)
|
||||
if not domain:
|
||||
# Unknown host → safe authenticated address, generic brand name.
|
||||
return (DEFAULT_BRAND_NAME, auth_sender)
|
||||
|
||||
display_name = BRAND_NAMES.get(domain, DEFAULT_BRAND_NAME)
|
||||
|
||||
if domain == auth_domain or domain in SENDER_AUTHORIZED_DOMAINS:
|
||||
address = f'{local}@{domain}' # branded address — DNS-authorized
|
||||
else:
|
||||
address = auth_sender # keep deliverable authenticated address
|
||||
|
||||
return (display_name, address)
|
||||
Reference in New Issue
Block a user