feat: Brevo double opt-in + contact sync
- Add brevo.py: transactional email sending + contact list sync via Brevo API - Add token.py: stateless HMAC-signed confirmation tokens (no DB migration needed) - Update routes.py: POST /waitlist sends confirmation email, GET /waitlist/confirm verifies token - Update config.py: Brevo + confirmation settings (gracefully disabled when BREVO_API_KEY is empty) - Update .env.example with new Brevo and confirmation variables - Add httpx dependency - Add 8 new tests (token roundtrip/expiry/tamper, confirm endpoint, Brevo mock)
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import secrets
|
||||
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
@@ -7,6 +9,17 @@ class Settings(BaseSettings):
|
||||
RATE_LIMIT_PER_MINUTE: int = 5
|
||||
ENVIRONMENT: str = "development"
|
||||
|
||||
# Brevo (email)
|
||||
BREVO_API_KEY: str = ""
|
||||
BREVO_SENDER_EMAIL: str = "noreply@adiuvai.com"
|
||||
BREVO_SENDER_NAME: str = "adiuvAI"
|
||||
BREVO_LIST_ID: int = 0 # Brevo contact list ID for waitlist subscribers
|
||||
|
||||
# Confirmation link
|
||||
CONFIRM_SECRET: str = secrets.token_hex(32) # override in production .env
|
||||
CONFIRM_BASE_URL: str = "https://waitlist.adiuvai.com"
|
||||
CONFIRM_TOKEN_EXPIRY_HOURS: int = 48
|
||||
|
||||
model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
|
||||
|
||||
@property
|
||||
@@ -17,5 +30,9 @@ class Settings(BaseSettings):
|
||||
def sync_database_url(self) -> str:
|
||||
return self.DATABASE_URL.replace("+asyncpg", "+psycopg2")
|
||||
|
||||
@property
|
||||
def brevo_configured(self) -> bool:
|
||||
return bool(self.BREVO_API_KEY)
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
Reference in New Issue
Block a user