Skip to the documentation
PHP

Forms

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

Every method

forms.php
use OpenEmail\Constants\FormStarterSlugs;use OpenEmail\Constants\FormSubmissionStatuses; $form = $client->forms->create([    'name' => 'Newsletter sign-up',    'starter' => FormStarterSlugs::NEWSLETTER,    'settings' => ['audienceIds' => ['aud_4c1b8e2a7d9f05c36b4e8a71']],    'publish' => true,]); echo $form['url'], ' ', $form['subscribeUrl'], PHP_EOL; $saved = $client->forms->update($form['id'], [    'settings' => ['doubleOptIn' => true, 'senderAddress' => '[email protected]'],    'expectedUpdatedAt' => $form['updatedAt'],]); $signup = $client->forms->subscribe($form['id'], [    'email' => '[email protected]',    'first_name' => 'Ann',    'consent' => true,]); foreach ($client->forms->iterateSubmissions($form['id'], status: FormSubmissionStatuses::PENDING) as $submission) {    if ($submission['expired']) {        $client->forms->resendConfirmation($form['id'], $submission['id']);    }} $stats = $client->forms->analytics($form['id'], days: 30);$starters = $client->forms->listStarters(); $client->forms->pause($form['id']);$client->forms->resume($form['id']);$copy = $client->forms->duplicate($form['id']);$client->forms->delete($copy['id']); echo $saved['senderIssue'] ?? 'ready to send', ' ', $signup['outcome'], ' ', $stats['totals']['conversion'] ?? 'no views yet', ' ', count($starters), PHP_EOL;

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 a 409 version_conflict, thrown as a ConflictException.

The fields of a form are the keys of one array under the API’s camelCase names (expectedUpdatedAt, doubleOptIn), while filters and options are named arguments (status: on listSubmissions, offsetMinutes: on analytics). A form comes back as an array keyed in camelCase, so $form['subscribeUrl'] reads the address that takes sign-ups.

design builds a new draft form from a written brief, as Create with AI does on the Forms page, and redesign applies written instructions to the draft of a form, as Ask AI does in the form builder. Each spends one AI action and writes only the draft, so visitors see nothing new until publish.

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. Until the token has one, delete throws a PermissionException whose isStepUpRequired() is true.

subscribe signs someone up as the form’s page does and sends no credential, even from a client that holds one, so apiKey: is ignored. The answers are its second argument, one array keyed by the form’s field keys. 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. Past the limit the call throws a RateLimitException. 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, thrown as a ValidationException, and the exception’s fields lists each answer that is missing or not valid as an array with key and error, with reasons such as required, email and option. OpenEmail\Constants\FormFieldErrors names every reason. The package runs on a server. A browser posts the answers to the form’s subscribeUrl itself, as a JSON body or with an Accept: application/json header, and gets JSON back from any origin. Without either it gets a 303 redirect to the hosted page.

Response: a form

list returns one OpenEmail\Result\Page of forms, newest first, without document and settings. listAll returns every form in one array, and iterate returns a Generator that walks every page one form at a time. get, create, update, publish, pause, resume and duplicate return the whole form as an array, which adds document, publishedDocument, settings, audiences, senderIssue and senderProblem.

idstring
The durable handle, `frm_` followed by 24 hex characters.
statusstring
`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`. `OpenEmail\Constants\FormStatuses` names the three.
urlstring
The hosted page of the published form, to share as a link.
subscribeUrlstring
Where a plain HTML form, or a script in the browser, posts the answers.
documentarray
The draft: `fields` in order, the `copy` around them and the `style`.
publishedDocumentarray or null
What visitors see now, or null until the first publish.
settingsarray
Where sign-ups go and what happens after one: `audienceIds`, `doubleOptIn`, `senderAddress`, the confirmation email, `successAction`, `redirectUrl` and `notifyAddresses`.
hasUnpublishedChangesbool
True when the draft differs from what visitors see. Always false before the first publish.
senderIssuestring or null
Why a double opt-in form cannot send its confirmation emails right now: `missing`, `not_sendable` or `not_allowed`. It is null when it can. `OpenEmail\Constants\FormSenderIssues` names the three.
statsarray
`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. OpenEmail\Constants\FormSubmissionStatuses names the two statuses. Each submission is an array that keeps the answers as they were sent, labels included, so it still reads right after the form changes.

resendConfirmation returns 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.