Membership: newsletter opt-in at signup and on the profile

An unticked checkbox on registration subscribes the verified address to
the listmonk list (preconfirmed) once the account exists. The profile
page gets a subscribe/unsubscribe toggle, and verified email changes
follow through to listmonk. Newsletter outages never block accounts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 20:27:40 -05:00
co-authored by Claude Opus 5.5
parent 5954ec1061
commit 98e09627ed
10 changed files with 463 additions and 5 deletions
+9
View File
@@ -115,6 +115,15 @@ if SHKEEPER_URL:
raise ImproperlyConfigured("SHKEEPER_URL must be a certificate-verified HTTPS origin.")
if any(not re.fullmatch(r"[A-Z0-9][A-Z0-9-]{0,31}", value) for value in SHKEEPER_CURRENCIES):
raise ImproperlyConfigured("SHKEEPER_CURRENCIES must contain API crypto identifiers such as BTC or ETH-USDT.")
LISTMONK_URL = os.environ.get("LISTMONK_URL", "").rstrip("/")
LISTMONK_API_USER = os.environ.get("LISTMONK_API_USER", "")
LISTMONK_API_TOKEN = os.environ.get("LISTMONK_API_TOKEN", "")
LISTMONK_LIST_NAME = os.environ.get("LISTMONK_LIST_NAME", "eom.dev")
LISTMONK_ENABLED = bool(LISTMONK_URL and LISTMONK_API_USER and LISTMONK_API_TOKEN and LISTMONK_LIST_NAME)
if LISTMONK_URL:
listmonk_origin = urlsplit(LISTMONK_URL)
if listmonk_origin.scheme != "https" or not listmonk_origin.hostname or listmonk_origin.username or listmonk_origin.path or listmonk_origin.query or listmonk_origin.fragment:
raise ImproperlyConfigured("LISTMONK_URL must be a certificate-verified HTTPS origin.")
tier_file = os.environ.get("MEMBERSHIP_TIERS_FILE", "")
MEMBERSHIP_TIERS = json.loads(os.environ["MEMBERSHIP_TIERS_JSON"]) if "MEMBERSHIP_TIERS_JSON" in os.environ else json.loads(Path(tier_file).read_text(encoding="utf-8")) if tier_file else {}
if not isinstance(MEMBERSHIP_TIERS, dict):