newsletter: operator accounts are not stale subscribers
info@inference.coop is on the Members list but has no members row, so the reconcile classified the co-op's own inbox as a stale subscriber and would have unsubscribed it. The data model already distinguishes this: OPERATOR_EMAILS (default info@inference.coop,bot@inference.coop) are infrastructure accounts — the SSO owner and the bot — which the panel surfaces under "admins" and excludes from member counts. They live outside the members table by design. The Loomio reconciliation applies the same rule to the same accounts via LOOMIO_ALWAYS_KEEP ("must always remain in the group"). So operators become the third class here: neither members nor stale. Their subscription is left exactly as it is (never unsubscribed, and the reconcile never subscribes, so an operator who unsubscribes stays unsubscribed). The response reports how many were left, and the log line carries counts only.
This commit is contained in:
1 parent
250ca6e8f0
commit
e55d8a3f1e
1 file changed
+24
-12
+24
-12
@@ -417,16 +417,18 @@ async def listmonk_sync(email: str, subscribe: bool) -> None:
|
||||
async def listmonk_reconcile() -> dict:
|
||||
"""Reconcile the Members newsletter list against the active membership.
|
||||
|
||||
The newsletter list must mirror the portal's ACTIVE members. Subscribers
|
||||
who are not active members are stale (deleted accounts, never-activated
|
||||
test accounts, lapsed members) and are removed:
|
||||
- emails that exist in the members table but are inactive are taken off
|
||||
the list (the subscriber record stays as a suppression marker);
|
||||
- emails that are not members at all are taken off the list too, and the
|
||||
record is KEPT. A DELETE would destroy the subscriber's consent
|
||||
history, and a stray test account is indistinguishable from a real
|
||||
person who subscribed outside the portal (or whose row was removed and
|
||||
who may come back). Nobody is purged automatically.
|
||||
The newsletter list mirrors the portal's ACTIVE members. Subscribers who are
|
||||
not active members fall into two classes, and only one is acted on:
|
||||
- emails that exist in the members table but are inactive, and emails that
|
||||
are not members at all, are taken off the list (the subscriber record
|
||||
stays as a suppression marker). A DELETE would destroy the subscriber's
|
||||
consent history, and a stray test account is indistinguishable from a
|
||||
real person who subscribed outside the portal, so nobody is purged
|
||||
automatically;
|
||||
- OPERATOR_EMAILS — infrastructure accounts, which the panel surfaces
|
||||
under "admins" and excludes from member counts — are left exactly as
|
||||
they are. They are neither members nor stale. The Loomio
|
||||
reconciliation applies the same rule through LOOMIO_ALWAYS_KEEP.
|
||||
|
||||
Best-effort: never raises, so a newsletter outage can't break the sweep.
|
||||
"""
|
||||
@@ -474,9 +476,14 @@ async def listmonk_reconcile() -> dict:
|
||||
page += 1
|
||||
removed, emails = 0, []
|
||||
by_reason = {"inactive member": 0, "not a member": 0}
|
||||
# Operator accounts are neither members nor stale; never touch their
|
||||
# subscription (LOOMIO_ALWAYS_KEEP is the same rule for the forum).
|
||||
operators_kept = sum(
|
||||
1 for s in subs if (s.get("email") or "").lower() in OPERATOR_EMAILS
|
||||
)
|
||||
for s in subs:
|
||||
email = (s.get("email") or "").lower()
|
||||
if not email or email in active:
|
||||
if not email or email in active or email in OPERATOR_EMAILS:
|
||||
continue
|
||||
sid = s.get("id")
|
||||
# Both classes leave the list the same way: unsubscribe, keep the
|
||||
@@ -505,7 +512,12 @@ async def listmonk_reconcile() -> dict:
|
||||
removed,
|
||||
", ".join(f"{k}: {v}" for k, v in by_reason.items() if v),
|
||||
)
|
||||
return {"removed": removed, "emails": emails}
|
||||
if operators_kept:
|
||||
logger.info(
|
||||
"Listmonk reconcile: left %d operator address(es) on the list",
|
||||
operators_kept,
|
||||
)
|
||||
return {"removed": removed, "emails": emails, "operators_kept": operators_kept}
|
||||
except Exception as e:
|
||||
logger.warning("Listmonk reconcile error: %s", e)
|
||||
return {"removed": 0, "emails": [], "error": str(e)}
|
||||
|
||||
Reference in new issue
Block a user