Skip to the documentation
Go

client.Forms

Every method in this namespace: its signature, its parameters, what it returns and an example.

Methods

Forms that add the people who fill them in to your audiences, with an optional confirmation email first: make them from a starter or your own fields, publish, pause and copy them, read their analytics, manage what people sent, and sign someone up without a credential.

Forms.List

List one page of the sign-up forms in the workspace

Scopesforms:readPages through results
Signature
List(ctx context.Context, opts ...openemail.RequestOption) (*openemail.Page, error)

Returns one page of the sign-up forms the caller can reach, newest first, without their documents or settings. Paging is keyset: openemail.WithLimit takes 1 to 100 and defaults to 25, and NextCursor goes back as openemail.WithCursor while HasMore is true. ListAll and Iterate do that walk for you.

Each form carries its status, url, the hosted page that shows it, subscribeUrl, where a plain HTML form posts, audienceIds, doubleOptIn, hasUnpublishedChanges and stats. stats is counted at the moment of the read: views of the hosted page and the embed while the form was live, submissions stored, added, the sign-ups added to the audiences, pending, the ones still to confirm, and lastSubmittedAt.

Read one with Get for the draft document, the publishedDocument visitors see and the settings.

Parameters

openemail.WithLimitint

Rows per page, a whole number from 1 to 100. The server defaults to 25.

openemail.WithCursorstring

The NextCursor from the previous page, passed back exactly as it came. One the server cannot read is a 400 invalid_cursor.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

A *openemail.Page with Items, HasMore and NextCursor. Each item has id, name, description, status, url, subscribeUrl, audienceIds, doubleOptIn, hasUnpublishedChanges, stats, publishedAt, createdAt and updatedAt.

Example

page, err := client.Forms.List(ctx, openemail.WithLimit(50))if err != nil {	return err} for _, form := range page.Items {	fmt.Println(form.String("name"), form.String("status"), form.Object("stats").Int("submissions"))} fmt.Println(page.HasMore, page.NextCursor)

Notes

  • An app a member connected lists only the forms that member made, as the console does for them. An API key and an app the owner connected list every form in the workspace.

  • Read only, so the SDK retries it after a network failure like any other read.

Also available in

API
GET /forms
TypeScript
forms.list()
Python
forms.list()
Ruby
forms.list
PHP
forms->list
Java
forms().list
C#
Forms.ListAsync
CLI
openemail forms list

Forms.ListAll

Collect every sign-up form you can reach into one slice

Scopesforms:readPages through results
Signature
ListAll(ctx context.Context, opts ...openemail.RequestOption) ([]openemail.Object, error)

Follows NextCursor from page to page and returns with every form the caller can reach, newest first, in the shape List returns. limit sets the page size of each request, not the total.

Parameters

openemail.WithLimitint

Page size per request, from 1 to 100, defaulting to 25 on the server.

openemail.WithCursorstring

A NextCursor from an earlier page to start after.

openemail.WithAPIKeystring

Overrides the client's API key for every page of this walk.

Returns

A []openemail.Object holding every form across all pages.

Example

forms, err := client.Forms.ListAll(ctx, openemail.WithLimit(100))if err != nil {	return err} for _, form := range forms {	fmt.Println(form.String("status"))}

Notes

  • A failure on any page fails the whole call.

Also available in

API
GET /forms
TypeScript
forms.listAll()
Python
forms.list_all()
Ruby
forms.list_all
PHP
forms->listAll
Java
forms().listAll
C#
Forms.ListAllAsync

Forms.Iterate

Stream the sign-up forms you can reach one at a time

Scopesforms:readPages through results
Signature
Iterate(ctx context.Context, opts ...openemail.RequestOption) *openemail.Iterator

Returns an iterator that yields forms one by one, newest first, and requests the next page only once the current one is drained. Breaking out of the loop stops the requests.

Parameters

openemail.WithLimitint

Page size per request, from 1 to 100, defaulting to 25 on the server.

openemail.WithCursorstring

A NextCursor from an earlier page to start after.

openemail.WithAPIKeystring

Overrides the client's API key for every page of this walk.

Returns

An *openemail.Iterator yielding one form per step.

Example

for form, err := range client.Forms.Iterate(ctx).All() {	if err != nil {		return err	} 	fmt.Println(form.Bool("hasUnpublishedChanges"), form.String("name"))}

Notes

  • The iterator is lazy, so an abandoned loop costs only the pages you consumed.

Also available in

API
GET /forms
TypeScript
forms.iterate()
Python
forms.iterate()
Ruby
forms.iterate
PHP
forms->iterate
Java
forms().iterate
C#
Forms.IterateAsync

Forms.Create

Create a sign-up form

Scopesforms:write
Signature
Create(ctx context.Context, body openemail.Body, opts ...openemail.RequestOption) (openemail.Object, error)

Makes a draft form and returns it whole, in the shape Get returns. Its fields, copy and style come from document when you send one, from the starter named in starter when you do not, and otherwise from a one-field email form. settings changes any of the defaults, which add people to the default audience only, send no confirmation email, show the thank-you message from the copy and notify nobody.

A form takes no sign-ups until it is published. Send publish: true to put it live in the same call, or call Publish later. Nothing is created when the document or settings break a rule.

Once it is live, share it with url, point a plain HTML <form method="post"> at subscribeUrl, embed it with the script at /embed/form.js on the OpenEmail web app, or sign people up from your own code with Subscribe.

Parameters

namestringRequired

What the workspace calls the form, trimmed, 1 to 120 characters. Visitors never see it, and it need not be unique.

descriptionstring | nil

A note for the workspace, at most 500 characters. Visitors never see it.

starterstring

