Documentation Index
Fetch the complete documentation index at: https://docs.qflowhub.io/llms.txt
Use this file to discover all available pages before exploring further.
Two patterns
You can send a templated email to a guest in two ways:A. Atomic
POST /api/guest with commsTemplateId. Use on the ticket-purchase happy path — no window where the guest exists in our DB without their email queued.B. Two-step
POST /api/comms/send. Use when the guest already exists (bulk-imported, “I lost my ticket” resends, or retries after transient API-side failures).A. Atomic — create + send
The send is triggered as part of guest creation — single round-trip:The
id field is yours to choose. It must be a globally-unique GUID. We recommend generating it on your side (one GUID per logical action), because retrying the same call with the same id is idempotent — see Idempotency below.comms.sent (or comms.failed) at your webhook URL within seconds, followed by comms.delivered / comms.opened etc. as the email lifecycle progresses.
What happens internally
Attendee row created
Or returned as-is if your
id is already in use — see Idempotency.Synchronous validation
Template exists, belongs to your account, your account is verified for sending.
attendeeInvitationId so you can match them to the original send.
B. Two-step — send to an existing attendee
Use this whenever the attendee already exists in Qflow — for first sends to bulk-imported guests, for resends (“I lost my ticket” / customer requests duplicate), and for retries after transient failures.comms.sent, comms.delivered, etc.) at your registered webhook URL.
When POST /api/comms/send rejects
The endpoint inspects the most recent AttendeeInvitation for this (attendee, template) pair. If the previous attempt is in a state where retrying won’t help, you get 422 with a structured body — sending again would just hit the same wall.
| Previous state | Sends? | Why |
|---|---|---|
| No previous attempt | ✅ | First send |
| Delivered successfully | ✅ | New attempt — “I lost my ticket” use case |
| Transient API-side failure (we never reached SendGrid) | ✅ | Likely temporary; retry is sensible |
bounced | ❌ 422 | Recipient mail server rejected the address — won’t change |
dropped (after delivery to SendGrid) | ❌ 422 | SendGrid suppression / hard fail |
spam_reported | ❌ 422 | Recipient marked as spam — anti-spam policy blocks resend |
unsubscribed | ❌ 422 | Compliance — recipient has opted out |
failureType is one of: bounced, dropped, spam_reported, unsubscribed.
Idempotency
The caller-suppliedid GUID is the idempotency key. Generate one per logical action (e.g. one per ticket purchase) and reuse it on any retry of the same logical action.
- Same
id→ no duplicate send. The first call creates the attendee + queues the send. Subsequent calls with the sameidreturn the existing attendee with no new send. - Different
id→ new send. If you need to re-send to the same person for a separate logical action, generate a new GUID.
correlationId
A free-form string (≤128 chars) you supply per send. We echo it back verbatim in every webhook event for that send (data.correlationId). Use it to correlate events to your own records (order ids, ticket numbers, etc.) without having to store our GUIDs.
It’s optional. If you don’t supply one, correlationId is null in the webhook payload.