Contact spaces

Embed
Preview of the card
<a href="https://forumulate.com/guides/contact-spaces"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/b4c3d5e6-ae02-4f37-b0f1-06547ad93b6e/social-card.svg?v=3e7ae33eec8b" alt="Contact spaces" width="600" height="315"></a>

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

frontDoor.title (default: "Contact us") and frontDoor.subtitle (default: "Send us a message"), both in the reader's language until you set them, plus an optional eyebrow line over both (theme.contact.head.eyebrow)

The intro

The space's home page: a page like any other, written in the editor. Its declared sections are intro, response-times, what-to-include and elsewhere.

Facts

The space's fields: email, hours, openingHours (a schedule, which lets the page say "Open now"), responseTarget, phone, address, offices, languages, escalationUrl. They follow the space's shape, in its order.

Topics

What the message is about (the departments door).

The form

Name, email and message, plus your own questions.

Offices

The offices field: one card per location, with address, phone, email, photo and hours.

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

standard (recommended)

Your words above the form, expectations beside it.

split

Your pitch and facts on one side, the form in a card on the other that stays in view. The page is wide unless you set a width.

router

Topic cards first. The form opens on the topic someone picks, or the card sends them where that topic is answered. Implies topic cards.

steps

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?

name

The topic's identity: what is posted, stored and filtered on. Up to 60 characters.

yes

email

Where its messages are mailed. Required.

no

description

One line on its card.

yes

icon

A glyph from the card icon list (GET /icons?for=card).

yes

fields

The form's questions this topic asks, by field name. Unset: every question. []: only name, email and message.

yes

redirectUrl

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 /path or an https:// address.

yes

redirectTo

Or one of the site's own places: docs, help, community, status, changelog, resolved to that space's address when the page is served. redirectUrl wins when both are set.

yes

booking

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 bookingUrl.

yes

successTitle, successMessage

This topic's own thank-you. {name} and {<field>} are filled from the sender's answers.

yes

accent

A hex colour for its card and its thank-you.

yes

autoAssign

The member a message about this topic is assigned to on arrival.

no

autoReply

The acknowledgement mailed to the sender for this topic, in place of the organization's.

no

customization

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

submitLabel

"Send message", in the reader's language

The button.

successTitle, successMessage

"Message sent", in the reader's language

The thank-you. A topic's own beats it.

redirectUrl

none

