Developers Email

Platform

Email

Frontpage Email: drafts, templates, series, sends, form confirmations, order receipts, the monthly Email sends allowance, CLI verbs, and the public v1 API.

What this is

Email is how a site writes to someone. It is a platform capability, like People or Store. It is not a module and it is not a widget. Do not invent markup for a campaign. Do not enable it through frontpage modules enable.

Two doors share one monthly allowance, labeled Email sends.

  • Mailings (frontpage mail) is campaigns: saved drafts, reusable templates, a series of those templates, one-off sends, the send ledger, enrolments, and the suppression list. Growth, Scale, and Enterprise. Each message carries a postal line and an unsubscribe link.
  • Triggered mail is the message a visitor just caused: a form confirmation, the staff note to notifyEmail, and a store receipt. Starter and above, while the month still has room. These go out immediately. They do not use the campaign footer.
frontpage mail settings set --postal "100 Main St, Springfield" --reply-to you@example.com
frontpage mail template create --file welcome.json --name Welcome
frontpage mail series create --name Welcome
frontpage mail series steps set <series-id> --step template=<template-id>,delay=0
frontpage mail series activate <series-id>
frontpage mail send series <series-id> --to ada@example.com --var first_name=Ada
frontpage modules forms set --instance start --notify-email sales@example.com --confirm-template <template-id>

frontpage mail is in current frontpage-host. If the binary says Unknown command: mail, upgrade with npm i -g frontpage-host@latest. --confirm-template on forms requires frontpage-host 0.1.23 or later. Older installs report that flag as unknown.

Why these are not the same command

Agents keep routing every email through one verb. They are siblings.

Job What it sends Command Plan
Campaigns and series Drafts, templates, one-offs, and series steps, with postal line and unsubscribe frontpage mail … Growth+
Form confirmation One template, or the fixed “We received your message” note, to the person who submitted frontpage modules forms set --confirm-template or --confirm Starter+ to send. Free can save the choice
Staff notify A plain copy of the submission to one address. Not a template --notify-email on forms, booking, or signups Starter+ to send
Signup confirmation The fixed confirmation only. Not a template frontpage modules signups set --confirm yes|no Starter+ to send
Order receipt One buyer template, or the stock receipt. Owner “you got paid” stays Store settings in the editor. No CLI flag Starter+ (the Store panel)
People Nothing. People does not send mail frontpage people … Growth to read and write
Inbox Nothing. The raw ledger frontpage inbox … Any member who can read the site
  • A form confirmation is not a campaign. Do not enrol the submitter from mail send to “confirm” a form.
  • Choosing a template on a form does not start a series. A series is not attached to a form or an order.
  • Login codes, team invites, and Frontpage billing mail are not this feature and do not count.

Who can send

Plan Email sends / month Templates, form confirmation, order receipt frontpage mail
Free 0 The form can store a template id. Nothing is emailed. No Emails screen. No Store panel 403 Mailings is included on Growth.
Starter 500 Create and attach templates in the editor. Campaigns and Series on that screen are the upgrade card 403. The CLI talks to the campaign API, which is Growth+
Growth 2,000 Yes Yes
Scale 5,000 Yes Yes
Enterprise 25,000 Yes Yes

On a plan that includes frontpage mail, the key still has to be a member of --site. Viewer can list and get. Editor and admin can create, send, and delete. A viewer mutate is HTTP 403. A key that is not a member gets HTTP 404 Site not found. Same fence as People and Store. See Isolation & keys.

  • --site is a subdomain slug (acme). Not a custom domain, not acme.frontpage.host, not a site UUID. Resolution order is the same as People: this command’s --site, then .frontpage/state.json from frontpage use, then exit 2.
  • There is not a second campaign allowance. Campaign recipients and series steps draw from Email sends.
  • At the month’s allowance the form still saves the submission. The confirmation and the staff email are skipped until the month resets. A campaign send returns 429 This month's email sends are used. There is no grace past 100%.
  • Downgrade keeps the template id on the form and the saved campaigns. Sends stop. Nothing is deleted. Upgrade turns them back on.

