---
title: "Forms"
description: "`forms.list`, `get`, `create`, `update`, `delete`, `publish`, `pause`, `resume`, `duplicate`, `analytics`, `listStarters`, `getStarter`, `listSubmissions`, `getSubmission`, `deleteSubmission`, `deleteSubmissions`, `approveSubmission`, `resendConfirmation` and `subscribe`."
url: "https://openemail.uk/docs/sdk/forms"
area: "SDK"
category: "Mailbox"
---

# Forms

`forms.list`, `get`, `create`, `update`, `delete`, `publish`, `pause`, `resume`, `duplicate`, `analytics`, `listStarters`, `getStarter`, `listSubmissions`, `getSubmission`, `deleteSubmission`, `deleteSubmissions`, `approveSubmission`, `resendConfirmation` and `subscribe`.

## Every method

**forms.ts**

```
const form = await openemail.forms.create({
  name: 'Newsletter sign-up',
  starter: 'newsletter',
  settings: { audienceIds: ['aud_4c1b8e2a7d9f05c36b4e8a71'] },
  publish: true,
})

console.log(form.url, form.subscribeUrl)

const saved = await openemail.forms.update(form.id, {
  settings: { doubleOptIn: true, senderAddress: 'news@acme.com' },
  expectedUpdatedAt: form.updatedAt,
})

const signup = await openemail.forms.subscribe(form.id, {
  email: 'ann@example.com',
  first_name: 'Ann',
  consent: true,
})

for await (const submission of openemail.forms.iterateSubmissions(form.id, { status: 'pending' })) {
  if (submission.expired) await openemail.forms.resendConfirmation(form.id, submission.id)
}

const stats = await openemail.forms.analytics(form.id, { days: 30 })
const starters = await openemail.forms.listStarters()

await openemail.forms.pause(form.id)
await openemail.forms.resume(form.id)
const copy = await openemail.forms.duplicate(form.id)
await openemail.forms.delete(copy.id)

console.log(saved.senderIssue, signup.outcome, stats.totals.conversion, starters.length)
```

A form keeps a draft `document` and the `publishedDocument` visitors see. `update` changes the draft and the settings, and `publish` puts the draft live. Settings take effect at once, published or not, and `expectedUpdatedAt` refuses a save that would overwrite someone else’s with 409 `version_conflict`.

Reading needs `forms:read` and changing needs `forms:write`. `approveSubmission` also needs `contacts:write`, because it adds a contact. `resendConfirmation` also needs `emails:send`, and so does a call that makes the form send mail: turning on `doubleOptIn`, setting `senderAddress` or the confirmation email, or publishing or resuming a double opt-in form. `delete` asks an OAuth access token for a verification code, and an API key never.

> `subscribe` signs someone up as the form’s page does and sends no credential, even from a client that holds one. Every sign-up from one network shares a limit of 40 every ten minutes, so a server relaying sign-ups for many people reaches it quickly: add people you already know with `audiences.importContacts` instead. Pass the page the form was on as `oe_source`, leave out `oe_started`, and send `oe_website` empty or not at all.

> A 422 from `subscribe` is `invalid_form_submission`, and the error’s `fields` lists each answer that is missing or not valid as `{ key, error }`, with reasons such as `required`, `email` and `option`. The client is for servers. In a browser, post the answers to the form’s `subscribeUrl` with `fetch`, as a JSON body or with an `Accept: application/json` header, and it answers JSON from any origin. Without either it answers with a 303 redirect to the hosted page.

## Response: FormDetailResource

`list` resolves to one page of `FormResource`, newest first, without `document` and `settings`, and `listAll` and `iterate` walk every page. `get`, `create`, `update`, `publish`, `pause`, `resume` and `duplicate` resolve to a `FormDetailResource`, which adds `document`, `publishedDocument`, `settings`, `audiences`, `senderIssue` and `senderProblem`.

- `id` (string): The durable handle, `frm_` followed by 24 hex characters.
- `status` ('draft' | 'live' | 'paused'): `draft` until the first publish, then `live` while it takes sign-ups and `paused` while it does not. A form never goes back to `draft`.
- `url` (string): The hosted page of the published form, to share as a link.
- `subscribeUrl` (string): Where a plain HTML form, or `fetch`, posts the answers.
- `document` (FormDocument): The draft: `fields` in order, the `copy` around them and the `style`.
- `publishedDocument` (FormDocument | null): What visitors see now. Null until the first publish.
- `settings` (FormSettings): Where sign-ups go and what happens after one: `audienceIds`, `doubleOptIn`, `senderAddress`, the confirmation email, `successAction`, `redirectUrl` and `notifyAddresses`.
- `hasUnpublishedChanges` (boolean): True when the draft differs from what visitors see. Always false before the first publish.
- `senderIssue` ('missing' | 'not_sendable' | 'not_allowed' | null): Why a double opt-in form cannot send its confirmation emails right now, or null when it can.
- `stats` (FormStats): `views`, `submissions`, `added`, `pending` and `lastSubmittedAt`, counted at the moment of the read.

## Submissions

`listSubmissions` pages newest first, with `q` to search email addresses and `status` for `pending` or `added`, and `listAllSubmissions` and `iterateSubmissions` walk every page. Each `FormSubmissionResource` keeps the answers as they were sent, labels included, so it still reads right after the form changes.

`resendConfirmation` resolves to the submission with `confirmationSent`. It is false when nothing went out: one address gets one confirmation per form every ten minutes and five a day across the workspace, and an added submission gets none. `expired` marks a pending sign-up whose latest link has run out.
