A contact form your customers actually finish.
Drop in the ready-made form with two lines of HTML, point your own form at one small API, or paste a single AI prompt and let your assistant build it. Every message lands in one calm inbox — with whatever your app knows about the customer right beside it.
Two lines. A live form.
Paste these wherever the form should appear. The script registers the element; the element renders a warm, working form wired to your inbox.
<script src="https://usepresence.com/embed/form.js" defer></script>
<presence-form form-id="YOUR_FORM_ID"></presence-form>
No build step, no npm install, no keys sitting exposed in your markup — the form id is safe for the browser. Signed in, the snippet in the embed guide already carries your real form id.
Add it the way that fits your stack.
Three honest paths to the same inbox. Pick one — you can always add another source later.
Drop in the component
The ready-made <presence-form>
element renders in a shadow DOM and takes your colors, dark
mode, and fonts through a handful of CSS variables. Best when
you want a form now.
Post to one endpoint
Already have a form? Point its submit handler at a single
POST.
Your markup, your styles, your framework — usepresence just
receives the message and any attributes you attach.
Let your assistant wire it
Paste one self-contained prompt into Claude or Cursor, or connect over MCP and let your agent set it all up — team, source, form, and a test message — in a single conversation.
Bring your AI assistantIf you'd rather write it yourself.
The whole integration is one POST
with an email and a message. Send extra fields as
attributes — plan,
locale, account id — and they show up next to every
conversation from that customer.
The pk_
key is publishable: safe in client-side code, and it can only
create conversations — never read them.
curl -X POST https://usepresence.com/api/v1/conversations \
-H "Authorization: Bearer pk_YOUR_PUBLISHABLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"message": "The monthly export just spins forever.",
"attributes": { "plan": "Pro" }
}'
Extra fields become context.
Whatever you attach to a submission rides along and lands in the customer panel — so you know who you're talking to before you answer.
It merges, per customer
Customers are identified by email per team. The same person writing twice is one customer with two conversations, and each submission updates the keys it sends.
Rendered for you
No schema to configure. Send plan,
customer_since,
an account id — up to 30 plain values — and they appear
generically beside the thread.
A source per surface
Give your app, marketing site, and staging their own publishable keys. See where each conversation came from and filter the inbox by source.
All of it flows into one calm inbox — conversations, not tickets.
Questions, answered.
Do I need a build step or an npm install?
No. The embeddable form is two lines of HTML — a script tag and a <presence-form> element. There is no bundler, no package to install, and no framework required. Paste it into a static page and it works.
Is it safe to put the key in my HTML?
Yes. Both the embed form id and the publishable key are meant for the browser. A publishable key can only create conversations and append to ones whose id you already have — it can never read your inbox. Revoking a source turns its form off without touching anything else.
Can I match it to my own design?
The embeddable form renders in a shadow DOM and reads a set of CSS custom properties — accent, background, text, border, radius, font — so you can match your colors, dark mode, and fonts without your styles leaking in or the form leaking out. If you build the form yourself against the API, it is entirely your markup.
What are customer attributes?
Anything your app knows about the person writing — plan, signup date, account id, locale. Send them alongside the message and they appear in the customer panel next to every one of their conversations, so you have context before you reply. Up to 30 keys, plain string, number, or boolean values.
Is there a hosted form page on my own sub-domain?
Not yet. Today you integrate by embedding the component, calling the API, or letting your AI assistant wire it up over MCP. A hosted contact page on your own sub-domain is on the roadmap, not shipped.
Give them somewhere to write. You'll be there.
Two lines of HTML, one API call, or one prompt. Whichever you pick, the first message lands in minutes.