Begin from a starter: blank, newsletter, waitlist, event, early-access or contact. Ignored when document is sent.

documentopenemail.Body

The whole form, fields, copy and style, in the shape Get and GetStarter return. Every field carries all 15 of its keys, with null for the ones it does not use, and a document needs exactly one email field, keyed email and required, and its field keys and ids must be unique. Leave it out to use starter or a one-field email form.

settings.audienceIds[]string

The audiences every sign-up joins, at most 20 ids such as aud_9f2c4b7e1a0d63d84c5f2e7b. Every contact is in the default audience as well, so an empty list adds people there only. An id that is not an audience the caller can reach is a 404 audience_not_found.

settings.doubleOptInbool

Email each person a confirmation link and add them only once they open it. Defaults to false. True needs senderAddress.

settings.senderAddressstring | nil

The workspace address confirmation emails come from, required for doubleOptIn. It has to be an address the caller may send as, or the call is refused with 422 form_sender_refused.

settings.confirmSubjectstring

The subject of the confirmation email, 1 to 200 characters. Defaults to Please confirm your subscription.

settings.confirmMessagestring

The text of the confirmation email above its button, at most 2000 characters.

settings.confirmButtonstring

The label of the confirmation button, 1 to 60 characters. Defaults to Confirm my subscription.

settings.successActionstring

What a person sees after signing up: message, the thank-you copy in the document, which is the default, or redirect, which sends them to redirectUrl.

settings.redirectUrlstring | nil

The http or https page redirect sends people to, at most 2000 characters. Required while successAction is redirect.

settings.notifyAddresses[]string

Up to 10 addresses of this workspace that get an email about each sign-up. Each has to be one the caller can read, or the call is refused with 422 invalid_form.

publishbool

Publish in the same call, so the form takes sign-ups at once.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object: the form object fields, with status draft, or live when publish was true, plus document, publishedDocument, settings, audiences, senderIssue and senderProblem.

Example

form, err := client.Forms.Create(ctx, openemail.Body{	"name":     "Newsletter",	"starter":  "newsletter",	"settings": openemail.Body{"audienceIds": []string{"aud_9f2c4b7e1a0d63d84c5f2e7b"}},	"publish":  true,})if err != nil {	return err} fmt.Println(form.String("id"), form.String("status"), form.String("url"))

Notes

  • A value of the wrong type or length is 422 invalid_parameter with param naming it, and a key the body does not take is 422 unknown_parameter. A document or settings that break a rule, such as a form without its required email field, doubleOptIn without senderAddress or redirect without redirectUrl, is 422 invalid_form.

  • A key or app limited to particular addresses may only name addresses it holds in senderAddress and notifyAddresses. Anything else is 422 capability_unsupported.

  • Turning on doubleOptIn, naming a senderAddress or changing the confirmation email also needs emails:send, because the form then sends mail for you.

  • A workspace holds 100 forms by default, and support can raise that for a workspace that needs more. The next one past the limit is 422 workspace_limit_reached.

  • Not retried automatically, and nothing deduplicates by name, so a retry after a lost response can leave two forms. List them and delete the spare.

Also available in

API
POST /forms
TypeScript
forms.create()
Python
forms.create()
Ruby
forms.create
PHP
forms->create
Java
forms().create
C#
Forms.CreateAsync
CLI
openemail forms create

Forms.Design

Design a form from a brief

Scopesforms:write
Signature
Design(ctx context.Context, body openemail.Body, opts ...openemail.RequestOption) (openemail.Object, error)

A designer builds a new sign-up form from a written brief, as Create with AI does on the Forms page of the app: it picks the fields, writes the copy and sets the look, then saves the form as a draft. Review it with Get, change it with Update or Redesign, and put it live with publish.

Put everything the form must ask and say in brief: what it is for, the fields, the wording word for word, the colours and the tone. It spends one AI action and can take up to half a minute.

Parameters

briefstringRequired

Everything the form must ask and say, up to 4,000 characters.

namestring

A short name for the form, up to 120 characters. Left out, the designer names it.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object: the new draft, as Get returns it, plus design.notes on what the designer adjusted or left out.

Example

form, err := client.Forms.Design(ctx, openemail.Body{	"brief": "A waitlist for our beta: email, first name and company, with a short line on what joining means. Dark green button that says Join the waitlist.",})if err != nil {	return err} fmt.Println(form.String("id"), form.String("status"))

Notes

  • The SDK does not retry it, because a second call designs and saves a second form.

  • A design that cannot be made valid is a 422 invalid_form, a workspace that already holds as many forms as it may a 422 workspace_limit_reached, a server with no model a 409 ai_not_configured, and a workspace out of AI actions a 429 ai_quota_exceeded. Nothing is created in any of them.

Also available in

API
POST /forms/design
TypeScript
forms.design()
Python
forms.design()
Ruby
forms.design
PHP
forms->design
Java
forms().design
C#
Forms.DesignAsync
CLI
openemail forms design

Forms.Redesign

Change a form’s design from instructions

Scopesforms:write
Signature
Redesign(ctx context.Context, id string, body openemail.Body, opts ...openemail.RequestOption) (openemail.Object, error)

A designer applies written instructions to the draft of a form and leaves the rest alone, as Ask AI does in the form builder of the app: add, remove or reorder fields, make one required, rewrite or translate the copy, change colours or fonts. The change is saved to the draft, so a live form keeps showing its published version until publish.

Send the updatedAt you read as expectedUpdatedAt to refuse the change when somebody saved the form since. It spends one AI action and can take up to half a minute.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

instructionsstringRequired

What to change, with every detail, up to 4,000 characters.

expectedUpdatedAtstring