Send the sender to a page instead (/path or https://).

redirectWithAnswers

off

Add the topic, the name and short answers to redirectUrl as query parameters, so your thank-you page can say "Thanks, Ada". Never the email, the message, the subject or a file.

nextSteps

none

Up to four links under the thank-you (the docs, the status page).

workEmailOnly

off

Refuse consumer mailboxes (gmail.com, outlook.com…) with a 422 on email.

spamFilter

on

Score each message and file a likely-spam one in the Spam folder.

bookingUrl

none

A Cal.com or Calendly page under the thank-you.

retentionDays

kept until deleted

Delete messages older than this, nightly (1–3650).

closesAt, maxSubmissions, maxPerSender, closedMessage

open

When the form stops taking messages. See Forms.

translations

none

Your words per reader language. See below.

behavior

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's name, description, successTitle, successMessage;

  • fields.<name>: a question's label, help, placeholder, and options (a label per stored option);

  • channels: the band's heading and each of your links' label and description, 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

default (unset)

The form in an outlined card (pad 24), facts as tiles, topics as cards, offices as cards with a map link, channels as cards.

paper

Nothing: the page as it was before contact had looks (topics in a dropdown, no channels band).

minimal

The default's keys, plus a md heading; a flat, narrow form with underline fields; facts as a list; topics as a select; channels as a list; compact density; a plain thank-you.

bold

The default's keys, plus a centred xl heading; a raised form card (pad 32, radius 20, shadow md) with filled, lg fields (radius 12), a lg pill button (radius 999) the width of the form and a progress bar; raised topic and channel cards (radius 16), channels three to a row; spacious density; a centred thank-you.

directory

The default's keys, plus a wide page; facts beside the form; offices, channels and topics three to a row.

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

page.width

default (wide under split)

narrow (the old 608px text measure), default (the list column every other type reads at), wide, full.

head.show

shown

false drops the visible heading. The <title> stays.

head.align

start

start or center.

head.size

sm, md, lg, xl.

head.eyebrow

none

A short line over the title ("Talk to sales"), up to 60 characters. Translatable.

density

comfortable

compact, comfortable, spacious: space between bands and inside cards.

backdrop

none

A painted backdrop behind the page: layers (1–4 title-band layers: gradient, colour or picture), layersDark, extent (top, the default, fading out over height px, default 560; or page), heightPhone. A layer's rate (parallax) is refused here by name: a page backdrop does not scroll.

The form

Key

Default

What it does

form.card

outlined, pad 24

The card around the form: surface (flat, outline, raised), radius (px; unset = components.radius), pad, shadow (none, sm, md, lg). It rounds only the form card.

form.width

the column

narrow keeps the form to 30rem inside a wider page; full.

form.labelPosition

above

above, beside, inside (as the placeholder).

form.columns

2

Short fields two to a row from 640px (name beside email, and any two short questions of yours); 1 stacks them.

form.submitAlign

start

start, center, end.

form.fullWidthSubmit

off

The button as wide as the form.

form.field

components.input

style (outline, filled, underline), size (sm, md, lg), radius (0–32).

form.button

components.button

size, radius (0–999; 999 is a pill), style (solid, outline).

form.progress

bar

A multi-page form's progress: bar, steps, text, none.

success.look

card

card keeps the form's card; plain is text on the page.

success.reference

shown

Show the message's reference number.

success.align

start

start or center.

mobile.topics

as on desktop

Below 640px, topics as cards or a select.

mobile.stickySubmit

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

facts.look

tiles

list, chips, tiles, kicker, card.

facts.placement

above

above, beside (a column next to the form from 1024px), below.

facts.width

256

The beside column, 200–400 px.

topics.look

cards (select under paper)

cards to click, tabs over the form, or a select dropdown inside it.

topics.columns

as many as fit, up to 3

1–4 from 640px.

topics.card

The topic card.

offices.look

cards

cards or list.

offices.columns

as many as fit, up to 3

1–4.

offices.map

link

link adds a "Map" link (your own, or an OpenStreetMap search); none. It is always a link, never an embedded map, because a map tile would be a third-party request on every view.

offices.showImage

shown when there is one

The office photo.

offices.card

The office card.

channels.look

cards

cards or list.

channels.hide

none

Drop found channels by kind: docs, help, community, status, changelog.

channels.links

none

Up to 8 links of your own (label, url as /path, https://, mailto: or tel:, description): a Slack, a phone line.

channels.heading

"Other ways to get help"

The band's heading.

channels.columns

as many as fit

1–4.

channels.card

The channel card.

split.side

start or end: which side the form takes.

split.formWidth

420

The form column, 320–640 px.

split.sticky

on

Keep the form in view while the other column scrolls.

deflect.limit

3

How many docs articles to suggest while someone types (with the deflect switch on), 1–5.

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

title

"Contact us"

The heading (120 characters).

subtitle

"Send us a message"

The line under it (300 characters).

showChannels

on

The channels band.

showOffices

on

The offices band.

showOpenNow

on

"Open now · closes 17:00" from the openingHours schedule, on the office's own clock.

deflect

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

contact-above-form

index

Above the form and its topic picker.

contact-below-form

index

Below the form.

contact-aside

index

The side column beside the form (channels, hours, address).

contact-topic-after

index, topic

topic.label, topic.slug

Under every topic card (repeats), and above the form once a topic is chosen.

contact-sent

index, topic

topic.label, topic.slug

Wraps the "message sent" confirmation. replace draws your own.

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

data-mode

inline (default: the form where the tag is, or inside data-target="#id"), popup (a centred dialog), slider (a side panel), button (a floating launcher).

data-topic

Open the form on a topic.

data-position

left puts the slider or launcher on the left.

data-label

The launcher's words.

data-title

The frame's accessible title.

data-scheme

Pin light or dark.

data-prefill-<name>

Fill a question you marked prefill (data-prefill-company="Acme").

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

/<mount>

The contact page.

/<mount>/<topic-slug>

The page opened on a topic.

/<mount>/thanks

The thank-you as an address (noindex).

/<mount>/embed.js

The embed script.

/<mount>/<lang>/…

The page in another language, with hreflang alternates.

/<mount>/updates.xml, /<mount>/llms.txt

The space's updates feed and its AI text file.

GET /public/orgs/{orgId}/spaces/{spaceId}/contact/search?q=

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.contact key, 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

Contact_departments

GET /orgs/{orgId}/spaces/{spaceId}/contact/departments

List departments in a space

client.forms.contact.departments()

Contact_setDepartments

PATCH /orgs/{orgId}/spaces/{spaceId}/contact/departments

Update departments in a space

client.forms.contact.setDepartments()

Contact_formFields

GET /orgs/{orgId}/spaces/{spaceId}/contact/form-fields

List form fields in a space

client.forms.contact.formFields()

Contact_setFormFields

PATCH /orgs/{orgId}/spaces/{spaceId}/contact/form-fields

Update form fields in a space

client.forms.contact.setFormFields()

Contact_form

GET /orgs/{orgId}/spaces/{spaceId}/contact/form

Get the form in a space (contact)

client.forms.contact.form()

Contact_setForm

PATCH /orgs/{orgId}/spaces/{spaceId}/contact/form

Update the form in a space (contact)

client.forms.contact.setForm()

Contact_exportCsv

GET /orgs/{orgId}/spaces/{spaceId}/contact/export.csv

File of export (spaces contact)

client.forms.contact.exportCsv()

Contact_list

GET /orgs/{orgId}/spaces/{spaceId}/contact

Get the contact in a space

client.forms.contact.list()

Contact_setHandled

PATCH /orgs/{orgId}/spaces/{spaceId}/contact/{id}

Update the contact

client.forms.contact.setHandled()

Contact_setStatus

PATCH /orgs/{orgId}/spaces/{spaceId}/contact/{id}/status

Update the status in a space

client.forms.contact.setStatus()

Contact_assign

PATCH /orgs/{orgId}/spaces/{spaceId}/contact/{id}/assign

Assign the contact

client.forms.contact.assign()

Contact_replies

GET /orgs/{orgId}/spaces/{spaceId}/contact/{id}/replies

List replies in a space (contact)

client.forms.contact.replies()

Contact_reply

POST /orgs/{orgId}/spaces/{spaceId}/contact/{id}/reply

Create the reply in a space (contact)

client.forms.contact.reply()

Operation

Request

What it does

Shortcuts

PublicContactSearch_search

GET /public/orgs/{orgId}/spaces/{spaceId}/contact/search

Search a contact space's topics

client.forms.publicContactSearch.search()

Space Settings

Operation

Request

What it does

Shortcuts

SpaceSettings_setPreset

PUT /orgs/{orgId}/spaces/{spaceId}/preset

Replace the preset in a space

client.spaces.settings.setPreset()

The Forumulate teamUpdated