Skip to the documentation
API

How forms work

Sign-up forms put people into your audiences. Build one here, share it as a link, embed it on any site, or post to it from your own code.

A draft and a live copy

A form keeps two copies of what visitors see. document is the draft you edit, and publishedDocument is what the hosted page, the embed and the subscribe endpoint use. Saving changes only the draft, and POST /forms/{id}/publish copies it live. hasUnpublishedChanges tells you the two differ.

  • draft: never published. Nobody can see it or sign up through it.
  • live: published and taking sign-ups.
  • paused: published but closed. The page shows the closed message from its copy and sign-ups are refused.

settings are different: where sign-ups go, double opt-in, the sender, the thank-you behaviour and who is told about each sign-up. They apply as soon as they are saved, published or not.

Fields

A document is a list of fields, the copy around them and a style. Each input field has a key, which is the name its answer is posted under: a lower-case letter followed by up to 39 lower-case letters, digits or underscores, unique in the form, and never starting with oe_. Every form has exactly one email field, keyed email and required.

  • Inputs: email, text, textarea, number, phone, url and date.
  • Choices: select, radio and checkboxes, each with options.
  • checkbox for a yes or no, and consent for a box that has to be ticked when it is required.
  • audiences lets the person choose lists: each option value is an audience id in this workspace.
  • hidden carries a value the visitor never sees: the one your page posts, or else its defaultValue, such as a campaign name.
  • heading, paragraph and divider only lay the form out and post nothing.

Set mapsTo to firstName, lastName or name on a text field, and the answer becomes the name of the contact the sign-up creates. A contact that already exists keeps its name. Every answer is kept on the submission, with the label it had, so old submissions still read right after the form changes.

Putting a form on a page

Publish first. Then use whichever of the three suits the page. All of them reach the same form and count the same sign-ups. Views are counted only on the hosted page and the embed, so sign-ups through your own HTML or code raise the conversion rate.

  • The hosted page at url, a page of its own you can link to from anywhere.
  • The embed script, which puts the form on your page in a frame that sizes itself.
  • Your own HTML or code, posting the answers to subscribeUrl.
Embed
<script src="https://openemail.uk/embed/form.js" data-openemail-form="frm_3b9d2e7a1c4f80d56e2a9b14" async></script>
HTML
<form action="https://api.openemail.uk/subscribe/frm_3b9d2e7a1c4f80d56e2a9b14" method="post">  <input type="email" name="email" required>  <div style="position:absolute;left:-9999px" aria-hidden="true">    <input type="text" name="oe_website" tabindex="-1" autocomplete="off">  </div>  <button type="submit">Subscribe</button></form>

A plain HTML form is redirected to the thank-you page, or to settings.redirectUrl. Code that posts JSON gets a JSON answer instead, described on the subscribe page.

Double opt-in

With settings.doubleOptIn on, a sign-up is stored as pending and the person is emailed a link from settings.senderAddress, an address of this workspace. They join the audiences when they open it. The link works for seven days. A person who unsubscribed from an audience earlier is only subscribed again this way, never by a single opt-in form. Signing up again before confirming updates the pending sign-up rather than adding another.

To protect the people you email, one address gets at most one confirmation per form every ten minutes and five a day across the workspace. You can approve a pending sign-up yourself, or send it a fresh link.

Who can see what

  • Reading needs forms:read and changing needs forms:write. Approving a sign-up also needs contacts:write, because it adds a contact.
  • Anything that makes a form send mail also needs emails:send: turning on double opt-in, setting the sender or the confirmation email, publishing or resuming a double opt-in form, and resending a confirmation.
  • An API key and the owner see every form in the workspace. An app a member connected sees only the forms that member made, and only the audiences that member made plus the built-in ones.
  • Creating, updating, publishing, resuming or duplicating a form whose sender or notify addresses lie outside what a limited key or app may reach answers 422 capability_unsupported.
  • A key or app limited to some addresses may only set a sender and notify addresses it holds.
  • Deleting a form asks an OAuth app for a verification code, as other destructive changes do. An API key never needs one.

form.submitted and form.confirmed webhooks tell your systems about every sign-up. A webhook limited to some addresses never receives them, because sign-ups belong to the whole workspace.

Bots and limits

  • A field named oe_website is a trap for bots: keep it empty and off screen, as the HTML above does. A sign-up that fills it gets a normal answer and is dropped.
  • The hosted page and the embed also check a signed start time, and a form sent back faster than a person could fill it in is dropped the same way.
  • One network can post 40 sign-ups in ten minutes, across all your forms and whatever the outcome. After that JSON callers get 429 form_rate_limited, and a plain HTML form goes to the hosted page with ?outcome=limited.
  • A workspace holds 100 forms by default.

From code, the terminal and agents

Everything here is also in the SDK as openemail.forms and in the CLI as openemail forms, and the MCP server has form tools, so an agent can build, publish and watch a form. Over MCP the client writes the design itself and passes it as document.

Posting to subscribeUrl from your own code needs no credential. Send the answers as JSON, add the page the form was on as oe_source, leave out oe_started, and send oe_website empty or not at all. Every sign-up from one network shares the limit of 40 every ten minutes, so a server relaying sign-ups for many people reaches it quickly: add people you already know with the audience import instead.

Your inbox,
on your own terms.

Email infrastructure for businesses, AI, agents and personal email. Built for scale, privacy and control. Everything email should have had from day one.

© 2026 OpenEmail. All rights reserved.