Spec How the triggers are integrated
Preferred path: Direct Brevo API via contact attributes. The backend writes a status attribute on the contact, the Brevo automation listens on the entry point "A contact updates an attribute".
Option A, Direct Brevo API (preferred)
The backend calls the Brevo Contact API at every touchpoint and writes a status attribute on the contact. The Brevo automation starts, branches or stops on COACH_STAGE or CLIENT_STAGE. Fewer layers, fewer error sources, no Make in between.
Why attribute updates and not the pure Events API: the attribute is at the same time the stored status. The dropout emails need exactly this condition, for example "is the contact still at account_created after 48h?". A pure event via POST /v3/events leaves no status behind, which would then have to be maintained separately.
Option B, Webhook → Make → Brevo (fallback only)
The backend fires a webhook to a Make endpoint, Make writes the Brevo field. Only use this if Option A is not feasible.
What GoSocial receives from us, to keep the integration near copy-and-paste
To keep the dev effort minimal, the marketing side does not just hand over a description. We hand over:
- A ready-made helper function, for example
notifyBrevo(email, attributes), with the Brevo API key handling and retry logic already built in. Delivered in the backend's language once GoSocial confirms the stack.
- The exact list of the 7 call sites with the precise attribute values for each, see section 2.
GoSocial's remaining work per trigger: find the spot in the codebase where that event fires, call the helper, bind the real values (contact email, name, timestamp), and follow the 5 rules in the critical review. That is integration, not authoring.
PUT https://api.brevo.com/v3/contacts/coach@example.com
Header: api-key: <BREVO_API_KEY>
Body: {
"attributes": {
"COACH_STAGE": "interview_booked",
"INTERVIEW_AT": "2026-05-28T14:30:00Z"
}
}
# First touchpoint per role (coach account active / client quiz submit):
# create the contact first, after that PUT updates are enough.
POST https://api.brevo.com/v3/contacts
Body: { "email": "...",
"attributes": { "FIRSTNAME": "...", "LASTNAME": "...", "COACH_STAGE": "account_created" },
"listIds": [<LIST>], "updateEnabled": true }
Naming convention
Every event name is prefixed with its audience, coach_* or client_*, so the role is unambiguous in code, logs and the Brevo automation. The Brevo attributes follow the same rule: COACH_* and CLIENT_*. No event or attribute ships without a role prefix.
Spec The 7 triggers, this is what GoSocial builds
Each row is one touchpoint where the backend makes one Brevo API call. Each trigger is tagged with its audience: Coach or Client. Below the 7 core triggers, a separate amber block shows 2 conditional ones that depend on the open decisions.
Coach 5 triggers
| # |
Touchpoint in the adavida backend + event name |
Brevo attribute update |
Starts or stops in Brevo |
| 1 |
Coach account is active on the platform (sign-up form done, password set, 2FA passed)
coach_account_created |
Create contact + assign list, set FIRSTNAME/LASTNAME, COACH_STAGE = account_created |
Starts the coach onboarding automation: P1 welcome after 1h, then a single "did you finish signing up?" dropout at 24h to 48h |
| 2 |
Coach books the vetting interview slot, this implies the entire pre-vetting Smart Onboarding is done (URL scrape, basic data, qualification docs, motivation check, AGB)
coach_interview_booked |
COACH_STAGE = interview_booked, INTERVIEW_AT (real slot time) |
Ends the "did you finish signing up?" dropout; starts the date-based 24h-before-interview reminder |
| 3 |
Vetting decision is made in the admin tool
coach_vetting_decision |
On approved: COACH_STAGE = coach_approved, VETTING_RESULT = approved, APPROVED_AT. On not approved: VETTING_RESULT = not_approved (stage stays) |
On approved: starts the "welcome to the club" email + the 72h facelift dropout. On not approved: the transactional rejection email is sent (dev team) and all coach automations stop via the VETTING_RESULT exit condition |
| 4 |
Profile is completed and published live
coach_profile_live |
COACH_STAGE = coach_live, LIVE_AT |
Ends the facelift dropout; starts the "profile online" email + the 7-day retention timer |
| 5 |
First session request received
coach_session_request_received |
FIRST_REQUEST_AT, increment SESSION_REQ_COUNT by 1 |
Ends the retention dropout "0 requests after 7 days" |
Why no Smart Import, docs or motivation-check trigger: the pre-vetting flow is all-or-nothing today. A dropout there resets the user to the start of Smart Onboarding on next login. Sub-triggers would not power useful nudges and are deliberately omitted for MVP.
Client 2 triggers
| # |
Touchpoint in the adavida backend + event name |
Brevo attribute update |
Starts or stops in Brevo |
| 1 |
Quiz result gate submitted (first name, last name, email)
client_quiz_submitted |
Create contact + assign list, set FIRSTNAME/LASTNAME, CLIENT_STAGE = quiz_submitted, QUIZ_AT. Optional QUIZ_MODE, QUIZ_BUDGET, QUIZ_STYLE only after GDPR sign-off |
Starts the client conversion sequence (24h / 48h / 72h / 5 days / 7 days) |
| 2 |
First session requested or booked
client_session_requested |
CLIENT_STAGE = booked, LAST_REQUEST_AT |
Stops the entire conversion sequence |
The client side stays minimal on purpose: the goal is purely to drive the first free get-to-know call. Past the quiz submit there is no further activity to cluster until a booking happens. The coach monthly recap and the client newsletter need no dev trigger, they run inside Brevo on a date or list basis.
Pending decision 2 conditional triggers, NOT part of the 7 above
These two are needed only if the two open decisions in the critical review (bucket 2) go to option (b). With the recommended option (a), neither is needed and the trigger count stays at exactly 7. They are shown here so GoSocial sees them upfront and knows what they would mean, not so they get built now.
| Conditional item |
Belongs to |
What it actually means |
Only needed if |
coach_metrics_sync new recurring event |
Coach monthly recap email "Your numbers at a glance" |
A nightly job writes metric attributes (SESSIONS_30D, REQUESTS_30D, REVENUE_30D) onto the contact, so the recap email can show the coach's real numbers instead of generic content. |
Decision 1 goes to option (b). Option (a) keeps the recap generic, no event and no extra attributes. |
MATCH_1/2/3_NAME + MATCH_1/2/3_URL extra fields on client_quiz_submitted |
Client email "These coaches fit you" (matches again, 48h) |
Coach display data (name and profile URL per match) written on the existing client_quiz_submitted call, so the email can render the actual coaches instead of just a link. |
Decision 2 goes to option (b) and GDPR allows persisting matches. Option (a) just links back to the result page, no fields needed. |
Attribute catalog, to be created in Brevo
Valentin creates these custom attributes and the lists in Brevo and gives the exact names and list IDs to GoSocial. The dev team does not invent its own names.
| Attribute | Type | Values / note |
COACH_STAGE | Text | account_created → interview_booked → coach_approved → coach_live |
VETTING_RESULT | Text | approved | not_approved. Used as a global exit condition on coach automations. |
CLIENT_STAGE | Text | quiz_submitted → booked |
FIRSTNAME, LASTNAME, EMAIL | Text | Brevo standard fields, set on contact creation, used for personalization in every email |
INTERVIEW_AT, APPROVED_AT, LIVE_AT, FIRST_REQUEST_AT, QUIZ_AT, LAST_REQUEST_AT | Date/time | ISO 8601 (UTC). INTERVIEW_AT must carry the real slot time, the 24h-before-interview reminder runs date-based on it. |
SESSION_REQ_COUNT | Number | Counter of session requests, used to exit the 7-day retention dropout |
QUIZ_MODE, QUIZ_BUDGET, QUIZ_STYLE | Text | Optional, only after GDPR sign-off (matching answers may count as health data) |
Implementation requirements
- Always identify the contact via email (primary key in Brevo)
- Keep calls idempotent, do not fire the same status twice
- Retry on errors: 3 attempts with backoff
- Timestamps as ISO 8601 (UTC) in the payload, for the dropout calculation in Brevo
- Staging environment against a test Brevo account, separate from production
- Brevo API key server-side as an environment variable, never in the frontend
- Definition of done: all 7 triggers fire correctly in staging and the attributes update as specified, verified together with Valentin and Janosch
- Plus the 5 rules in the critical review below, they are part of the build
Critical review Is this enough to be fully hands-off?
The goal is that after this handoff, the marketing side runs everything in Brevo without dev input, unless a new trigger is added. This section challenges the briefing against that goal honestly. Three buckets: what is covered, what needs a decision now, and the rules that prevent dev rework later.
1, Fully covered, we can self-serve in Brevo
- The simplified coach pillars (account created → interview booked → vetting decision → profile live → first request) and the full client conversion sequence (24h / 48h / 72h / 5d / 7d). Pure state plus time logic, Brevo handles start, wait and stop entirely from
COACH_STAGE / CLIENT_STAGE and the timestamps. No dev input ever needed to change timing or copy.
- The single coach onboarding dropout ("did you finish signing up?", 24h to 48h after
account_created while COACH_STAGE is still account_created) replaces the previous three sub-step dropouts. Simpler and accurate to the all-or-nothing reality of the platform.
- The "approved" and "profile live" activation emails, the 72h facelift dropout and the 7-day retention email, all driven by stage values and
SESSION_REQ_COUNT.
- The 24h-before-interview reminder runs as a "one day before
INTERVIEW_AT" send. Brevo's date trigger works at day granularity, which is fine for a prep email. Edge case we accept: if a coach books a slot less than ~1 day out, that email does not fire.
- Newsletter: list-based, no trigger at all.
2, Not yet covered, decide before kickoff (these are the 2 conditional triggers in section 2)
-
Coach monthly recap, "Your numbers at a glance". Brevo can schedule the monthly send, but it has no data to fill it. It only knows the contact attributes. A real numbers recap (sessions held, requests, revenue, profile views) needs those metrics pushed to Brevo as attributes on a schedule, which is the conditional trigger
coach_metrics_sync.
Decision: (a) descope the recap to generic content (tips, social proof, no personal numbers), no extra dev work, or (b) add the recurring metrics-sync trigger now.
Recommendation: (a) for launch, revisit after go-live.
-
Client "These coaches fit you", matches again, 48h. A contact attribute can hold a match ID, but an email cannot render a coach's name, photo and profile link from an ID alone. That needs the conditional
MATCH_*_NAME / MATCH_*_URL fields.
Decision: (a) the email links back to the client's result page ("see your matches again"), no per-coach rendering and no extra fields, or (b) if GDPR allows persisting matches, store the display fields per match.
Recommendation: (a). It also sidesteps the open GDPR question.
3, The 5 rules, without these the briefing breaks and the dev team gets pulled back in
- Stage moves forward only. A retried or late event must never overwrite a later stage with an earlier one. That would re-trigger the welcome and the signup dropout, the user gets spammed. Write a new stage only if it is further along than the current value.
- Standard fields on contact creation. The first call per role must set
FIRSTNAME, LASTNAME, EMAIL. Every email is personalized, without these the greeting is blank.
- Consent and list assignment. Onboarding and dropout nudges are operational, the newsletter needs marketing opt-in. Contacts must be created with the correct consent state and list, agreed with Valentin, or Brevo will not send or will send unlawfully.
- Deep-link URLs for the CTAs. Every email has a CTA back into the right step (book interview, complete profile, etc.). Marketing cannot invent these URLs. GoSocial provides the canonical deep-link URLs per step, and if a login or token is required, exposes it as a per-contact attribute (e.g.
LOGIN_URL or a magic link). This is the one thing we need back from GoSocial.
- Account deletion equals Brevo deletion. When a user deletes their account or requests GDPR erasure, the backend must also delete or anonymize the Brevo contact, so we never email ghosts.
The previous rules "skip counts as advance" and "strictly linear onboarding" are no longer needed: with the simplified set we do not track Smart Onboarding sub-steps, so there is nothing to skip or order.
4, What the adavida marketing side owns, not GoSocial
- Building and maintaining all Brevo automations, every wait time, every branch and exit condition.
- All email copy, design, subject lines and tests.
- Creating the custom attributes and the lists in Brevo, and handing the exact names and list IDs to GoSocial.
- Brevo sending-domain authentication (SPF, DKIM, DMARC) and sender setup. Prerequisite for sending, not a GoSocial task.
- Using
VETTING_RESULT = not_approved as a global exit condition to stop coach automations.
- The Join adavida marketing list (DOI from the landing page) is a separate track and not in this briefing. It runs entirely on the marketing side via Brevo, no GoSocial dependency.
Bottom line
Yes, this makes the marketing side hands-off, on two conditions. First, the two emails in bucket 2 are decided now (recommended: descope both, zero extra dev work). Second, the 5 rules in bucket 3 are built in, not treated as nice-to-have. With that, the only thing that brings GoSocial back into scope is implementing a genuinely new trigger.