Skip to content

Add CRM contact sync endpoints (3DT-566)#6

Open
dluks wants to merge 9 commits into
mainfrom
3dt-566-implement-api-integration-for-adding-users-to-brevo
Open

Add CRM contact sync endpoints (3DT-566)#6
dluks wants to merge 9 commits into
mainfrom
3dt-566-implement-api-integration-for-adding-users-to-brevo

Conversation

@dluks
Copy link
Copy Markdown
Contributor

@dluks dluks commented Mar 4, 2026

Summary

  • Adds POST /contacts/subscribe and POST /contacts/sync-user API endpoints
  • Implements a CRMService abstraction (ABC) with a BrevoCRMService implementation, making it easy to swap CRM providers in the future
  • Adds BrevoConfig to centralized config system (BREVO_API_KEY, BREVO_SUBSCRIBER_LIST_ID, BREVO_USER_LIST_ID)
  • Passes BREVO_API_KEY to the API container in docker-compose

Context

The frontend will call these endpoints when:

  1. A user fills out the waitlist/mailing list form → POST /contacts/subscribe
  2. A user creates an account → POST /contacts/sync-user

This replaces the current approach of inserting directly into the public.subscribers Supabase table, making Brevo the single source of truth for subscriber contacts.

Test plan

  • Verified module imports load correctly
  • Tested add_subscriber and add_user end-to-end against Brevo API locally
  • Confirmed contact created in Brevo with correct list assignments and attributes
  • Deploy and test full server with all env vars configured
  • Test from frontend after companion PR is merged

Resolves 3DT-566


Note

Medium Risk
Adds a new public endpoint that calls an external CRM API and relies on new runtime configuration, so misconfiguration or Brevo failures can impact request behavior (503/502) but core auth/data paths are otherwise untouched.

Overview
Adds a new POST /contacts/subscribe API endpoint and wires it into the FastAPI app to sync mailing-list signups into Brevo (upserting contacts and attaching them to a configured list).

Introduces a small CRM abstraction (CRMService, ContactData) with a Brevo-backed implementation (BrevoCRMService) plus centralized BrevoConfig (BREVO_API_KEY, BREVO_SUBSCRIBER_LIST_ID) and updates production docker-compose to pass these env vars; also adds Serena project/docs files for local tooling guidance.

Written by Cursor Bugbot for commit ae64e43. This will update automatically on new commits. Configure here.

@linear
Copy link
Copy Markdown

linear Bot commented Mar 4, 2026

Comment thread trees_api/integrations/contacts/brevo.py Outdated
Comment thread trees_api/core/config.py Outdated
Comment thread deploy/docker-compose.api.prod.yml
Comment thread trees_api/routes/contacts/router.py Outdated
Comment thread trees_api/routes/contacts/router.py Outdated
Comment thread trees_api/routes/contacts/router.py

api_key: str
subscriber_list_id: int
user_list_id: int
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant config dataclass duplicates BrevoConfig fields

Low Severity

BrevoContactsConfig is a frozen dataclass with the same three fields (api_key, subscriber_list_id, user_list_id) as BrevoConfig in config.py. The router's _get_crm_service manually copies every field from one to the other. This duplication means any future field change requires updating both classes plus the mapping code, risking drift. BrevoCRMService could accept BrevoConfig directly or take the values as constructor parameters, eliminating the extra class entirely.

Additional Locations (1)

Fix in Cursor Fix in Web

dluks added 8 commits March 10, 2026 11:42
Add POST /contacts/subscribe and POST /contacts/sync-user endpoints
with a CRM service abstraction (CRMService ABC) backed by a Brevo
implementation. This allows the frontend to sync waitlist signups
and new user accounts to Brevo contact lists via the API.

Resolves 3DT-566
…trigger

User-to-CRM sync is now handled by a database trigger + edge function
instead of an unauthenticated API endpoint. Remove the endpoint, the
add_user service method, user_list_id config, and related dead code
(first_name/last_name on ContactData, is_configured on BrevoConfig).
@dluks dluks force-pushed the 3dt-566-implement-api-integration-for-adding-users-to-brevo branch from 7c0377b to ae64e43 Compare March 10, 2026 10:43
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.



class SubscribeRequest(BaseModel):
email: str
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No email validation allows invalid input causing misleading 502

Medium Severity

SubscribeRequest.email is typed as plain str with no format validation. Any arbitrary string (empty, whitespace, "not-an-email") is forwarded directly to the Brevo API, which returns a 400 error. The except Exception handler then converts this to a generic HTTPException(502, "CRM sync failed"), giving the caller a misleading server error instead of a 422 validation error. Using EmailStr from pydantic (with email-validator dependency) or a regex-constrained str field would catch invalid input at the API boundary.

Additional Locations (1)

Fix in Cursor Fix in Web

try:
crm.add_subscriber(ContactData(email=request.email, source=request.source))
except HTTPException:
raise
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreachable except HTTPException clause is dead code

Low Severity

The except HTTPException: raise clause inside the try block in subscribe() can never execute. The only call inside the try block is crm.add_subscriber(), which calls _upsert_contact — that method only raises httpx.HTTPStatusError, never FastAPI's HTTPException. The _get_crm_service() call (which does raise HTTPException(503)) sits outside the try block. This dead branch obscures the actual error flow.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant