openemail.forms
Каждый метод этого пространства имён: его сигнатура, параметры, что он возвращает, и пример.
Методы
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()forms.list_all()forms.iterate()forms.create()forms.design()forms.redesign()forms.list_starters()forms.get_starter()forms.get()forms.update()forms.delete()forms.publish()forms.pause()forms.resume()forms.duplicate()forms.analytics()forms.list_submissions()forms.list_all_submissions()forms.iterate_submissions()forms.get_submission()forms.delete_submission()forms.delete_submissions()forms.approve_submission()forms.resend_confirmation()forms.subscribe()
forms.list()
List one page of the sign-up forms in the workspace
def list( *, limit: int | None = None, cursor: str | None = None, api_key: str | None = None, timeout: float | None = None,) -> Page[FormResource]Returns one page of the sign-up forms the caller can reach, newest first, without their documents or settings. Paging is keyset: limit= takes 1 to 100 and defaults to 25, and nextCursor goes back as cursor= while hasMore is True. list_all 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.
Параметры
limitintRows per page, a whole number from 1 to 100. The server defaults to 25.
cursorstrThe
nextCursorfrom the previous page, passed back exactly as it came. One the server cannot read is a 400invalid_cursor.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
Page[FormResource], a dict with items, hasMore and nextCursor. Each item has id, name, description, status, url, subscribeUrl, audienceIds, doubleOptIn, hasUnpublishedChanges, stats, publishedAt, createdAt and updatedAt.
Пример
from openemail import openemail page = openemail.forms.list(limit=50) for form in page['items']: print(form['name'], form['status'], f'{form["stats"]["submissions"]} sign-ups') if page['hasMore']: print('next page starts after', page['nextCursor'])Примечания
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.
Также доступно в
- API
GET /forms- TypeScript
forms.list()- Ruby
forms.list- CLI
openemail forms list
forms.list_all()
Collect every sign-up form you can reach into one list
def list_all( *, limit: int | None = None, cursor: str | None = None, api_key: str | None = None, timeout: float | None = None,) -> builtins.list[FormResource]Follows nextCursor from page to page and returns every form the caller can reach as one list, newest first, in the shape list returns. limit= sets the page size of each request, not the total.
Параметры
limitintPage size per request, from 1 to 100, defaulting to 25 on the server.
cursorstrA
nextCursorfrom an earlier page to start after.api_keystrOverrides the client's API key for every page of this walk.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
list[FormResource] holding every form across all pages.
Пример
from openemail import openemail forms = openemail.forms.list_all(limit=100) live = [form for form in forms if form['status'] == 'live'] print(f'{len(live)} of {len(forms)} forms take sign-ups')Примечания
A failure on any page raises, and the forms already fetched are discarded.
Также доступно в
- API
GET /forms- TypeScript
forms.listAll()- Ruby
forms.list_all
forms.iterate()
Stream the sign-up forms you can reach one at a time
def iterate( *, limit: int | None = None, cursor: str | None = None, api_key: str | None = None, timeout: float | None = None,) -> Iterator[FormResource]Returns a generator that yields forms one by one, newest first, and requests the next page only once the current one is used up. Breaking out of the loop stops the requests.
Параметры
limitintPage size per request, from 1 to 100, defaulting to 25 on the server.
cursorstrA
nextCursorfrom an earlier page to start after.api_keystrOverrides the client's API key for every page of this walk.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
Iterator[FormResource], a generator yielding one form per step.
Пример
from openemail import openemail for form in openemail.forms.iterate(): if form['hasUnpublishedChanges']: print('Unpublished edits:', form['name'], form['url'])Примечания
The generator is lazy, so an abandoned loop costs only the pages it read.
Также доступно в
- API
GET /forms- TypeScript
forms.iterate()- Ruby
forms.iterate
forms.create()
Create a sign-up form
def create( body: FormCreate, *, api_key: str | None = None, timeout: float | None = None,) -> FormDetailResourceMakes 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.
Параметры
body['name']strОбязательноWhat the workspace calls the form, trimmed, 1 to 120 characters. Visitors never see it, and it need not be unique.
body['description']str | NoneA note for the workspace, at most 500 characters. Visitors never see it.
body['starter']FormStarterSlugBegin from a starter:
blank,newsletter,waitlist,event,early-accessorcontact, andFORM_STARTER_SLUGSnames them. Ignored whendocumentis sent.body['document']FormDocumentThe whole form,
fields,copyandstyle, in the shapegetandget_starterreturn. Every field carries all 15 of its keys, withNonefor the ones it does not use, and a document needs exactly one email field, keyedemailand required, and its field keys and ids must be unique. Leave it out to usestarteror a one-field email form.body['settings']['audienceIds']list[str]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 404audience_not_found.body['settings']['doubleOptIn']boolEmail each person a confirmation link and add them only once they open it. Defaults to
False.TrueneedssenderAddress.body['settings']['senderAddress']str | NoneThe 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 422form_sender_refused.body['settings']['confirmSubject']strThe subject of the confirmation email, 1 to 200 characters. Defaults to Please confirm your subscription.
body['settings']['confirmMessage']strThe text of the confirmation email above its button, at most 2000 characters.
body['settings']['confirmButton']strThe label of the confirmation button, 1 to 60 characters. Defaults to Confirm my subscription.
body['settings']['successAction']FormSuccessActionWhat a person sees after signing up:
message, the thank-you copy in the document, which is the default, orredirect, which sends them toredirectUrl.body['settings']['redirectUrl']str | NoneThe http or https page
redirectsends people to, at most 2000 characters. Required whilesuccessActionisredirect.body['settings']['notifyAddresses']list[str]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.body['publish']boolPublish in the same call, so the form takes sign-ups at once.
api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormDetailResource: the FormResource fields, with status draft, or live when publish was True, plus document, publishedDocument, settings, audiences, senderIssue and senderProblem.
Пример
from openemail import openemail form = openemail.forms.create( { 'name': 'Newsletter', 'starter': 'newsletter', 'settings': {'audienceIds': ['aud_9f2c4b7e1a0d63d84c5f2e7b']}, 'publish': True, }) print(form['id'], form['status'], form['url'])Примечания
A value of the wrong type or length is 422
invalid_parameterwithparamnaming it, and a key the body does not take is 422unknown_parameter. A document or settings that break a rule, such as a form without its required email field,doubleOptInwithoutsenderAddressorredirectwithoutredirectUrl, is 422invalid_form.A key or app limited to particular addresses may only name addresses it holds in
senderAddressandnotifyAddresses. Anything else is 422capability_unsupported.Turning on
doubleOptIn, naming asenderAddressor changing the confirmation email also needsemails: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.
Также доступно в
- API
POST /forms- TypeScript
forms.create()- Ruby
forms.create- CLI
openemail forms create
forms.design()
Design a form from a brief
def design( body: FormDesignInput, *, api_key: str | None = None, timeout: float | None = None,) -> DesignedFormResourceA 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, as long as the client's default timeout, so give the call a longer timeout=.
Параметры
body['brief']strОбязательноEverything the form must ask and say, up to 4,000 characters.
body['name']strA short name for the form, up to 120 characters. Left out, the designer names it.
api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
DesignedFormResource: the new draft, as get returns it, plus design, whose notes say what the designer adjusted or left out.
Пример
from openemail import openemail 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.') form = openemail.forms.design({'brief': brief, 'name': 'Beta waitlist'}, timeout=60) print(form['id'], form['status'], form['design']['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 422workspace_limit_reached, a server with no model a 409ai_not_configured, and a workspace out of AI actions a 429ai_quota_exceeded. Nothing is created in any of them.
Также доступно в
- API
POST /forms/design- TypeScript
forms.design()- Ruby
forms.design- CLI
openemail forms design
forms.redesign()
Change a form’s design from instructions
def redesign( id: str, body: FormRedesignInput, *, api_key: str | None = None, timeout: float | None = None,) -> DesignedFormResourceA 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, as long as the client's default timeout, so give the call a longer timeout=.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.body['instructions']strОбязательноWhat to change, with every detail, up to 4,000 characters.
body['expectedUpdatedAt']strThe
updatedAtyou read, as it came. When the form was saved since, nothing is written and the answer is 409version_conflict.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
DesignedFormResource: the form with the change in its draft, plus design, whose notes say what the designer changed.
Пример
from openemail import openemail current = openemail.forms.get('frm_8d2f6a1c9b3e47d0a5f1c2e9') form = openemail.forms.redesign( current['id'], { 'instructions': 'Add a required company field and make the button say Join.', 'expectedUpdatedAt': current['updatedAt'], }, timeout=60,) print(form['hasUnpublishedChanges'], form['design']['notes'])Примечания
The SDK does not retry it, because each call is a fresh design pass.
The same errors as
design, plus 404form_not_foundfor a form the caller cannot reach and 409version_conflictwhen the form was saved sinceexpectedUpdatedAt, or while the designer worked.
Также доступно в
- API
POST /forms/{id}/redesign- TypeScript
forms.redesign()- Ruby
forms.redesign- CLI
openemail forms redesign
forms.list_starters()
List the starting points for a new form
def list_starters( *, api_key: str | None = None, timeout: float | None = None,) -> builtins.list[FormStarterResource]Returns the starters the console offers when somebody makes a form, as a plain list: 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 get_starter 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.
Параметры
api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
list[FormStarterResource], each a dict with object set to form_starter, slug, name and description.
Пример
from openemail import openemail starters = openemail.forms.list_starters() for starter in starters: print(starter['slug'], starter['name'], starter['description'])Примечания
Not paginated, and there is no cursor. The list is short.
Также доступно в
forms.get_starter()
Retrieve one starter with its document
def get_starter( slug: str, *, api_key: str | None = None, timeout: float | None = None,) -> FormStarterDetailResourceReturns one starter in full: everything list_starters 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.
Параметры
slugstrОбязательноA starter slug from
list_starters:blank,newsletter,waitlist,event,early-accessorcontact.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormStarterDetailResource: object, slug, name and description, plus document with fields, copy and style.
Пример
from openemail import openemail starter = openemail.forms.get_starter('newsletter') document = starter['document']document['copy']['title'] = 'Get our product news' form = openemail.forms.create({'name': 'Product news', 'document': document}) print(form['id'], len(form['document']['fields']))Примечания
An unknown slug is a 404
form_starter_not_found.Passing
'starter': 'newsletter'tocreatedoes the same seeding on the server, in one call instead of two.
Также доступно в
forms.get()
Retrieve a form with its documents and settings
def get( id: str, *, api_key: str | None = None, timeout: float | None = None,) -> FormDetailResourceReturns the whole form: the fields list carries with fresh stats, the draft document, the publishedDocument visitors see, which is None 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 None when confirmations can go out.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormDetailResource: id, name, description, status, url, subscribeUrl, audienceIds, doubleOptIn, hasUnpublishedChanges, stats, publishedAt, createdAt and updatedAt, plus document, publishedDocument, settings, audiences, each a dict with id, name and builtin, senderIssue and senderProblem.
Пример
from openemail import openemail form = openemail.forms.get('frm_8d2f6a1c9b3e47d0a5f1c2e9') print(form['status'], form['url'])print([field['key'] for field in form['document']['fields']]) if form['senderIssue']: print(form['senderProblem'])Примечания
A form in another workspace is a 404
form_not_found, never a 403, and raisesOpenEmailApiErrorwithis_not_foundset. An app a member connected reaches only the forms that member made.audienceIdsandsettings.audienceIdsleave out an audience that was deleted after it was chosen, or that the caller cannot reach.
Также доступно в
- API
GET /forms/{id}- TypeScript
forms.get()- Ruby
forms.get- CLI
openemail forms get
forms.update()
Change a form's name, document or settings
def update( id: str, patch: FormPatch, *, api_key: str | None = None, timeout: float | None = None,) -> FormDetailResourceA partial update: a field you leave out keeps its stored value, and 'description': None 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.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.patch['name']strNew name, trimmed, 1 to 120 characters.
patch['description']str | NoneNew note, at most 500 characters.
Noneclears it.patch['document']FormDocumentThe new draft, whole, in the shape
getreturns. Every field carries all 15 of its keys, and a document needs exactly one email field, keyedemailand required, and its field keys and ids must be unique.patch['settings']['audienceIds']list[str]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 404audience_not_found.patch['settings']['doubleOptIn']boolEmail each person a confirmation link and add them only once they open it.
TrueneedssenderAddress.patch['settings']['senderAddress']str | NoneThe 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 422form_sender_refused.patch['settings']['confirmSubject']strThe subject of the confirmation email, 1 to 200 characters.
patch['settings']['confirmMessage']strThe text of the confirmation email above its button, at most 2000 characters.
patch['settings']['confirmButton']strThe label of the confirmation button, 1 to 60 characters.
patch['settings']['successAction']FormSuccessActionWhat a person sees after signing up:
message, the thank-you copy in the document, orredirect, which sends them toredirectUrl.patch['settings']['redirectUrl']str | NoneThe http or https page
redirectsends people to, at most 2000 characters. Required whilesuccessActionisredirect.patch['settings']['notifyAddresses']list[str]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.patch['expectedUpdatedAt']strThe
updatedAtyou read, as it came. When the form was saved since, nothing is written and the answer is 409version_conflict.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormDetailResource as it stands after the change, with a new updatedAt.
Пример
from openemail import openemail form = openemail.forms.get('frm_8d2f6a1c9b3e47d0a5f1c2e9') updated = openemail.forms.update( form['id'], { 'settings': {'doubleOptIn': True, 'senderAddress': '[email protected]'}, 'expectedUpdatedAt': form['updatedAt'], },) print(updated['doubleOptIn'], updated['hasUnpublishedChanges'])Примечания
The checks
createmakes apply to what you send: 422invalid_parameter,unknown_parameter,invalid_form,form_sender_refusedorcapability_unsupported, and 404audience_not_foundonsettings.audienceIds, each withparamnaming the field.Turning on
doubleOptIn, naming asenderAddressor changing the confirmation email also needsemails: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 409version_conflictbecause the first attempt went through, so read the form before trying again.
Также доступно в
- API
PATCH /forms/{id}- TypeScript
forms.update()- Ruby
forms.update- CLI
openemail forms update
forms.delete()
Delete a form and every submission it holds
def delete( id: str, *, api_key: str | None = None, timeout: float | None = None,) -> DeletedFormResourceDeletes 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.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
DeletedFormResource, a dict with object set to form, the id and deleted set to True.
Пример
from openemail import OpenEmailApiError, openemail try: removed = openemail.forms.delete('frm_8d2f6a1c9b3e47d0a5f1c2e9')except OpenEmailApiError as error: if error.is_not_found: print('Already gone') else: raiseelse: print(removed['id'], removed['deleted'])Примечания
An OAuth access token needs a verification code for this call, and is refused with 403
step_up_requireduntil the app has verified one in the last 60 minutes.is_step_up_requiredon the error says so. 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.
Также доступно в
- API
DELETE /forms/{id}- TypeScript
forms.delete()- Ruby
forms.delete- CLI
openemail forms delete
forms.publish()
Put the draft of a form live
def publish( id: str, *, api_key: str | None = None, timeout: float | None = None,) -> FormDetailResourceCopies 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.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormDetailResource with status live, a new publishedAt and hasUnpublishedChanges set to False.
Пример
from openemail import OpenEmailApiError, openemail try: form = openemail.forms.publish('frm_8d2f6a1c9b3e47d0a5f1c2e9')except OpenEmailApiError as error: if error.is_validation: print('Fix the draft first:', error.code, error.param) else: raiseelse: print(form['status'], form['publishedAt'], form['url'])Примечания
A draft that breaks a rule is 422
invalid_form, and a double opt-in form that cannot send confirmations is 422form_sender_refused, each withparamnaming the field. Publishing a double opt-in form also needsemails:send. A key or app limited to some addresses is refused with 422capability_unsupportedwhen the form'ssettings.senderAddressorsettings.notifyAddressesfalls outside them.Retried automatically on network failure and retryable statuses, since publishing the same draft twice leaves the same live form.
Также доступно в
- API
POST /forms/{id}/publish- TypeScript
forms.publish()- Ruby
forms.publish- CLI
openemail forms publish
forms.pause()
Stop a published form taking sign-ups
def pause( id: str, *, api_key: str | None = None, timeout: float | None = None,) -> FormDetailResourceCloses 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.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormDetailResource with status paused.
Пример
from openemail import openemail form = openemail.forms.pause('frm_8d2f6a1c9b3e47d0a5f1c2e9') print(form['status'], form['stats']['submissions'])Примечания
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.
Также доступно в
- API
POST /forms/{id}/pause- TypeScript
forms.pause()- Ruby
forms.pause- CLI
openemail forms pause
forms.resume()
Open a paused form again
def resume( id: str, *, api_key: str | None = None, timeout: float | None = None,) -> FormDetailResourceOpens 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.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormDetailResource with status live.
Пример
from openemail import openemail form = openemail.forms.resume('frm_8d2f6a1c9b3e47d0a5f1c2e9') print(form['status'], form['hasUnpublishedChanges'])Примечания
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
senderAddresscan no longer send confirmations is refused with 422form_sender_refused, and one that breaks a rule with 422invalid_form. Resuming a double opt-in form also needsemails:send. A key or app limited to some addresses is refused with 422capability_unsupportedwhen the form'ssettings.senderAddressorsettings.notifyAddressesfalls outside them.Retried automatically on network failure and retryable statuses, since a second resume changes nothing.
Также доступно в
- API
POST /forms/{id}/resume- TypeScript
forms.resume()- Ruby
forms.resume- CLI
openemail forms resume
forms.duplicate()
Copy a form into a new draft
def duplicate( id: str, *, api_key: str | None = None, timeout: float | None = None,) -> FormDetailResourceMakes 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.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormDetailResource for the new form, with its own id, status draft, publishedDocument set to None and empty stats.
Пример
from openemail import openemail copy = openemail.forms.duplicate('frm_8d2f6a1c9b3e47d0a5f1c2e9') renamed = openemail.forms.update(copy['id'], {'name': 'Spring launch'}) print(renamed['id'], renamed['name'], renamed['status'])Примечания
It counts toward the workspace limit of 100 forms, as
createdoes, and is refused with 422workspace_limit_reachedpast it. A key or app limited to some addresses is refused with 422capability_unsupportedwhen the form'ssettings.senderAddressorsettings.notifyAddressesfalls outside them.Not retried automatically, since a retry after a lost response would make a second copy. List the forms and delete the spare.
Также доступно в
forms.analytics()
Read views, sign-ups and people added over a window
def analytics( id: str, *, days: int | None = None, minutes: int | None = None, grain: TrackingGrain | None = None, offset_minutes: int | None = None, api_key: str | None = None, timeout: float | None = None,) -> FormAnalyticsResourceReturns 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 offset_minutes= 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.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.daysintHow far back to look, from 1 to 1095, defaulting to 30.
minutesintThe window in minutes, from 1 to 1576800, which wins over
days=when both are sent.grainTrackingGrainBucket width:
minute,hourorday, defaulting today.offset_minutesintMinutes east of UTC to cut the buckets in, from -840 to 840, defaulting to 0. Pass
time.localtime().tm_gmtoff // 60for the local zone.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormAnalyticsResource, a dict 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 has bucket, views, submissions and added.
Пример
import time from openemail import openemail analytics = openemail.forms.analytics( 'frm_8d2f6a1c9b3e47d0a5f1c2e9', days=90, grain='day', offset_minutes=time.localtime().tm_gmtoff // 60,) totals = analytics['totals']print(totals['views'], totals['submissions'], totals['conversion'])Примечания
totals.conversionis submissions divided by views over the window, afloatof at most 1, andNonewhen the window has no views.totals.pendingcounts the submissions made in the window that are still waiting, andtotals.lastSubmittedAtis 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.
Также доступно в
forms.list_submissions()
List one page of a form's submissions
def list_submissions( id: str, *, limit: int | None = None, cursor: str | None = None, q: str | None = None, status: FormSubmissionStatus | None = None, api_key: str | None = None, timeout: float | None = None,) -> Page[FormSubmissionResource]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: limit= takes 1 to 100 and defaults to 25, and nextCursor, a submission id, goes back as cursor= while hasMore is True. list_all_submissions and iterate_submissions 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 approve_submission, or send a fresh link with resend_confirmation.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.limitintRows per page, a whole number from 1 to 100. The server defaults to 25.
cursorstrThe
nextCursorfrom the previous page, a submission id of this form. One that names no submission of this form is a 400invalid_cursor.qstrOnly submissions whose email address contains this text, ignoring case, at most 200 characters.
statusFormSubmissionStatusOnly
pendingor onlyaddedsubmissions.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
Page[FormSubmissionResource], a dict with items, hasMore and nextCursor. Each item has id, formId, email, status, expired, answers, audienceIds, sourceUrl, confirmedAt and createdAt, and each answer is a dict with fieldId, key, label, type, value and display.
Пример
from openemail import openemail page = openemail.forms.list_submissions('frm_8d2f6a1c9b3e47d0a5f1c2e9', status='pending') for submission in page['items']: print(submission['email'], 'link expired' if submission['expired'] else 'waiting')Примечания
valueon an answer is astrfor most fields, alist[str]of option values for a multiple choice or audience field, and aboolfor a checkbox or consent field.displayis the same answer as a person reads it, option labels included.sourceUrlis the page the form was filled in on, its origin and path only and at most 500 characters, when it was known.
Также доступно в
forms.list_all_submissions()
Collect every submission of a form into one list
def list_all_submissions( id: str, *, limit: int | None = None, cursor: str | None = None, q: str | None = None, status: FormSubmissionStatus | None = None, api_key: str | None = None, timeout: float | None = None,) -> builtins.list[FormSubmissionResource]Follows nextCursor from page to page and returns every submission of the form that matches q= and status= as one list, newest first. limit= sets the page size of each request, not the total.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.limitintPage size per request, from 1 to 100, defaulting to 25 on the server.
cursorstrA submission id to start after.
qstrOnly submissions whose email address contains this text, ignoring case, at most 200 characters.
statusFormSubmissionStatusOnly
pendingor onlyaddedsubmissions.api_keystrOverrides the client's API key for every page of this walk.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
list[FormSubmissionResource] holding every matching submission across all pages.
Пример
from openemail import openemail added = openemail.forms.list_all_submissions( 'frm_8d2f6a1c9b3e47d0a5f1c2e9', status='added', limit=100) print('\n'.join(submission['email'] for submission in added))Примечания
A failure on any page raises, and the submissions already fetched are discarded. For a large form,
iterate_submissionsholds one page in memory at a time.
Также доступно в
forms.iterate_submissions()
Stream a form's submissions one at a time
def iterate_submissions( id: str, *, limit: int | None = None, cursor: str | None = None, q: str | None = None, status: FormSubmissionStatus | None = None, api_key: str | None = None, timeout: float | None = None,) -> Iterator[FormSubmissionResource]Returns a generator that yields submissions one by one, newest first, and requests the next page only once the current one is used up. Breaking out of the loop stops the requests.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.limitintPage size per request, from 1 to 100, defaulting to 25 on the server.
cursorstrA submission id to start after.
qstrOnly submissions whose email address contains this text, ignoring case, at most 200 characters.
statusFormSubmissionStatusOnly
pendingor onlyaddedsubmissions.api_keystrOverrides the client's API key for every page of this walk.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
Iterator[FormSubmissionResource], a generator yielding one submission per step.
Пример
from openemail import openemail submissions = openemail.forms.iterate_submissions('frm_8d2f6a1c9b3e47d0a5f1c2e9', q='acme.com') for submission in submissions: print(submission['email'], submission['createdAt'])Примечания
The generator is lazy, so an abandoned loop costs only the pages it read.
Также доступно в
forms.get_submission()
Retrieve one submission with every answer
def get_submission( id: str, submission_id: str, *, api_key: str | None = None, timeout: float | None = None,) -> FormSubmissionResourceReturns 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.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.submission_idstrОбязательноSubmission id such as
fsb_3c7e1a9f0b2d4c6e8a1f3b5d, fromlist_submissionsor aform.submittedwebhook.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormSubmissionResource with id, formId, email, status, expired, answers, audienceIds, sourceUrl, confirmedAt and createdAt.
Пример
from openemail import openemail submission = openemail.forms.get_submission( 'frm_8d2f6a1c9b3e47d0a5f1c2e9', 'fsb_3c7e1a9f0b2d4c6e8a1f3b5d') for answer in submission['answers']: print(f'{answer["label"]}: {answer["display"]}')Примечания
A form you cannot reach is a 404
form_not_found, and a submission that is not on this form is a 404form_submission_not_found. Both raiseOpenEmailApiErrorwithis_not_foundset, so tell them apart bycode.confirmedAtis when the person joined the audiences, andNonewhile the submission is pending.
Также доступно в
forms.delete_submission()
Delete one submission of a form
def delete_submission( id: str, submission_id: str, *, api_key: str | None = None, timeout: float | None = None,) -> DeletedFormSubmissionResourceDeletes 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.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.submission_idstrОбязательноSubmission id such as
fsb_3c7e1a9f0b2d4c6e8a1f3b5d, fromlist_submissionsor aform.submittedwebhook.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
DeletedFormSubmissionResource, a dict with object set to form_submission, id, formId and deleted set to True.
Пример
from openemail import openemail removed = openemail.forms.delete_submission( 'frm_8d2f6a1c9b3e47d0a5f1c2e9', 'fsb_3c7e1a9f0b2d4c6e8a1f3b5d') print(removed['id'], removed['deleted'])Примечания
To take the person off your lists as well, remove them from the audience with
audiences.remove_contact, or delete the contact withcontacts.delete.The SDK does not retry a delete. A 404
form_submission_not_foundon your own second attempt after a lost response means the first one worked.
Также доступно в
forms.delete_submissions()
Delete up to 200 submissions of a form in one call
def delete_submissions( id: str, submission_ids: Sequence[str], *, api_key: str | None = None, timeout: float | None = None,) -> FormSubmissionBatchDeleteResourceDeletes many submissions of one form and returns 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.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.submission_idsSequence[str]ОбязательноFrom 1 to 200 submission ids of this form, such as
fsb_3c7e1a9f0b2d4c6e8a1f3b5d, sent asids. An empty list or more than 200 is a 422invalid_parameteronids.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormSubmissionBatchDeleteResource, a dict with object set to form_submission_batch, formId and deleted, which counts the submissions this call removed.
Пример
from openemail import openemail result = openemail.forms.delete_submissions( 'frm_8d2f6a1c9b3e47d0a5f1c2e9', ['fsb_3c7e1a9f0b2d4c6e8a1f3b5d', 'fsb_9a4c2e7f1b3d5a6c8e0f2b4d'],) print(f'{result["deleted"]} submissions deleted')Примечания
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
deletedcan read 0.
Также доступно в
forms.approve_submission()
Add a waiting sign-up without its confirmation
def approve_submission( id: str, submission_id: str, *, api_key: str | None = None, timeout: float | None = None,) -> FormSubmissionResourceAdds 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.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.submission_idstrОбязательноSubmission id such as
fsb_3c7e1a9f0b2d4c6e8a1f3b5d, fromlist_submissionsor aform.submittedwebhook.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormSubmissionResource, now with status added and confirmedAt set.
Пример
from openemail import openemail submission = openemail.forms.approve_submission( 'frm_8d2f6a1c9b3e47d0a5f1c2e9', 'fsb_3c7e1a9f0b2d4c6e8a1f3b5d') print(submission['status'], submission['confirmedAt'])Примечания
It needs
contacts:writeas well asforms:write, since it adds a contact. A key without both is refused with 403insufficient_scope, andis_scope_missingon the error says so.Retried automatically on network failure and retryable statuses, since approving an added submission changes nothing.
Также доступно в
forms.resend_confirmation()
Email a waiting sign-up a fresh confirmation link
def resend_confirmation( id: str, submission_id: str, *, api_key: str | None = None, timeout: float | None = None,) -> ResentFormConfirmationResourceEmails 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 returns confirmationSent set to False, and so does a call for a submission that is already added.
Параметры
idstrОбязательноForm id such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.submission_idstrОбязательноSubmission id such as
fsb_3c7e1a9f0b2d4c6e8a1f3b5d, fromlist_submissionsor aform.submittedwebhook.api_keystrOverrides the client's API key for this call only.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
ResentFormConfirmationResource: the FormSubmissionResource fields plus confirmationSent, True when an email went out on this call.
Пример
from openemail import openemail result = openemail.forms.resend_confirmation( 'frm_8d2f6a1c9b3e47d0a5f1c2e9', 'fsb_3c7e1a9f0b2d4c6e8a1f3b5d') if result['confirmationSent']: print('Sent a fresh link to', result['email'])else: print('Nothing sent, try again later')Примечания
Needs
emails:sendas well asforms: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_refusedonsettings.senderAddress.expiredfollows the newest confirmation link, so a resend that went out turns itFalseagain for the next 7 days.Not retried automatically, since a retry could send a second email. Read
confirmationSentrather than calling again at once.
Также доступно в
forms.subscribe()
Sign someone up through a published form
def subscribe( form_id: str, values: FormSubscribeValues, *, api_key: str | None = None, timeout: float | None = None,) -> FormSubscriptionResourcePosts a sign-up to a published form, as a visitor of its hosted page does, and returns 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 str, a number field an int, a float 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 a list 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 call returns 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.
Параметры
form_idstrОбязательноThe id of a published form, such as
frm_8d2f6a1c9b3e47d0a5f1c2e9.values['email']strОбязательноThe address to sign up, the answer to the email field every form has.
values['oe_source']strThe page the form was filled in on, stored as its origin and path, at most 500 characters. A call from a server has no
Refererto fall back on, so pass it to keep the source.api_keystrIgnored and never sent. Signing up needs no credential.
timeoutfloatSeconds this call may take, the response included, before it raises
OpenEmailNetworkErrorwithis_timeout. It overrides the client'stimeoutfor this call, and0turns the limit off.
Возвращает
FormSubscriptionResource, a dict 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 None.
Пример
from openemail import openemail result = openemail.forms.subscribe( 'frm_8d2f6a1c9b3e47d0a5f1c2e9', { 'email': '[email protected]', 'first_name': 'Ada', 'topics': ['product', 'events'], 'oe_source': 'https://acme.com/launch', },) print(result['outcome'], result['redirectUrl'])Примечания
An answer that is missing or not valid is a 422
invalid_form_submission, and nothing is stored.fieldson the error lists one dict per problem, such as{'key': 'email', 'error': 'email'}, andbodyholds the whole response. A form that was never published is a 404form_not_found, a paused one a 409form_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 sendoe_websiteempty or not at all: they catch bots. A sign-up that fillsoe_website, or carries anoe_startedtoken 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.submittedfires again only when the answers changed.