101:async def google_start_impl(user):
102-    """Shared implementation: generate state, persist, return Google's authorization URL."""
103-    try:
104-        client = gmail_service.resolve_client_config()
105-    except gmail_service.GmailOauthError as e:
106-        # Be honest: no client config -> no authorization URL (never fabricate one)
107-        raise HTTPException(400, f"Google OAuth is not configured: {e}")
108-    state = gmail_service.generate_state()
109-    if _oauth_redis is not None:
110-        _oauth_redis.setex(f"oauthstate:{state}", 600, json.dumps({"user_id": user.id}))
111-    else:
112-        _pending_oauth_states[state] = {"user_id": user.id}
113-    auth_url = gmail_service.build_authorization_url(client, state)
114-    return {"authorization_url": auth_url}
115-
116-
117-_pending_oauth_states: dict = {}
118-
119-
120-@router.get("/google/start")
121-async def google_start(request: Request, user=Depends(get_current_user)):
122:    return await google_start_impl(user)
123-
124-
125-@router.get("/google/callback")
126-async def google_callback(request: Request, code: str = "", state: str = "", error: str = "",
127-                          db: AsyncSession = Depends(get_db)):
128-    """Handle Google's response: verify state, exchange code for tokens, store encrypted."""
129-    from app.models import GmailAccount, OAuthClientConfig, OAuthToken
130-
131-    if error:
132-        return HTMLResponse(f"<h3>Google authorization failed: {error}</h3>"
133-                            f"<p><a href='{settings.FRONTEND_BASE_URL}/gmail-accounts.html'>Back</a></p>")
134-    if not code or not state:
135-        raise HTTPException(400, "Missing code or state")
136-
137-    # 1) verify state (CSRF protection of OAuth handshake)
138-    user_id = None
139-    if _oauth_redis is not None:
140-        raw = _oauth_redis.get(f"oauthstate:{state}")
=== path
''
=== plan keys
201:async def materialize_campaign(db: AsyncSession, campaign: Campaign, recipient_rows: Sequence[Recipient],
202-                               preview: bool = False, preview_size: int = 0) -> dict:
203-    """Create CampaignRecipient rows with generated values & assignments.
204-
205-    This is the core deterministic plan: which subject/template/phone/account each recipient gets,
206-    plus generated invoice numbers (unique per campaign), amounts, shipping.
--
275:        plan.append({"seq": seq, "email": r.email, "invoice": inv_num, "amount": g.amount,
276-                     "subject": subj.subject if subj else "", "template": tpl.name if tpl else None,
277:                     "phone": phone, "account": account.email if account else None,
278-                     "account_id": account.id if account else None,
279-                     "recipient_id": r.id, "subject_id": subj.id if subj else None,
280-                     "template_id": tpl.id if tpl else None,
281-                     "invoice_number": inv_num,
282-                     "invoice_date": g.invoice_date, "due_date": g.due_date,
