--- name: pombo-events description: Wire a product event to a Pombo lifecycle flow. Shape the event payload, post it to POST /api/v1/events from the backend, and build the sequence that reacts with a trigger and a goal. Use when the user wants email to fire from something that happens in their app (signup, purchase, plan change, inactivity). --- # Event-triggered lifecycle in Pombo Your backend posts an event when something worth emailing about happens. A sequence (or a transactional template) bound to that event name picks it up. ## 1. Name the event Use `noun.past_tense`, lowercase: `user.signed_up`, `donation.received`, `subscription.cancelled`, `trial.expiring`. Names match case-insensitively. Pick the name once and use it in both the backend and the sequence trigger. ## 2. Shape the payload ``` POST /api/v1/events scope: events:write Authorization: Bearer { "event": "donation.received", "email": "dana@example.com", "properties": { "amount": "$50", "campaign_title": "Winter Appeal" } } ``` - `email` is required. Pombo finds the contact or creates one if the address is new. - Everything in `properties` becomes available in every email step of that enrollment as `{{ event.amount }}`, `{{ event.campaign_title }}`, and so on. - Send display-ready strings (`"$50"`, not `5000`). They are interpolated verbatim. - The payload is per enrollment. Two donations enroll the same person twice, each run carrying its own `amount`. Do not copy event data onto contact fields to work around this. There is nothing to work around. - Create the API key with `create_api_key` (scope `events:write`). Store it as a server-side secret. `revoke_api_key` if it leaks. ## 3. Add the call to the backend Fire the event where the state change is committed, not in a controller before the transaction lands. It should be: - **Safe to retry.** A network retry that double-posts enrolls twice. Dedupe on your side if the flow should only run once per real event. - **Non-blocking.** A queued job or after-commit hook, not inline in the request path. A 5xx from Pombo must not fail the user's action. - On the free plan, over-cap contact creation returns `202` with a note that the contact was not created. The event is accepted, not an error. Your backend should not start throwing when the user hits the plan limit. ## 4. Build the sequence - `create_sequence` with a custom event trigger set to the event name. - Add steps with the sequence step tools or `update_sequence`: each email step, each `wait` between them (`{ amount: 2, unit: "days" }`). A wait belongs before every step after the first, or they all send at once. - Use `{{ event.* }}` in the copy so each enrollment is personalised from its own payload. If a property is only sometimes sent, wrap the part of the sentence that depends on it in `{% if event.property != blank %}...{% endif %}`. Liquid conditionals work, just type the tag text in; there's no toolbar button for it. Otherwise a blank property leaves stray punctuation behind ("Thanks for your gift of !") that no marker will flag for you. - Set a goal (`compose_sequence_goal`): "event received" (for example `purchase.completed`) or "segment membership". This gives the flow a conversion rate and, if enabled, stops emailing someone who has already converted. - `validate_sequence` to catch a missing wait, an unresolved tag, a broken step. ## 5. Activate and verify - Leave the sequence in draft until the backend is posting real events. - `set_sequence_status` or `activate_sequence` to turn it on. - Post one real event and confirm the enrollment on the sequence page. ## One event, two outcomes The same event name can also drive a transactional email (see `pombo-transactional`), a receipt that must always send. A sequence and a transactional template can both bind to `donation.received` and both run. Use the transactional template for the must-send receipt and the sequence for the follow-up nudges. ## Done when The backend posts the event on the real state change, the sequence is active, and a live event produced a visible enrollment with the payload interpolated.