feat: GDPR compliance — anonymization, unsubscribe, consent tracking

- Add consent_given_at and anonymized_at fields + Alembic migration (002)
- Add GET /waitlist/unsubscribe endpoint (HMAC token, anonymizes PII)
- Add cleanup.py: cron-able script to anonymize unconfirmed entries after 48h
- Clear IP address on email confirmation (no longer needed)
- Add unsubscribe link in confirmation email footer
- Record consent timestamp on signup
- Add 4 new tests (unsubscribe, consent timestamp)
- Update .env.example, schemas
This commit is contained in:
Roberto Musso
2026-04-11 19:41:27 +02:00
parent 5f79ce87f9
commit 352e25d651
6 changed files with 232 additions and 14 deletions

View File

@@ -24,7 +24,7 @@ def _headers() -> dict[str, str]:
}
async def send_confirmation_email(email: str, confirm_url: str) -> bool:
async def send_confirmation_email(email: str, confirm_url: str, unsubscribe_url: str = "") -> bool:
"""Send a double opt-in confirmation email. Returns True on success."""
if not settings.brevo_configured:
logger.warning("Brevo not configured — skipping confirmation email for %s***", email[:3])
@@ -37,7 +37,7 @@ async def send_confirmation_email(email: str, confirm_url: str) -> bool:
},
"to": [{"email": email}],
"subject": "Confirm your spot on the adiuvAI waitlist",
"htmlContent": _confirmation_html(confirm_url),
"htmlContent": _confirmation_html(confirm_url, unsubscribe_url),
}
try:
@@ -78,7 +78,7 @@ async def add_contact_to_list(email: str) -> bool:
return False
def _confirmation_html(confirm_url: str) -> str:
def _confirmation_html(confirm_url: str, unsubscribe_url: str = "") -> str:
"""Email template aligned with the adiuvAI landing page brand."""
return f"""\
<!DOCTYPE html>
@@ -211,6 +211,7 @@ def _confirmation_html(confirm_url: str) -> str:
<p style="margin:8px 0 0;font-size:12px;color:#c8c3cd;">
<a href="https://adiuvai.com" style="color:#8a8ea9;text-decoration:underline;
text-underline-offset:2px;">adiuvai.com</a>
{f'&ensp;·&ensp;<a href="{unsubscribe_url}" style="color:#8a8ea9;text-decoration:underline;text-underline-offset:2px;">Unsubscribe</a>' if unsubscribe_url else ''}
</p>
</td></tr>