The updatedAt you read, as it came. When the form was saved since, nothing is written and the answer is 409 version_conflict.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object: the form with the change in its draft, plus design.notes on what the designer changed.

Example

form, err := client.Forms.Redesign(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9", openemail.Body{	"instructions": "Add a required company field after the name, and make the button say Join.",})if err != nil {	return err} fmt.Println(form.Bool("hasUnpublishedChanges"))

Notes

  • The SDK does not retry it, because each call is a fresh design pass.

  • The same errors as Design, plus 404 form_not_found for a form the caller cannot reach and 409 version_conflict when the form was saved since expectedUpdatedAt, or while the designer worked.

Also available in

API
POST /forms/{id}/redesign
TypeScript
forms.redesign()
Python
forms.redesign()
Ruby
forms.redesign
PHP
forms->redesign
Java
forms().redesign
C#
Forms.RedesignAsync
CLI
openemail forms redesign

Forms.ListStarters

List the starting points for a new form

Scopesforms:read
Signature
ListStarters(ctx context.Context, opts ...openemail.RequestOption) ([]openemail.Object, error)

Returns the starters the console offers when somebody makes a form, as a plain slice: blank, just the email field, then newsletter, waitlist, event, early-access and contact. Each carries its slug, name and description, and the documents are left out.

Pass a slug to Create as starter to begin a form from it, or read it whole with GetStarter to change its fields first.

Starters are part of the product rather than workspace data, so the answer is the same for every key and changes only when a release adds one.

Parameters

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

A []openemail.Object, each a map with object set to form_starter, slug, name and description.

Example

starters, err := client.Forms.ListStarters(ctx)if err != nil {	return err} for _, starter := range starters {	fmt.Println(starter.String("slug"), starter.String("name"))}

Notes

  • Not paginated, and there is no cursor. The list is short.

Also available in

API
GET /forms/starters
TypeScript
forms.listStarters()
Python
forms.list_starters()
Ruby
forms.list_starters
PHP
forms->listStarters
Java
forms().listStarters
C#
Forms.ListStartersAsync
CLI
openemail forms list-starters

Forms.GetStarter

Retrieve one starter with its document

Scopesforms:read
Signature
GetStarter(ctx context.Context, slug string, opts ...openemail.RequestOption) (openemail.Object, error)

Returns one starter in full: everything ListStarters carries plus document, the fields, copy and style a form made from it begins with.

The document is there so a client can change it before it creates a form, rather than creating from the starter and updating afterwards. Send it, changed or not, as document on Create.

Parameters

slugstringRequired

A starter slug from ListStarters: blank, newsletter, waitlist, event, early-access or contact.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object: object, slug, name and description, plus document with fields, copy and style.

Example

starter, err := client.Forms.GetStarter(ctx, "newsletter")if err != nil {	return err} fmt.Println(starter.String("name"), starter.String("object"))

Notes

  • An unknown slug is a 404 form_starter_not_found.

  • Passing starter: 'newsletter' to Create does the same seeding on the server, in one call instead of two.

Also available in

API
GET /forms/starters/{slug}
TypeScript
forms.getStarter()
Python
forms.get_starter()
Ruby
forms.get_starter
PHP
forms->getStarter
Java
forms().getStarter
C#
Forms.GetStarterAsync
CLI
openemail forms get-starter

Forms.Get

Retrieve a form with its documents and settings

Scopesforms:read
Signature
Get(ctx context.Context, id string, opts ...openemail.RequestOption) (openemail.Object, error)

Returns the whole form: the fields List carries with fresh stats, the draft document, the publishedDocument visitors see, which is null until the first publish, the settings and the named audiences sign-ups join.

hasUnpublishedChanges is true while the draft differs from what the live form shows. Settings take effect when they are saved, published or not.

For a form with doubleOptIn on, senderIssue says why confirmation emails cannot go out right now: missing when no senderAddress is set, not_sendable when the address cannot send, and not_allowed when its owner may not send as it. senderProblem says the same in a sentence. Both are null when confirmations can go out.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object: id, name, description, status, url, subscribeUrl, audienceIds, doubleOptIn, hasUnpublishedChanges, stats, publishedAt, createdAt and updatedAt, plus document, publishedDocument, settings, audiences, each a map with id, name and builtin, senderIssue and senderProblem.

Example

form, err := client.Forms.Get(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9")if err != nil {	return err} fmt.Println(form.String("status"), form.String("url"), form.String("senderIssue"), form.String("senderProblem"))

Notes

  • A form in another workspace is a 404 form_not_found, never a 403. An app a member connected reaches only the forms that member made.

  • audienceIds and settings.audienceIds leave out an audience that was deleted after it was chosen, or that the caller cannot reach.

Also available in

API
GET /forms/{id}
TypeScript
forms.get()
Python
forms.get()
Ruby
forms.get
PHP
forms->get
Java
forms().get
C#
Forms.GetAsync
CLI
openemail forms get

Forms.Update

Change a form's name, document or settings

Scopesforms:write
Signature
Update(ctx context.Context, id string, patch openemail.Body, opts ...openemail.RequestOption) (openemail.Object, error)

A partial update: a field you leave out keeps its stored value, and description: null clears the note. document replaces the draft whole, and a live form keeps showing its published copy until publish. settings is merged field by field, so { settings: { doubleOptIn: true, senderAddress: '[email protected]' } } changes those two and keeps the rest, and settings take effect at once, live form or not.

To avoid overwriting somebody else's change, read the form and send its updatedAt back as expectedUpdatedAt. A save made in between is then refused with 409 version_conflict rather than overwritten, and nothing is written.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

namestring

New name, trimmed, 1 to 120 characters.

descriptionstring | nil

New note, at most 500 characters. Null clears it.

documentopenemail.Body

The new draft, whole, in the shape Get returns. Every field carries all 15 of its keys, and a document needs exactly one email field, keyed email and required, and its field keys and ids must be unique.

settings.audienceIds[]string

The new list of audiences every sign-up joins, replacing the old one, at most 20 ids such as aud_9f2c4b7e1a0d63d84c5f2e7b. Every contact is in the default audience as well, so an empty list adds people there only. A new id that is not an audience the caller can reach is a 404 audience_not_found.

settings.doubleOptInbool

Email each person a confirmation link and add them only once they open it. True needs senderAddress.

settings.senderAddressstring | nil

The workspace address confirmation emails come from, required for doubleOptIn. It has to be an address the caller may send as, or the call is refused with 422 form_sender_refused.

settings.confirmSubjectstring

The subject of the confirmation email, 1 to 200 characters.

settings.confirmMessagestring

The text of the confirmation email above its button, at most 2000 characters.

settings.confirmButtonstring

The label of the confirmation button, 1 to 60 characters.

settings.successActionstring

What a person sees after signing up: message, the thank-you copy in the document or redirect, which sends them to redirectUrl.

settings.redirectUrlstring | nil

The http or https page redirect sends people to, at most 2000 characters. Required while successAction is redirect.

settings.notifyAddresses[]string

Up to 10 addresses of this workspace that get an email about each sign-up, replacing the old list. Each has to be one the caller can read, or the call is refused with 422 invalid_form.

expectedUpdatedAtstring

The updatedAt you read, as it came. When the form was saved since, nothing is written and the answer is 409 version_conflict.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object as it stands after the change, with a new updatedAt.

Example

form, err := client.Forms.Get(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9")if err != nil {	return err} updated, err := client.Forms.Update(ctx, form.ID(), openemail.Body{	"settings":          openemail.Body{"doubleOptIn": true, "senderAddress": "[email protected]"},	"expectedUpdatedAt": form.String("updatedAt"),})if err != nil {	return err} fmt.Println(updated.Bool("doubleOptIn"), updated.Bool("hasUnpublishedChanges"))

Notes

  • The checks Create makes apply to what you send: 422 invalid_parameter, unknown_parameter, invalid_form, form_sender_refused or capability_unsupported, and 404 audience_not_found on settings.audienceIds, each with param naming the field.

  • Turning on doubleOptIn, naming a senderAddress or changing the confirmation email also needs emails:send, because the form then sends mail for you.

  • Retried automatically on network failure and retryable statuses, since the same patch sent twice leaves the same form. With expectedUpdatedAt, a retry after a lost response can come back 409 version_conflict because the first attempt went through, so read the form before trying again.

Also available in

API
PATCH /forms/{id}
TypeScript
forms.update()
Python
forms.update()
Ruby
forms.update
PHP
forms->update
Java
forms().update
C#
Forms.UpdateAsync
CLI
openemail forms update

Forms.Delete

Delete a form and every submission it holds

Scopesforms:write
Signature
Delete(ctx context.Context, id string, opts ...openemail.RequestOption) (openemail.Object, error)

Deletes the form and every submission stored on it. Its hosted page and embed stop working at once, and Subscribe answers 404 form_not_found. The people it added stay in your contacts and audiences.

There is no undo. To stop sign-ups and keep the form, its submissions and its statistics, use Pause.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object with object set to form, id and deleted set to true.

Example

removed, err := client.Forms.Delete(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9")if err != nil {	return err} fmt.Println(removed.String("id"), removed.Bool("deleted"))

Notes

  • An OAuth access token needs a verification code for this call, and is refused with 403 step_up_required until the app has verified one in the last 60 minutes. The error matches openemail.ErrStepUpRequired. An API key is never asked for a code.

  • The SDK does not retry a delete. A 404 on your own second attempt after a lost response means the first one worked.

Also available in

API
DELETE /forms/{id}
TypeScript
forms.delete()
Python
forms.delete()
Ruby
forms.delete
PHP
forms->delete
Java
forms().delete
C#
Forms.DeleteAsync
CLI
openemail forms delete

Forms.Publish

Put the draft of a form live

Scopesforms:write
Signature
Publish(ctx context.Context, id string, opts ...openemail.RequestOption) (openemail.Object, error)

Copies the draft document to publishedDocument and sets the form live, so the hosted page, the embed and Subscribe show and take the new version from then on. Publishing a paused form opens it again. It takes no body.

The draft is checked first: the document and settings have to pass the rules Create applies, and a double opt-in form needs a senderAddress that can send confirmations. An audience deleted since is skipped when people sign up. Submissions keep the answers, labels included, they were sent with, so publishing a change never rewrites them.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object with status live, a new publishedAt and hasUnpublishedChanges false.

Example

form, err := client.Forms.Publish(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9")if err != nil {	return err} fmt.Println(form.String("status"), form.String("publishedAt"), form.String("url"))

Notes

  • A draft that breaks a rule is 422 invalid_form, and a double opt-in form that cannot send confirmations is 422 form_sender_refused, each with param naming the field. Publishing a double opt-in form also needs emails:send. A key or app limited to some addresses is refused with 422 capability_unsupported when the form's settings.senderAddress or settings.notifyAddresses falls outside them.

  • Retried automatically on network failure and retryable statuses, since publishing the same draft twice leaves the same live form.

Also available in

API
POST /forms/{id}/publish
TypeScript
forms.publish()
Python
forms.publish()
Ruby
forms.publish
PHP
forms->publish
Java
forms().publish
C#
Forms.PublishAsync
CLI
openemail forms publish

Forms.Pause

Stop a published form taking sign-ups

Scopesforms:write
Signature
Pause(ctx context.Context, id string, opts ...openemail.RequestOption) (openemail.Object, error)

Closes a published form. Its hosted page and embed stay up and show the closed title and message from its copy, and Subscribe answers 409 form_closed. Pausing a paused form changes nothing. It takes no body.

The form keeps its documents, settings, submissions and statistics, and Resume opens it again with the version that was published last.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object with status paused.

Example

form, err := client.Forms.Pause(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9")if err != nil {	return err} fmt.Println(form.String("status"))

Notes

  • A form that was never published cannot be paused, and is refused with 409 form_not_published.

  • Retried automatically on network failure and retryable statuses, since a second pause changes nothing.

Also available in

API
POST /forms/{id}/pause
TypeScript
forms.pause()
Python
forms.pause()
Ruby
forms.pause
PHP
forms->pause
Java
forms().pause
C#
Forms.PauseAsync
CLI
openemail forms pause

Forms.Resume

Open a paused form again

Scopesforms:write
Signature
Resume(ctx context.Context, id string, opts ...openemail.RequestOption) (openemail.Object, error)

Opens a paused form again with the version that was published last, so it takes sign-ups once more. Changes made to the draft since stay in the draft: publish puts them live and opens the form in one call. Resuming a live form changes nothing. It takes no body.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object with status live.

Example

form, err := client.Forms.Resume(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9")if err != nil {	return err} fmt.Println(form.String("status"), form.Bool("hasUnpublishedChanges"))

Notes

  • A form that was never published is refused with 409 form_not_published. Publish it instead.

  • The published version is checked again first, so a double opt-in form whose senderAddress can no longer send confirmations is refused with 422 form_sender_refused, and one that breaks a rule with 422 invalid_form. Resuming a double opt-in form also needs emails:send. A key or app limited to some addresses is refused with 422 capability_unsupported when the form's settings.senderAddress or settings.notifyAddresses falls outside them.

  • Retried automatically on network failure and retryable statuses, since a second resume changes nothing.

Also available in

API
POST /forms/{id}/resume
TypeScript
forms.resume()
Python
forms.resume()
Ruby
forms.resume
PHP
forms->resume
Java
forms().resume
C#
Forms.ResumeAsync
CLI
openemail forms resume

Forms.Duplicate

Copy a form into a new draft

Scopesforms:write
Signature
Duplicate(ctx context.Context, id string, opts ...openemail.RequestOption) (openemail.Object, error)

Makes a new draft with the same document and settings, named after the original with copy added, such as Newsletter copy. Submissions and statistics are not copied, and the copy takes no sign-ups until you publish it. It takes no body.

An audience in the settings that the caller cannot reach is left out of the copy.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object for the new form, with its own id, status draft, publishedDocument null and empty stats.

Example

form, err := client.Forms.Duplicate(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9")if err != nil {	return err} fmt.Println(form.String("id"))

Notes

  • It counts toward the workspace limit of 100 forms, as Create does, and is refused with 422 workspace_limit_reached past it. A key or app limited to some addresses is refused with 422 capability_unsupported when the form's settings.senderAddress or settings.notifyAddresses falls outside them.

  • Not retried automatically, since a retry after a lost response would make a second copy. List the forms and delete the spare.

Also available in

API
POST /forms/{id}/duplicate
TypeScript
forms.duplicate()
Python
forms.duplicate()
Ruby
forms.duplicate
PHP
forms->duplicate
Java
forms().duplicate
C#
Forms.DuplicateAsync
CLI
openemail forms duplicate

Forms.Analytics

Read views, sign-ups and people added over a window

Scopesforms:read
Signature
Analytics(ctx context.Context, id string, opts ...openemail.RequestOption) (openemail.Object, error)

Returns the numbers behind a form's Analytics tab in one request: views, submissions and added over a window, cut into buckets, with totals and the conversion rate.

A view is one load of the hosted page or the embed while the form is live, and views are not deduplicated by visitor. A submission is counted when it is stored, waiting or added. added counts the sign-ups added to the audiences in the window, one per sign-up, dated when they joined, so a double opt-in sign-up can be submitted in one bucket and added in a later one.

series is sparse and oldest first: a bucket with nothing in it has no entry, so a chart must fill the gaps. grain sets the bucket width and the key shape, YYYY-MM-DD, YYYY-MM-DDTHH or YYYY-MM-DDTHH:MM, and offsetMinutes shifts the boundaries so days break where the reader's day does. The window starts at the beginning of its oldest bucket, reported as since, and ends now, reported as until.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

openemail.WithDaysint

How far back to look, from 1 to 1095, defaulting to 30.

openemail.WithMinutesint

The window in minutes, from 1 to 1576800, which wins over days when both are sent.

openemail.WithGrainstring

Bucket width: minute, hour or day, defaulting to day.

openemail.WithOffsetMinutesint

Minutes east of UTC to cut the buckets in, from -840 to 840, defaulting to 0. For the local zone, pass the offset time.Now().Zone() reports, divided by 60.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object with object set to form_analytics, formId, since, until, grain, offsetMinutes, totals and series. totals has views, submissions, added, pending, lastSubmittedAt and conversion, and each entry of series is a map with bucket, views, submissions and added.

Example

_, offset := time.Now().Zone() analytics, err := client.Forms.Analytics(	ctx,	"frm_8d2f6a1c9b3e47d0a5f1c2e9",	openemail.WithDays(90),	openemail.WithGrain("day"),	openemail.WithOffsetMinutes(offset/60),)if err != nil {	return err} totals := analytics.Object("totals") fmt.Println(totals.Int("views"), totals.Int("submissions"), totals.Float("conversion"))

Notes

  • totals.conversion is submissions divided by views over the window, at most 1, and null when the window has no views. totals.pending counts the submissions made in the window that are still waiting, and totals.lastSubmittedAt is the latest sign-up ever, inside the window or not.

  • Views are counted by the hour, so with grain: 'minute' each hour's views fall in the bucket at the start of that hour.

  • Read only, so the SDK retries it after a network failure like any other read.

Also available in

API
GET /forms/{id}/analytics
TypeScript
forms.analytics()
Python
forms.analytics()
Ruby
forms.analytics
PHP
forms->analytics
Java
forms().analytics
C#
Forms.AnalyticsAsync
CLI
openemail forms analytics

Forms.ListSubmissions

List one page of a form's submissions

Scopesforms:readPages through results
Signature
ListSubmissions(ctx context.Context, id string, opts ...openemail.RequestOption) (*openemail.Page, error)

Returns one page of the sign-ups a form has stored, newest first. Each keeps the answers as they were sent, labels included, so it still reads right after the form changes. Paging is keyset: openemail.WithLimit takes 1 to 100 and defaults to 25, and NextCursor, a submission id, goes back as openemail.WithCursor while HasMore is true. ListAllSubmissions and IterateSubmissions do that walk for you.

status is added once the person is a contact in the audiences, and pending while a double opt-in form waits for them to confirm. expired is true on a pending submission whose newest confirmation link, sent at sign-up or by a resend, is more than 7 days old: approve it with ApproveSubmission, or send a fresh link with ResendConfirmation.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

openemail.WithLimitint

Rows per page, a whole number from 1 to 100. The server defaults to 25.

openemail.WithCursorstring

The NextCursor from the previous page, a submission id of this form. One that names no submission of this form is a 400 invalid_cursor.

openemail.WithQstring

Only submissions whose email address contains this text, ignoring case, at most 200 characters.

openemail.WithStatus...string

Only pending or only added submissions.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

A *openemail.Page with Items, HasMore and NextCursor. Each item has id, formId, email, status, expired, answers, audienceIds, sourceUrl, confirmedAt and createdAt, and each answer is a map with fieldId, key, label, type, value and display.

Example

page, err := client.Forms.ListSubmissions(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9", openemail.WithStatus("pending"))if err != nil {	return err} for _, submission := range page.Items {	fmt.Println(submission.String("email"), submission.Bool("expired"))} fmt.Println(page.HasMore, page.NextCursor)

Notes

  • value on an answer is text for most fields, a list of option values for a multiple choice or audience field, and true or false for a checkbox or consent field. display is the same answer as a person reads it, option labels included.

  • sourceUrl is the page the form was filled in on, its origin and path only and at most 500 characters, when it was known.

Also available in

API
GET /forms/{id}/submissions
TypeScript
forms.listSubmissions()
Python
forms.list_submissions()
Ruby
forms.list_submissions
PHP
forms->listSubmissions
Java
forms().listSubmissions
C#
Forms.ListSubmissionsAsync
CLI
openemail forms list-submissions

Forms.ListAllSubmissions

Collect every submission of a form into one slice

Scopesforms:readPages through results
Signature
ListAllSubmissions(ctx context.Context, id string, opts ...openemail.RequestOption) ([]openemail.Object, error)

Follows NextCursor from page to page and returns with every submission of the form that matches q and status, newest first. limit sets the page size of each request, not the total.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

openemail.WithLimitint

Page size per request, from 1 to 100, defaulting to 25 on the server.

openemail.WithCursorstring

A submission id to start after.

openemail.WithQstring

Only submissions whose email address contains this text, ignoring case, at most 200 characters.

openemail.WithStatus...string

Only pending or only added submissions.

openemail.WithAPIKeystring

Overrides the client's API key for every page of this walk.

Returns

A []openemail.Object holding every matching submission across all pages.

Example

added, err := client.Forms.ListAllSubmissions(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9", openemail.WithStatus("added"), openemail.WithLimit(100))if err != nil {	return err} for _, submission := range added {	fmt.Println(submission.String("email"))}

Notes

  • A failure on any page fails the whole call. For a large form, IterateSubmissions holds one page in memory at a time.

Also available in

API
GET /forms/{id}/submissions
TypeScript
forms.listAllSubmissions()
Python
forms.list_all_submissions()
Ruby
forms.list_all_submissions
PHP
forms->listAllSubmissions
Java
forms().listAllSubmissions
C#
Forms.ListAllSubmissionsAsync

Forms.IterateSubmissions

Stream a form's submissions one at a time

Scopesforms:readPages through results
Signature
IterateSubmissions(ctx context.Context, id string, opts ...openemail.RequestOption) *openemail.Iterator

Returns an iterator that yields submissions one by one, newest first, and requests the next page only once the current one is drained. Breaking out of the loop stops the requests.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

openemail.WithLimitint

Page size per request, from 1 to 100, defaulting to 25 on the server.

openemail.WithCursorstring

A submission id to start after.

openemail.WithQstring

Only submissions whose email address contains this text, ignoring case, at most 200 characters.

openemail.WithStatus...string

Only pending or only added submissions.

openemail.WithAPIKeystring

Overrides the client's API key for every page of this walk.

Returns

An *openemail.Iterator yielding one submission per step.

Example

for submission, err := range client.Forms.IterateSubmissions(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9", openemail.WithQ("acme.com")).All() {	if err != nil {		return err	} 	fmt.Println(submission.String("email"), submission.String("createdAt"))}

Notes

  • The iterator is lazy, so an abandoned loop costs only the pages you consumed.

Also available in

API
GET /forms/{id}/submissions
TypeScript
forms.iterateSubmissions()
Python
forms.iterate_submissions()
Ruby
forms.iterate_submissions
PHP
forms->iterateSubmissions
Java
forms().iterateSubmissions
C#
Forms.IterateSubmissionsAsync

Forms.GetSubmission

Retrieve one submission with every answer

Scopesforms:read
Signature
GetSubmission(ctx context.Context, id string, submissionID string, opts ...openemail.RequestOption) (openemail.Object, error)

Returns one submission of the form: the address the person signed up with, lower cased, its status, every answer as it was sent, the audiences it joins, the page it came from, and when it was made and confirmed.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

submissionIDstringRequired

Submission id such as fsb_3c7e1a9f0b2d4c6e8a1f3b5d, from ListSubmissions or a form.submitted webhook.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object with id, formId, email, status, expired, answers, audienceIds, sourceUrl, confirmedAt and createdAt.

Example

submission, err := client.Forms.GetSubmission(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9", "fsb_3c7e1a9f0b2d4c6e8a1f3b5d")if err != nil {	return err} fmt.Println(submission.String("id"), submission.String("status"))

Notes

  • A form you cannot reach is a 404 form_not_found, and a submission that is not on this form is a 404 form_submission_not_found.

  • confirmedAt is when the person joined the audiences, and null while the submission is pending.

Also available in

API
GET /forms/{id}/submissions/{submissionId}
TypeScript
forms.getSubmission()
Python
forms.get_submission()
Ruby
forms.get_submission
PHP
forms->getSubmission
Java
forms().getSubmission
C#
Forms.GetSubmissionAsync
CLI
openemail forms get-submission

Forms.DeleteSubmission

Delete one submission of a form

Scopesforms:write
Signature
DeleteSubmission(ctx context.Context, id string, submissionID string, opts ...openemail.RequestOption) (openemail.Object, error)

Deletes the submission. The person it added stays in your contacts and audiences, and a pending one can no longer be confirmed, since its link stops working. There is no undo.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

submissionIDstringRequired

Submission id such as fsb_3c7e1a9f0b2d4c6e8a1f3b5d, from ListSubmissions or a form.submitted webhook.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object with object set to form_submission, id, formId and deleted set to true.

Example

removed, err := client.Forms.DeleteSubmission(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9", "fsb_3c7e1a9f0b2d4c6e8a1f3b5d")if err != nil {	return err} fmt.Println(removed.String("id"), removed.Bool("deleted"))

Notes

  • To take the person off your lists as well, remove them from the audience with Audiences.RemoveContact, or delete the contact with Contacts.Delete.

  • The SDK does not retry a delete. A 404 form_submission_not_found on your own second attempt after a lost response means the first one worked.

Also available in

API
DELETE /forms/{id}/submissions/{submissionId}
TypeScript
forms.deleteSubmission()
Python
forms.delete_submission()
Ruby
forms.delete_submission
PHP
forms->deleteSubmission
Java
forms().deleteSubmission
C#
Forms.DeleteSubmissionAsync
CLI
openemail forms delete-submission

Forms.DeleteSubmissions

Delete up to 200 submissions of a form in one call

Scopesforms:write
Signature
DeleteSubmissions(ctx context.Context, id string, submissionIDs []string, opts ...openemail.RequestOption) (openemail.Object, error)

Deletes many submissions of one form and answers with how many went. A repeated id counts once, and an id that names no submission of this form is skipped rather than refused. The people the submissions added stay in your contacts and audiences, and a pending one can no longer be confirmed.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

submissionIDs[]stringRequired

From 1 to 200 submission ids of this form, such as fsb_3c7e1a9f0b2d4c6e8a1f3b5d, sent as ids. An empty list or more than 200 is a 422 invalid_parameter on ids.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object with object set to form_submission_batch, formId and deleted, where deleted counts the submissions this call removed.

Example

result, err := client.Forms.DeleteSubmissions(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9", []string{"fsb_3c7e1a9f0b2d4c6e8a1f3b5d", "fsb_9a4c2e7f1b3d5a6c8e0f2b4d"})if err != nil {	return err} fmt.Println(result.Int("deleted"))

Notes

  • Send a longer list in chunks of 200.

  • Not retried automatically. A retry after a lost response skips the submissions the first call removed, so its deleted can read 0.

Also available in

API
POST /forms/{id}/submissions/batch-remove
TypeScript
forms.deleteSubmissions()
Python
forms.delete_submissions()
Ruby
forms.delete_submissions
PHP
forms->deleteSubmissions
Java
forms().deleteSubmissions
C#
Forms.DeleteSubmissionsAsync
CLI
openemail forms delete-submissions

Forms.ApproveSubmission

Add a waiting sign-up without its confirmation

Scopesforms:writecontacts:write
Signature
ApproveSubmission(ctx context.Context, id string, submissionID string, opts ...openemail.RequestOption) (openemail.Object, error)

Adds the person behind a pending submission to its audiences without waiting for them to open the confirmation email, for when you know them. The submission comes back added with confirmedAt set, and a form.confirmed webhook goes out with via set to approval. It takes no body.

It does not resubscribe someone who unsubscribed from one of the audiences, as their own confirmation would. A submission that is already added is returned as it is.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

submissionIDstringRequired

Submission id such as fsb_3c7e1a9f0b2d4c6e8a1f3b5d, from ListSubmissions or a form.submitted webhook.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object, now with status added and confirmedAt set.

Example

submission, err := client.Forms.ApproveSubmission(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9", "fsb_3c7e1a9f0b2d4c6e8a1f3b5d")if err != nil {	return err} fmt.Println(submission.String("status"), submission.String("confirmedAt"))

Notes

  • It needs contacts:write as well as forms:write, since it adds a contact. A key without both is refused with 403 insufficient_scope.

  • Retried automatically on network failure and retryable statuses, since approving an added submission changes nothing.

Also available in

API
POST /forms/{id}/submissions/{submissionId}/approve
TypeScript
forms.approveSubmission()
Python
forms.approve_submission()
Ruby
forms.approve_submission
PHP
forms->approveSubmission
Java
forms().approveSubmission
C#
Forms.ApproveSubmissionAsync
CLI
openemail forms approve-submission

Forms.ResendConfirmation

Email a waiting sign-up a fresh confirmation link

Scopesforms:writeemails:send
Signature
ResendConfirmation(ctx context.Context, id string, submissionID string, opts ...openemail.RequestOption) (openemail.Object, error)

Emails the person behind a pending submission a new confirmation link from the form's senderAddress, with the form's current subject, message and button. The new link works for 7 days from now. It takes no body.

To protect the person, one address gets at most one confirmation from a form every ten minutes, and five a day across the workspace. A call within ten minutes of the last one, or past the fifth in a day, sends nothing and answers confirmationSent: false, and so does a call for a submission that is already added.

Parameters

idstringRequired

Form id such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

submissionIDstringRequired

Submission id such as fsb_3c7e1a9f0b2d4c6e8a1f3b5d, from ListSubmissions or a form.submitted webhook.

openemail.WithAPIKeystring

Overrides the client's API key for this call only.

Returns

An openemail.Object: the form submission object fields plus confirmationSent, true when an email went out on this call.

Example

result, err := client.Forms.ResendConfirmation(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9", "fsb_3c7e1a9f0b2d4c6e8a1f3b5d")if err != nil {	return err} fmt.Println(result.Bool("confirmationSent"))

Notes

  • Needs emails:send as well as forms:write, because the call sends an email.

  • When confirmations cannot go out from the form's address, or it has none, the call is refused with 422 form_sender_refused on settings.senderAddress.

  • expired follows the newest confirmation link, so a resend that went out turns it false again for the next 7 days.

  • Not retried automatically, since a retry could send a second email. Read confirmationSent rather than calling again at once.

Also available in

API
POST /forms/{id}/submissions/{submissionId}/resend
TypeScript
forms.resendConfirmation()
Python
forms.resend_confirmation()
Ruby
forms.resend_confirmation
PHP
forms->resendConfirmation
Java
forms().resendConfirmation
C#
Forms.ResendConfirmationAsync
CLI
openemail forms resend-confirmation

Forms.Subscribe

Sign someone up through a published form

No credential
Signature
Subscribe(ctx context.Context, formID string, values openemail.Body, opts ...openemail.RequestOption) (openemail.Object, error)

Posts a sign-up to a published form, as a visitor of its hosted page does, and answers with the outcome. It sends no credential, even from a client that holds one, so it works for any published form, in this workspace or another.

values holds the answers keyed by field key, and every form has the field keyed email. A text field takes a string, a number field a number or a numeric string, a checkbox or consent field true or false, a dropdown or single choice field one option value, and a multiple choice or audience field an array of option values. A key the form does not have is ignored, and a key starting oe_ is never stored as an answer.

On a double opt-in form outcome is pending: the person joins the audiences once they open a confirmation link, emailed after this answer unless this form emailed that address in the last ten minutes or the address has had five from this workspace today. Otherwise they join at once and outcome is added. Someone who unsubscribed from an audience stays unsubscribed unless they confirm through a double opt-in form.

Parameters

formIDstringRequired

The id of a published form, such as frm_8d2f6a1c9b3e47d0a5f1c2e9.

emailstringRequired

The address to sign up, the answer to the email field every form has.

oe_sourcestring

The page the form was filled in on, stored as its origin and path, at most 500 characters. A call from a server has no Referer to fall back on, so pass it to keep the source.

openemail.WithAPIKeystring

Ignored and never sent. Signing up needs no credential.

Returns

An openemail.Object with object set to form_subscription, formId, outcome and redirectUrl. outcome is added or pending, and redirectUrl is where the form sends people after a sign-up when it is set to redirect, otherwise null.

Example

result, err := client.Forms.Subscribe(ctx, "frm_8d2f6a1c9b3e47d0a5f1c2e9", openemail.Body{	"email":      "[email protected]",	"first_name": "Ada",	"topics":     []string{"product", "events"},	"oe_source":  "https://acme.com/launch",})if err != nil {	return err} fmt.Println(result.String("outcome"))

Notes

  • An answer that is missing or not valid is a 422 invalid_form_submission, and nothing is stored. The error's fields lists one a map with key and error per problem, such as a map with key set to email and error set to email, and its body holds the whole response. A form that was never published is a 404 form_not_found, a paused one a 409 form_closed.

  • Every post to any of the workspace's forms counts toward a limit of 40 every 10 minutes from one network, whatever the outcome, counted by the address the request comes from, so a server that relays sign-ups for many people shares one allowance. Past it the call is refused with 429 form_rate_limited.

  • Leave out oe_started, and send oe_website empty or not at all: they catch bots. A sign-up that fills oe_website, or carries an oe_started token that is not valid or is under 1.5 seconds old, gets a normal answer and is dropped.

  • Not retried automatically, since a retry after a lost response would store a second submission on a single opt-in form. On a double opt-in form, signing up again before confirming updates the waiting submission instead, and form.submitted fires again only when the answers changed.

Also available in

API
POST /subscribe/{formId}
TypeScript
forms.subscribe()
Python
forms.subscribe()
Ruby
forms.subscribe
PHP
forms->subscribe
Java
forms().subscribe
C#
Forms.SubscribeAsync
CLI
openemail forms subscribe