Data Flow¶
This document shows the complete data flows through Subscribe Flow, including multi-tenant request handling, authentication, billing, and email sending.
Multi-Tenant Request Flow¶
Every API request is resolved to an organization context before any business logic executes.
sequenceDiagram
participant C as Client
participant API as FastAPI Gateway
participant Auth as Auth Middleware
participant Service as Business Logic
participant DB as PostgreSQL
C->>API: Request with API Key or Session
API->>Auth: Authenticate
alt API Key Auth
Auth->>DB: Lookup api_keys WHERE key_hash = hash(key)
DB-->>Auth: api_key.organization_id
else Magic Link Session
Auth->>Auth: Decode session cookie
Auth-->>Auth: user_id + organization_id
end
Auth-->>API: OrgContext (org_id, tier, limits)
API->>Service: Business Operation
Service->>DB: SELECT ... WHERE organization_id = org_id
DB-->>Service: Org-scoped results
Service-->>API: Response DTO
API-->>C: HTTP Response Magic Link Auth Flow¶
Dashboard users authenticate via magic links (passwordless).
sequenceDiagram
participant U as User (Browser)
participant API as FastAPI
participant MLS as MagicLinkService
participant Email as Resend API
participant DB as PostgreSQL
Note over U,DB: 1. Request Magic Link
U->>API: POST /auth/magic-link {email}
API->>MLS: request_magic_link(email)
MLS->>MLS: Generate token (secrets.token_urlsafe)
MLS->>MLS: Hash token (SHA-256)
MLS->>DB: INSERT INTO magic_link_tokens (email, token_hash, expires_at)
MLS->>Email: Send email with login link
Email-->>MLS: 200 OK
MLS-->>API: Success (always, even if user not found)
API-->>U: "Check your email"
Note over U,DB: 2. Verify Magic Link
U->>API: GET /auth/verify?token={token}
API->>MLS: verify_magic_link(token)
MLS->>MLS: Hash token (SHA-256)
MLS->>DB: SELECT FROM magic_link_tokens WHERE token_hash = ...
DB-->>MLS: Token record (email, expires_at, used_at)
MLS->>DB: UPDATE magic_link_tokens SET used_at = now() (one-time use)
MLS->>DB: SELECT user, org_membership
DB-->>MLS: User + Organization
MLS->>MLS: Create JWT session token
MLS-->>API: Session token + user info
API-->>U: Set-Cookie: session={jwt} + user data Stripe Billing Flow¶
Organizations subscribe to plans via Stripe Checkout.
sequenceDiagram
participant U as User (Dashboard)
participant API as FastAPI
participant BS as BillingService
participant Stripe as Stripe API
participant DB as PostgreSQL
participant Beat as Celery-Beat
Note over U,Beat: 1. Start Checkout
U->>API: POST /api/v1/billing/checkout {price_id}
API->>BS: create_checkout_session(org_id, price_id)
BS->>Stripe: stripe.checkout.Session.create()
Stripe-->>BS: checkout_url
BS-->>API: {checkout_url}
API-->>U: Redirect to Stripe Checkout
Note over U,Beat: 2. Payment Complete (Webhook)
Stripe->>API: POST /webhooks/stripe (checkout.session.completed)
API->>BS: handle_checkout_completed(session)
BS->>Stripe: Retrieve subscription details
Stripe-->>BS: Subscription + price_id
BS->>BS: Map price_id to tier (free/starter/professional)
BS->>DB: UPDATE organizations SET tier, stripe_customer_id, stripe_subscription_id
DB-->>BS: Success
BS->>DB: UPDATE organizations SET subscriber_limit, email_limit, ...
BS-->>API: 200 OK
Note over U,Beat: 3. Monthly Email Counter Reset
Beat->>Beat: Cron: 1st of month, 00:00 UTC
Beat->>DB: UPDATE organizations SET emails_sent_this_month = 0 Email Sending Flow with Per-Org From Address¶
Each organization can have its own send domain and from address.
sequenceDiagram
participant C as Client
participant API as FastAPI
participant ES as Email Service
participant EU as Email Utils
participant DB as PostgreSQL
participant Resend as Resend API
C->>API: POST /api/v1/emails/send {template, to}
API->>ES: send_email(org_id, template_slug, to)
ES->>DB: SELECT organization WHERE id = org_id
DB-->>ES: Organization (send_domain, from_name, from_email)
ES->>EU: resolve_from_address(org)
alt Custom Domain (Professional)
EU-->>ES: "Company Name <newsletter@custom-domain.com>"
else Default Domain
EU-->>ES: "Company Name <org-slug@default-send-domain.com>"
end
ES->>DB: SELECT template WHERE slug = ... AND organization_id = org_id
DB-->>ES: Template + MJML
ES->>ES: Render template with variables
ES->>Resend: POST /emails {from, to, subject, html}
Resend-->>ES: {id: "email_id"}
ES->>DB: UPDATE organizations SET emails_sent_this_month += 1
ES-->>API: {id, status: "queued"}
API-->>C: 202 Accepted Preference Center Flow¶
sequenceDiagram
participant User as User
participant PC as Preference Center
participant API as FastAPI Gateway
participant Service as Business Logic
participant DB as PostgreSQL
participant Redis as Redis Cache
participant Resend as Resend API
Note over User,Resend: 1. User opens Preference Center
User->>PC: Click on email link with token
PC->>API: GET /preference-center?token=xyz
API->>Service: Validate Token & Fetch Preferences
Service->>DB: SELECT subscriber, tags (org-scoped)
DB-->>Service: Subscriber + Tags
Service-->>API: Preferences Data
API-->>PC: JSON Response
PC->>User: Display current subscriptions
Note over User,Resend: 2. User adds a new tag
User->>PC: Click "Add Interest: Newsletter"
PC->>API: POST /subscribers/{id}/tags
API->>Service: Add Tag to Subscriber
Service->>DB: INSERT INTO subscriber_tags
DB-->>Service: Success
Service->>Resend: Update Contact Topics
Resend-->>Service: 200 OK
Service->>Redis: Invalidate Cache
Service-->>API: Success
API-->>PC: 201 Created
PC->>User: Toast "Successfully subscribed!"
Note over User,Resend: 3. Resend sends webhook
Resend->>API: POST /webhooks/resend (contact.updated)
API->>Service: Process Webhook
Service->>DB: UPDATE subscriber (org-scoped)
Service->>Redis: Invalidate Cache
Service-->>API: 200 OK
API-->>Resend: Webhook Acknowledged Caching Strategy¶
- Cache Key:
org:{org_id}:subscriber:{id}:preferences - TTL: 5 minutes
- Invalidation: On every change (tag add/remove, subscriber update)
- Fallback: On cache miss, fall back to PostgreSQL query
- Magic Link Tokens: Stored in PostgreSQL (
magic_link_tokenstable) with 15-minute expiry - Rate Limit Counters: Per-organization counters in Redis