Contact spaces
A contact space is a way to reach you and somewhere the messages land. Its page is a form with your words above it, your hours and promises beside it, and your offices and other channels below it. Every message arrives in the space's inbox in the studio, where staff can read, assign and reply to it.
This guide covers the page: what it is made of, how it looks, and how it behaves. The form engine underneath (field types, branching, scoring, verified email, insights, integrations, closing rules) is shared with page forms and is described in Forms. The inbox (folders, spam scoring, CRM handoff, retention and data requests) is described there too.
What a contact page is made of
Part | Where it comes from |
|---|---|
The heading |
|
The intro | The space's home page: a page like any other, written in the editor. Its declared sections are |
Facts | The space's fields: |
Topics | What the message is about (the departments door). |
The form | Name, email and message, plus your own questions. |
Offices | The |
Channels | The organization's other public spaces (docs, help centre, community, status, changelog), found automatically, plus your own links. |
The front page is composed from bands: intro, topics and form, then offices and channels. Each band draws nothing until it has something to draw, so a space without topics or offices reads as a heading, an intro and a form. A front page you composed yourself without a topics band gets one placed automatically when the topic look is cards.
The heading's words are also the page's <title>, in the reader's language.
Arrangements
PUT /orgs/{orgId}/spaces/{spaceId}/preset picks one of four arrangements. Each renders differently and keeps every theme key.
Preset | What it does |
|---|---|
| Your words above the form, expectations beside it. |
| Your pitch and facts on one side, the form in a card on the other that stays in view. The page is |
| Topic cards first. The form opens on the topic someone picks, or the card sends them where that topic is answered. Implies topic cards. |
| Email and topic first, the rest after Continue. |
Two shape presets start the page's words for you: Sales desk (contact-sales: "Talk to sales", "Before we talk") and Support desk (contact-support: "Get help", "What to send"). Apply one with POST /orgs/{orgId}/spaces/{spaceId}/shape/presets/{name} or from the studio's shape panel.
Seven contact templates start a whole space: Atriumline Contact, Fieldnote Support, Halyard Contact, Meridian Help (directory look, topic router, offices), Merrow Sales, Relay Intake (bold look, a multi-page branching form with scale, rating, country and yes/no questions and per-topic endings) and Vantage Contact.
Topics
Topics are what the form asks first: Sales, Support, Billing, Press. They are stored where departments always were, so integrations that use PATCH /orgs/{orgId}/spaces/{spaceId}/contact/departments keep working. The list is replaced whole on every save, up to 20 topics, and two topics cannot share a name (compared without case).
Key | What it does | Public? |
|---|---|---|
| The topic's identity: what is posted, stored and filtered on. Up to 60 characters. | yes |
| Where its messages are mailed. Required. | no |
| One line on its card. | yes |
| A glyph from the card icon list ( | yes |
| The form's questions this topic asks, by field name. Unset: every question. | yes |
| Somewhere else answers this topic. The card links there, and picking it in the form says so instead of offering a form nobody reads. A | yes |
| Or one of the site's own places: | yes |
| A Cal.com or Calendly page offered after sending about this topic, with the sender's name and email filled in. Beats the form's own | yes |
| This topic's own thank-you. | yes |
| A hex colour for its card and its thank-you. | yes |
| The member a message about this topic is assigned to on arrival. | no |
| The acknowledgement mailed to the sender for this topic, in place of the organization's. | no |
| This topic's own look, CSS, share card and JSON-LD. See Per-topic overrides. | yes |
The address, the assignee and the auto-reply never leave the API: the reader is only told the public keys.
curl -X PATCH "https://forumulate.com/api/orgs/$ORG/spaces/$SPACE/contact/departments" \
-H "Authorization: Bearer $FORUMULATE_TOKEN" -H "Content-Type: application/json" \
-d '{ "departments": [
{ "name": "Sales", "email": "sales@acme.com",
"description": "Plans, pricing and procurement", "fields": ["company", "team_size"],
"booking": "https://cal.com/acme/intro", "successMessage": "Thanks {name}, we will call within a day.",
"accent": "#2563eb" },
{ "name": "Support", "email": "help@acme.com", "redirectTo": "help" }
] }'Every topic has its own address. /<mount>/<topic-slug> (for example /contact-us/talk-to-sales) opens the page on that topic, with its own title, description and canonical, which is what an ad or a pricing page's "talk to sales" should link to. The slug comes from the name, so renaming a topic moves it. ?topic=<name> does the same on the bare page. The studio's Embed on another site panel lists every topic's address.
The contact form
PATCH /orgs/{orgId}/spaces/{spaceId}/contact/form (needs space.update) sets the questions (fields, the form field model, including the upload types file, signature and video) and these:
Key | Default | What it does |
|---|---|---|
| "Send message", in the reader's language | The button. |
| "Message sent", in the reader's language | The thank-you. A topic's own beats it. |
| none | Send the sender to a page instead ( |
| off | Add the topic, the name and short answers to |
| none | Up to four links under the thank-you (the docs, the status page). |
| off | Refuse consumer mailboxes (gmail.com, outlook.com…) with a |
| on | Score each message and file a likely-spam one in the Spam folder. |
| none | A Cal.com or Calendly page under the thank-you. |
| kept until deleted | Delete messages older than this, nightly (1–3650). |
| open | When the form stops taking messages. See Forms. |
| none | Your words per reader language. See below. |
| none | One question at a time, verified email, blocked domains, partial answers, score endings, integrations (Forms). |
Multi-page forms. A page field splits the form into pages with a progress indicator (theme.contact.form.progress), Continue and Back, and Enter to advance. showIf branches on earlier answers, and a showIf on a page break skips the whole page.
The thank-you shows your words (or the topic's), with {name} filled in, the topic's accent colour, the message's reference number and any booking frame. The browser's address changes to /<mount>/thanks (with ?topic= and ?ref=), so an analytics goal that counts a URL counts a message, and a reload lands on the thank-you instead of an empty form. That address is never indexed. The page also fires the forumulate:form-sent event and a dataLayer push (Forms).
Uploads. file questions take up to three files (pictures, PDFs or plain text, 5 MB each). A signature is drawn on a pad and stored as a PNG (1 MB at most). A video answer is recorded in the browser or picked from the device (mp4 or webm, 50 MB at most). Every upload is judged by its bytes, not its name, and stored on our own storage. No third party sees it.
Languages
contactForm.translations holds your words per reader locale (fr, pt-BR, up to 20 languages). Each entry may set:
title,subtitle,eyebrow: the heading;topics.<name>: the topic'sname,description,successTitle,successMessage;fields.<name>: a question'slabel,help,placeholder, andoptions(a label per stored option);channels: the band'sheadingand each of your links'labelanddescription, keyed by the original label;nextSteps: each link's words, keyed by the original label;submitLabel,successTitle,successMessage,closedMessage.
A reader in that language sees your translation (exact locale first, then the language). What is posted and stored stays the original, so routing, the inbox and the CSV keep one vocabulary. The words we supply ourselves (buttons, errors, "Message sent") follow the page's language without any setup. In the studio every translatable string has a box under Other languages.
How it looks: theme.contact
theme.contact is the part of the space theme only a contact space reads. Set it with PATCH /orgs/{orgId}/spaces/{spaceId}/theme. A misspelt key is a 422 naming its path. Setting theme.docs on a contact space is refused too: the contact page is not drawn by the docs reader, so none of those keys would do anything. The contact page's own width and heading are theme.contact.page and theme.contact.head.
The layering is: the look's patch, then the site theme, then your space theme, then a topic's own customization. Every key you set wins.
Looks are patches
theme.contact.look picks a starting point. A look sets independent keys, all of which you can set yourself, and every key works under every look.
Look | What it sets |
|---|---|
| The form in an outlined card (pad 24), facts as |
| Nothing: the page as it was before contact had looks (topics in a dropdown, no channels band). |
| The default's keys, plus a |
| The default's keys, plus a centred |
| The default's keys, plus a |
PATCH /orgs/{orgId}/spaces/{spaceId}/theme
{ "contact": { "look": "bold" } }Because a look is only keys, you can combine parts of looks. None of these is a template's look.
1. The directory layout with minimal's quiet form.
{ "contact": {
"look": "directory",
"form": { "card": { "surface": "flat" }, "field": { "style": "underline" } },
"success": { "look": "plain" }
} }2. bold without the shouting: its form and button, a normal heading, topics as tabs.
{ "contact": {
"look": "bold",
"head": { "align": "start", "size": "md" },
"topics": { "look": "tabs" },
"density": "comfortable"
} }3. minimal with topic cards and a split form that sits on the left.
{ "contact": {
"look": "minimal",
"topics": { "look": "cards", "columns": 2, "card": { "surface": "outline", "radius": 8 } },
"split": { "side": "start", "formWidth": 480 }
} }(The split keys take effect with the split arrangement.)
The keys
Page and heading
Key | Default | What it does |
|---|---|---|
|
|
|
| shown |
|
|
|
|
|
| |
| none | A short line over the title ("Talk to sales"), up to 60 characters. Translatable. |
|
|
|
| none | A painted backdrop behind the page: |
The form
Key | Default | What it does |
|---|---|---|
| outlined, pad 24 | The card around the form: |
| the column |
|
|
|
|
| 2 | Short fields two to a row from 640px (name beside email, and any two short questions of yours); |
|
|
|
| off | The button as wide as the form. |
|
|
|
|
|
|
|
| A multi-page form's progress: |
|
|
|
| shown | Show the message's reference number. |
|
|
|
| as on desktop | Below 640px, topics as |
| off | Pin the button to the bottom of a phone screen. |
Facts, topics, offices and channels. Each band has its own card (cardSurfaceSchema: surface, radius, pad, shadow). An unset radius falls back to components.radius, and pad is honoured on flat and unset surfaces.
Key | Default | What it does |
|---|---|---|
|
|
|
|
|
|
| 256 | The |
|
|
|
| as many as fit, up to 3 | 1–4 from 640px. |
| The topic card. | |
|
|
|
| as many as fit, up to 3 | 1–4. |
|
|
|
| shown when there is one | The office photo. |
| The office card. | |
|
|
|
| none | Drop found channels by kind: |
| none | Up to 8 links of your own ( |
| "Other ways to get help" | The band's heading. |
| as many as fit | 1–4. |
| The channel card. | |
|
| |
| 420 | The form column, 320–640 px. |
| on | Keep the form in view while the other column scrolls. |
| 3 | How many docs articles to suggest while someone types (with the |
Front-door switches
settings.frontDoor holds the page's switches (studio: the front-door panel; API: PUT /orgs/{orgId}/spaces/{spaceId}/preset with frontDoor).
Key | Default | What it does |
|---|---|---|
| "Contact us" | The heading (120 characters). |
| "Send us a message" | The line under it (300 characters). |
| on | The channels band. |
| on | The offices band. |
| on | "Open now · closes 17:00" from the |
| off | Suggest articles from the organization's docs while the reader types their message. |
Per-topic overrides
A topic's customization is the look of the page as that topic opens it (/<mount>/<topic-slug> and ?topic=): its own theme keys, CSS, share card (shareImageUrl, ogTitle, ogDescription, twitterCard) and JSON-LD. A "Talk to sales" link shared on its own gets its own card.
The theme it accepts is the shared tokens (colours, palette, fonts, type, components, layout, motion, content width) plus the contact namespace. Any other type's namespace (docs, post, forum…) is refused with a 422 that names it. Saving a topic patches its stored customization: a key you leave out is kept, null clears it.
{ "name": "Sales", "email": "sales@acme.com",
"customization": {
"theme": { "primaryColor": "#0f766e", "contact": { "form": { "button": { "radius": 999 } } } },
"ogTitle": "Talk to Acme sales"
} }In the studio, each topic in the Topics list has its own style card.
Theme outlets
Contact pages have their own outlets for theme components:
Outlet | Pages | Context | What it is |
|---|---|---|---|
| index | Above the form and its topic picker. | |
| index | Below the form. | |
| index | The side column beside the form (channels, hours, address). | |
| index, topic |
| Under every topic card (repeats), and above the form once a topic is chosen. |
| index, topic |
| Wraps the "message sent" confirmation. |
Putting the form somewhere else
On your own pages. The Contact form block places this space's form on any page of any space (Forms).
On another site. Every contact space serves an embed script at /<mount>/embed.js. One tag:
<script src="https://acme.com/contact-us/embed.js" data-mode="popup" data-topic="Sales" async></script>
<button data-forumulate-contact>Talk to sales</button>Attribute | What it does |
|---|---|
|
|
| Open the form on a topic. |
|
|
| The launcher's words. |
| The frame's accessible title. |
| Pin light or dark. |
| Fill a question you marked |
Any element with data-forumulate-contact, or window.ForumulateContact.open(), opens the popup. The form itself is a frame of the contact page's bare ?embed=1 render, served from your contact page's own address, so the host site never touches the answers. The frame reports its height to the script, which accepts messages from that one origin only. When a message is sent, the script fires forumulate:contact-sent on itself so the host page can react. The studio's Embed on another site panel writes the tag for you, lists every topic's address, and has a downloadable QR code for the page.
Other addresses
Address | What it is |
|---|---|
| The contact page. |
| The page opened on a topic. |
| The thank-you as an address (noindex). |
| The embed script. |
| The page in another language, with |
| The space's updates feed and its AI text file. |
| What the site header's search box asks on a contact page. |
In the studio
Settings → Contact form: Topics (each with its card line, icon, questions it asks, where it is answered elsewhere, booking page, its own thank-you, colour, auto-assign, auto-reply and style card); the form's questions; the button label and thank-you heading; Links under the thank-you; Who may write, spam, booking, retention, languages; When the form closes; Behaviour; Other languages; and the form's insights.
Settings → Embed on another site: the embed tag, topic addresses and QR code.
Housekeeping → Inbox: the messages.
Design: every
theme.contactkey, the look and the arrangement.The intro is an ordinary page: edit it in place, with snapshots, trash and import like any other page.
What you need to set up
Nothing is required for a working contact page. A few features use your own accounts:
Mail. Staff alerts, auto-replies, replies from the inbox and verification codes go out through your organization's mail provider. Without one, messages still land in the inbox.
Booking. A Cal.com or Calendly account and its booking page URL.
CRM handoff. The HubSpot or Salesforce integration (Forms).
Integrations. Your own Airtable, Notion or Google Sheets token for
behavior.integrations(Forms).A captcha, if you want one: your own Turnstile or hCaptcha keys.
In the API
Every operation below is also reachable from the other surfaces without a dedicated command. forumulate api <Operation> calls it from the CLI, the JavaScript SDK has a typed method for each one (the last column names it; the Python and Go clients are generated from the same spec), and an agent reaches the organization-scoped ones through the MCP call_api tool. Where a CLI command or MCP tool exists too, the last column names it. Operation names link to the API reference.
Contact
Operation | Request | What it does | Shortcuts |
|---|---|---|---|
| List departments in a space |
| |
| Update departments in a space |
| |
| List form fields in a space |
| |
| Update form fields in a space |
| |
| Get the form in a space (contact) |
| |
| Update the form in a space (contact) |
| |
| File of export (spaces contact) |
| |
| Get the contact in a space |
| |
| Update the contact |
| |
| Update the status in a space |
| |
| Assign the contact |
| |
| List replies in a space (contact) |
| |
| Create the reply in a space (contact) |
|
Public Contact Search
Operation | Request | What it does | Shortcuts |
|---|---|---|---|
| Search a contact space's topics |
|
Space Settings
Operation | Request | What it does | Shortcuts |
|---|---|---|---|
| Replace the preset in a space |
|