Outbound
Banners, posts, checklists, surveys, tours and campaigns, and how each is targeted.
Outbound covers six surfaces, one outbound_messages row each. Every message has a kind, a state of draft, live or paused, a name, a schedule window in starts_at and ends_at, and an audience written as the contacts search DSL. An empty audience is everyone, serialized as null.
Authoring is the API under settings:read and settings:write. Serving is the messenger's job: each resolve runs the audience against the contact's row and their receipts, so a dismissed banner stays hidden while a post stays in the news list.
Banner
A one-line announcement with an optional link. The action is a link, never a callback the widget would have to trust. style is accent, neutral or warning; position is top or bottom; dismissible decides whether a dismiss button appears, and a dismissed banner is held back on later loads.
curl -X POST http://127.0.0.1/api/outbound_messages \
-H 'content-type: application/json' \
-H 'authorization: Bearer fk_<key>' \
-d '{"kind":"banner","name":"New feature","content":{"text":"We shipped offline mode","style":"accent","action":{"label":"Read more","url":"https://example.com/blog"},"dismissible":true,"position":"top"},"audience":null,"starts_at":null,"ends_at":null}'Post
A news-list entry. The body is the article editor's Tiptap JSON, rendered by the same renderer, so a post can carry the same structure as an article. The author is set server-side from the member who saved it, and a client cannot claim authorship. reactions_enabled turns on a single reaction per visitor.
Checklist
A title, a short description and up to fifty ordered steps. A step's body is plain text and its action is a link. Completion is manual, ticked off by the visitor, or url_visited, ticked off when the visitor follows the step's link through the tracked redirect.
Survey
An optional one-line intro and up to twenty five questions, answered in the messenger one at a time. Question types are nps, rating_5, text and select; a select carries its options and no other type does. Results aggregate per question, with text answers paged.
Tour
A walk of selector-addressed steps over the customer's own site. url_match is the glob the host page must match before the tour starts. Each step names a CSS selector, a title, a plain-text body, a position and an advance rule: next_button or click_target. The widget renders the pointer; the body is plain text so there is no markup for it to trust.
Campaign
An email blast to the audience. The content is a subject, a Tiptap body and a from_address_id that must be a verified sending address. Taking a campaign live enqueues the fan-out, which sends at most FIKADESK_CAMPAIGN_RATE_PER_SECOND messages a second. Results are the four receipt counts: sent, delivered, bounced and complained, where a complaint is a form of bounce but the raw count stays beside it.
Targeting
The audience is a contacts search predicate, the same DSL POST /api/contacts/search compiles. Leave it null to reach everyone. The messenger resolves each surface against the contact's row and their first-seen receipt, so each kind holds back what the visitor already acted on: banners they dismissed, surveys they submitted or dismissed, tours they finished, posts they saw.
Unsubscribe
Campaign mail carries an RFC 8058 one-click unsubscribe at /api/outbound/unsubscribe/{token}. The token in the path is the whole authentication, so a token that does not verify answers 401 with no body.