Settings

A campaign or series will not leave until the site has a postal line. That line is the physical address in the footer. Reply-to is where a human reply goes. From-name is the display name. Timezone is an IANA zone and defaults to UTC.

frontpage mail settings
frontpage mail settings get
frontpage mail settings set --postal "100 Main St, Springfield" --reply-to you@example.com --from-name "Acme" --timezone America/Chicago
Flag API field Rule
--postal or --postal-line postalLine Required before a campaign sends. Trimmed, max 300 characters. Empty clears it
--reply-to replyTo A valid email, or empty to clear. Invalid non-empty is 400 Reply-to must be a valid email address.
--from-name fromName Max 100 characters. Empty clears it
--timezone timezone IANA zone, e.g. America/Chicago. Anything else is 400 Timezone must be a valid IANA zone.

Only flags you pass are patched. Human output of settings get is timezone postal reply-to on one line.

Two more fields exist on the API and are not CLI flags. sendWindowStart and sendWindowEnd are minutes from midnight in that timezone, 0 through 1440. Defaults are 480 and 1200 (08:00–20:00). Later steps of a series wait until that window. hourlyCap is an integer from 1 to 1000, or null to use the default pace of 100 campaign sends an hour. That pace is not the monthly Email sends allowance. Form confirmations and order receipts do not wait for the window and do not use this pace.

Drafts and templates

A draft (mail email) is one unsent email. A template (mail template) is reusable: series steps point at templates, and a form or a store receipt can point at one template. The verbs are the same.

frontpage mail email list
frontpage mail email get <id>
frontpage mail email create --file draft.json --name Hours
frontpage mail email set <id> --subject "We are open"
frontpage mail email preview <id>
frontpage mail email rm <id>

frontpage mail template list --q welcome --limit 50
frontpage mail template get <id>
frontpage mail template create --file welcome.json
frontpage mail template set <id> --file welcome.json
frontpage mail template preview <id>
frontpage mail template rm <id>
  • email list is unsent drafts only. A sent one-off leaves that list. Read it with mail sends list.
  • List flags: --q (name or subject), --limit (1–200, default 50), --cursor (opaque, from the previous page’s nextCursor). Do not invent a cursor.
  • Create requires --file, a JSON object. --name, --subject, and --preheader override the file. The file must still include blocks, and on create it must include name, subject, and preview text unless those flags supply them.
  • set patches only the keys you pass. --file is optional on set.
  • The id is a UUID from list or create. Positional, or --id. A non-UUID is rejected locally. A UUID from another site is 404 Document not found.
  • Caps: 50 unsent drafts per site, 50 live templates per site. Past that, create fails with the cap message.
  • Name max 120. Subject max 200. Preview text (preheader) max 200 and required. Empty name, subject, or preview text is 400.
  • rm is a soft delete. A template still used by a series is 409 This template is used by a series. A template still chosen on a form is 409 and names those forms. Remove it from the form first. A sent draft cannot be edited: A sent email cannot be edited. Duplicate it into a new draft.
  • preview compiles the saved document and prints the subject. It does not send and does not count.

The JSON file

Blocks are structured. Owner HTML is stripped. A token looks like {{first_name}}. Declare every token you wrote, except the two built-ins first_name and unsubscribe_url. A token that is not declared is rejected on save: Unknown template variable: ….

{
  "name": "Welcome",
  "subject": "Hello {{first_name}}",
  "preheader": "You are on the list",
  "variables": ["organization_name"],
  "blocks": [
    { "type": "heading", "level": 1, "text": "Welcome" },
    { "type": "paragraph", "text": "Thanks from {{organization_name}}. See [the site](https://example.com)." },
    { "type": "button", "label": "Visit", "href": "https://example.com" },
    { "type": "image", "src": "https://example.com/logo.png", "alt": "Logo" },
    { "type": "divider" },
    { "type": "list", "style": "bullet", "items": ["One", "Two"] }
  ]
}
Block Required Notes
heading level 1 or 2, text Optional align: left, center, right
paragraph text A small subset: **bold**, *italic*, and [label](https://…). Links must be http, https, or mailto
button label, href href must start with http:, https:, or mailto:
image src, alt PNG, JPEG, or WebP on https, or a site upload path /__media/uploads/…. At most 10 images. Optional width and height from 16 to 1600. Every image needs a label
divider none
list style bullet or number, items At least one non-empty item

Variable names are lowercase, start with a letter, and then letters, digits, _, or -, up to 64 characters. Organization Name is rejected: Variable names must be kebab or snake case. A block of text over 50,000 characters is rejected. An empty blocks array is Add at least one content block.

What a token fills

The same template document fills differently depending on which door sends it.

Door What fills the tokens Footer
Campaign or series (mail send) --var key=value on that recipient, plus {{first_name}} from the recipient name (first word) or from --var first_name=. An empty first name becomes there. {{unsubscribe_url}} is the unsubscribe link Postal line, unsubscribe, “Sent by {brand} via Frontpage”
Form confirmation Each token must equal an input name on that form. {{organization_name}} fills only when the form has name="organization_name". A token the form does not have is left blank. The message still sends. The visitor address is a field the form already collects, usually name="email". If Frontpage cannot tell which field is the address, it does not send the confirmation. The submission is still saved None. Replies go to the form’s notify email. Brand is Brand settings
Order receipt {{buyer_name}}, {{buyer_email}}, {{total}}, {{currency}}, {{items}}. Any other token is left blank. The message still sends None. Replies go to the site owner

Form confirmation

One form uses one choice. Off sends nothing to the visitor. Simple confirmation sends the fixed “We received your message” email. A template sends only that template. The staff email is separate and is not a template.

frontpage modules forms get --instance start
frontpage modules forms set --instance start --notify-email sales@example.com --confirm-template <template-id>
frontpage modules forms set --instance start --confirm yes
frontpage modules forms set --instance start --confirm-template=
  • --instance is the form slug, the same value as data-fp-form. Required on set. Do not target default.
  • --confirm-template <id> attaches that template. --confirm-template= clears it and leaves the simple confirmation alone.
  • --confirm yes or --confirm no is the fixed confirmation. It is not a template. Booking and signups reject --confirm-template.
  • Human output prints a template line when a template id is set.
  • Free stores the id and stores the submission. Nothing is emailed until the plan includes Email sends.
<form data-fp-form="start">
  <input name="organization_name" />
  <input name="fundraiser_name" />
  <input type="email" name="email" />
</form>

A template for that form declares organization_name, fundraiser_name, and email, and uses those tokens. Nothing else.

Signup and booking

Signups have a notify address and a yes/no for the fixed confirmation. They cannot take a template.

frontpage modules signups set --notify-email hello@example.com --confirm no
frontpage modules booking set --notify-email book@example.com --hours "Mon–Fri 9–5"

Booking has a notify address and hours. It does not send a visitor confirmation template.

Order receipt

On Store settings, Buyer receipt is either the stock receipt or one saved template. There is no frontpage store flag and no frontpage mail verb for this. The editor control and the agent tool manage_store set_receipt_template are the writers. An empty template id keeps the stock receipt.

Declare buyer_name, buyer_email, total, currency, and items. The buyer receipt and the owner “you got paid” mail both count toward Email sends. With no template, the stock receipt stays and still counts. A series is not attached to an order. Catalog commands are on Store.

Series

A series is an ordered list of templates. You enrol people into it. Each step is one send. The first step sends immediately. Later steps wait delay after the previous step, and they also wait for the send window.

frontpage mail series list
frontpage mail series create --name Welcome
frontpage mail series get <id>
frontpage mail series set <id> --name "Welcome sequence"
frontpage mail series set <id> --status paused
frontpage mail series activate <id>
frontpage mail series pause <id>
frontpage mail series steps set <id> --step template=<id>,delay=0 --step template=<id>,delay=2d
frontpage mail series steps set <id> --file steps.json
frontpage mail series rm <id>
  • Status is draft, active, or paused. activate and pause are those two writes. set --status accepts all three. If you pass both --name and --status, status wins and the name is left as it was.
  • Activate with no steps fails: Add at least one step before activating a series. Send to a series that is not active fails: This series is not active.
  • At most 25 live series per site. At most 12 steps. Each step must point at a template, not a draft.
  • The first step’s delay is always 0. A non-zero first delay is The first series step always sends immediately.
  • Delay is minutes, or a duration: 30m, 2h, 2d. In JSON, delayMinutes, delay, or delayDays. Max one year. steps.json is either { "steps": [ … ] } or a bare array.
  • Replacing steps does not unsend mail already delivered. People still in the series move to whatever step is now next. If no step remains, that enrolment completes.
  • A series is not attached to a form or an order. Enrolment is mail send series.
{
  "steps": [
    { "templateId": "11111111-1111-1111-1111-111111111111", "delayMinutes": 0 },
    { "templateId": "22222222-2222-2222-2222-222222222222", "delay": "2d" }
  ]
}

Send

Four ways. Each needs at least one --to. One request accepts at most 200 recipients. Duplicate addresses in that request are dropped. Suppressed addresses are skipped and do not count as accepted.

frontpage mail send --file email.json --to ada@example.com --to grace@example.com --wait
frontpage mail send email <draft-id> --to ada@example.com --name "Ada Lovelace" --var first_name=Ada
frontpage mail send template <template-id> --to ada@example.com --test
frontpage mail send series <series-id> --to ada@example.com,grace@example.com
Form What is sent
send --file email.json --to … An inline one-off. Nothing is saved as a draft. The file supplies subject, preview text, and blocks. --to supplies recipients
send email <id> --to … That saved draft
send template <id> --to … That template, once, to those people. This does not attach it to a form
send series <id> --to … Enrols those people. The series must be active and must have steps
  • --to repeats, or one flag with commas. --name is applied to every recipient in that command. --var key=value repeats and is applied to every recipient. Per-recipient vars are the API body, not a CLI flag.
  • --test is one recipient. It counts as one Email send. It does not require --wait; a test waits for the result.
  • --wait waits up to 30 seconds for that batch. Without it, the command prints the batch id and returns while rows are still queued.
  • Human output prints the batch id, or Queued.
  • A missing postal line accepts the rows and holds them until settings have a postal line. They are not delivered.
  • There is no --dry-run flag. The API accepts "dryRun": true, which returns accepted, skipped, and held and writes no rows.

Sends, enrolments, suppress

frontpage mail sends list
frontpage mail sends list --batch <batch-id> --limit 50
frontpage mail sends get <send-id>

frontpage mail enrolments list
frontpage mail enrolments list --series <series-id>
frontpage mail enrolments cancel <enrolment-id>

frontpage mail suppress list
frontpage mail suppress add --email bounce@example.com
frontpage mail suppress rm --email bounce@example.com
  • A send row status is pending, claimed, sent, skipped, failed, delivered, bounced, or complained. Human list output is id status email.
  • An enrolment status is active, paused, completed, or cancelled. cancel stops the rest of that series for that person. Mail already sent stays sent.
  • suppress add records reason manual and cancels active enrolments for that address on this site. Later sends to that address are skipped. Reasons unsubscribed, bounced, and complained are written by the platform, not by this command. suppress rm removes the row. It does not restart a cancelled enrolment. Adding the same address again returns the existing row.
  • Suppression is per site. The same address on another site is a different row.

CLI map

Unknown leftover flags fail closed (exit 2). Global flags: --site / -s / --site=, --json, --quiet. See Flags.

Command Role What it does
mail settings / settings getviewerRead postal, reply-to, from-name, timezone, window, hourly cap
mail settings seteditorPatch the flags you pass
mail email list|getviewerUnsent drafts
mail email create|set|rmeditorDraft write and soft-delete
mail email previewviewerCompile a draft. Does not send
mail template list|getviewerTemplates
mail template create|set|rmeditorTemplate write. Delete refuses a series or a form that still points at it
mail template previewviewerCompile a template. Does not send
mail series list|getviewerSeries and their steps
mail series create|set|rm|activate|pauseeditorSeries write. Activate needs at least one step
mail series steps seteditorReplace the step list
mail sendeditorQueue an inline message, a draft, a template, or a series enrolment
mail sends list|getviewerThe send ledger
mail enrolments listviewerWho is in a series
mail enrolments canceleditorStop the rest of that enrolment
mail suppress listviewerAddresses that will not be mailed
mail suppress add|rmeditorManual suppression
modules forms set --confirm-templateeditorAttach one template to one form. Not part of mail
modules forms set --confirmeditorFixed confirmation on or off
modules forms|signups|booking set --notify-emaileditorStaff address. Not a template
modules signups set --confirmeditorFixed signup confirmation. Not a template

There is no mail confirm, no mail receipt, no mail people, and no store receipt flag. Starter writes templates in the editor, not with frontpage mail.

Public v1 API

Base: https://app.frontpage.host/api/v1/sites/:slug/mail. Same key as every other public CLI call. Growth and above. Viewer for reads, editor for writes. Reads are RL-READ. Writes are RL-WRITE-LIGHT. See Rate limits.

Cookie routes under the signed-in editor are not this API. Do not call those from a script. Successful bodies use the usual v1 envelope: the fields below sit next to slug.

Method Path Role Returns
GET/settingsviewersettings
PATCH/settingseditorsettings. Audit site.mailings.settings.set
GET/emailsvieweremails, nextCursor. Query q, limit, cursor, includeSent=1
POST/emailseditor201 email. Audit site.mailings.email.create
GET/emails/:idvieweremail
PATCH/emails/:ideditoremail. Audit site.mailings.email.set
DELETE/emails/:ideditorok. Audit site.mailings.email.delete
POST/emails/:id/previewviewerpreview. A read, despite POST. Body may be {}
POST/emails/:id/sendeditorbatchId, accepted, skipped, held, sends. Audit site.mailings.email.send
GET/templatesviewertemplates, nextCursor
POST/templateseditor201 template. Audit site.mailings.template.create
GET / PATCH / DELETE/templates/:idviewer / editorSame shape as emails. Audits site.mailings.template.set and .delete
POST/templates/:id/previewviewerpreview. A read, despite POST. Body may be {}
POST/templates/:id/sendeditorSame send shape. Audit site.mailings.template.send
GET/seriesviewerseries
POST/serieseditorseries. Body { "name" }. Audit site.mailings.series.create
GET / PATCH / DELETE/series/:idviewer / editorPatch name or status. Audits site.mailings.series.set and .delete
PUT/series/:id/stepseditorseries including steps. Audit site.mailings.series.steps
POST/series/:id/sendeditorSend shape plus enrolmentIds. Audit site.mailings.series.send
POST/sendeditorInline one-off. Body includes subject, preheader, blocks, recipients. Audit site.mailings.send
GET/sendsviewersends, nextCursor. Query batchId, limit, cursor
GET/sends/:idviewersend
GET/enrolmentsviewerenrolments. Query seriesId, q, limit (max 100), offset
POST/enrolments/:id/canceleditorAudit site.mailings.enrolment.cancel
GET/suppressionsviewersuppressions
POST/suppressionseditor201. Body { "email" }. Reason stored as manual. Audit site.mailings.suppress.add
DELETE/suppressions/:emaileditorAudit site.mailings.suppress.remove

A recipient object is { "email", "name?", "vars?" }. Send bodies also accept wait, test, and dryRun. test: true implies wait and allows one recipient.

The form template id is not on this base path. It is POST /api/v1/sites/:slug/modules/forms with { "action": "set", "instanceId", "confirmTemplateId" }. An empty string clears it. Order receipts are not on v1.

Worked example

Growth site, folder already linked with frontpage use acme.

frontpage mail settings set --postal "100 Main St, Springfield" --reply-to owner@example.com --timezone America/Chicago

frontpage mail template create --file welcome.json --name Welcome
# prints the template UUID

frontpage mail series create --name Welcome
frontpage mail series steps set <series-id> --step template=<template-id>,delay=0 --step template=<template-id>,delay=2d
frontpage mail series activate <series-id>
frontpage mail send series <series-id> --to ada@example.com --var first_name=Ada --wait

frontpage mail enrolments list --series <series-id>
frontpage mail sends list
frontpage mail suppress add --email bounce@example.com

frontpage modules forms set --instance start --notify-email sales@example.com --confirm-template <template-id>

The series enrols Ada into both steps. The form attachment is a separate write. Submitting the form does not enrol her, and enrolling her does not confirm the form.

Errors you will actually see

What you did Result
frontpage mail on Free or Starter403 Mailings is included on Growth.
Viewer tries to create or send403
Key is not a member of --site404 Site not found.
Unknown flagExit 2. Nothing is sent
No --site and no frontpage useExit 2
Non-UUID idRejected locally. Message names the noun (Document not found., Series not found., Send not found., Enrolment not found.)
UUID from another site404 for that noun
Create without preview text or blocks400 Preview text is required. or Add at least one content block.
Token not listed in variables400 Unknown template variable: …
Variable name with a space or a capital400 Variable names must be kebab or snake case
51st template, 51st draft, 26th series, 13th step400 with the cap message (50, 50, 25, 12)
Delete a template a series or a form still uses409
Edit a draft that already sent400 A sent email cannot be edited. Duplicate it into a new draft.
Send with no postal lineRows are accepted and held. They are not delivered
Send when the month is full429 This month's email sends are used.
More than 200 recipients, or --test with two400 Too many recipients. Max 200 per send. or A test send can go to one recipient.
Series send while paused, or with no steps400 This series is not active. or This series has no steps.
First step delay other than 0400 The first series step always sends immediately.
Step points at a draft400 Series steps must point at templates.
Image that is not https PNG, JPEG, or WebP400 Images must be PNG, JPEG, or WebP on https.
--confirm-template on booking or signupsUsage error. The flag is forms only
Old CLIUnknown command: mail, or --confirm-template reported as an unknown flag. Upgrade to frontpage-host@latest (0.1.23 or later for the form flag)

Agent rules

  • Read this page and frontpage help mail before inventing verbs or flags.
  • Unknown leftover flags fail closed.
  • Do not send a campaign to confirm a form. Attach the template with modules forms set --confirm-template.
  • Do not attach a series to a form or an order. That is not a flag.
  • Do not call frontpage mail on a Starter site. Templates there are the editor. The command returns 403.
  • Do not invent a store receipt CLI flag. Buyer receipt is Store settings.
  • Do not put confirmation or receipt mail through the campaign footer. Those sends have no unsubscribe link and no postal line.
  • Do not route People or inbox rows into mail send unless the user asked to email those addresses.
  • Campaign writes do not change site HTML and do not require publish.
  • Treat recipient lists as private. Do not paste them into public logs.

Common mistakes

Mistake What actually happens
Document only the form confirmation and call that Email That is one door. Campaigns, series, the ledger, and suppress are frontpage mail
Use mail send template as the form confirmation That emails the people you named, once. The form is unchanged. The next visitor is not mailed
Expect Starter frontpage mail template create to work 403. Starter writes templates in the editor. The CLI mail API is Growth+
Send a campaign before settings set --postal The batch is held. Nobody receives it
Put {{organization_name}} on a form that has no input by that name The confirmation still sends. That token is blank
Assume a series starts when the form is submitted It does not. Enrol with mail send series
Delete a template that a form still uses 409. Clear --confirm-template= first
Look for a sent draft in mail email list That list is unsent only. Use mail sends list
Treat suppress as a legal delete of a person It only stops campaign mail to that address on this site. People and inbox are unchanged

Also see