Platform
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 sendto “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.
-
--siteis a subdomain slug (acme). Not a custom domain, notacme.frontpage.host, not a site UUID. Resolution order is the same as People: this command’s--site, then.frontpage/state.jsonfromfrontpage 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 listis unsent drafts only. A sent one-off leaves that list. Read it withmail sends list.- List flags:
--q(name or subject),--limit(1–200, default 50),--cursor(opaque, from the previous page’snextCursor). Do not invent a cursor. - Create requires
--file, a JSON object.--name,--subject, and--preheaderoverride the file. The file must still includeblocks, and on create it must include name, subject, and preview text unless those flags supply them. setpatches only the keys you pass.--fileis optional on set.- The id is a UUID from
listorcreate. Positional, or--id. A non-UUID is rejected locally. A UUID from another site is 404Document 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. rmis a soft delete. A template still used by a series is 409This 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.previewcompiles 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 getviewer Read postal, reply-to, from-name, timezone, window, hourly cap mail settings seteditor Patch the flags you pass mail email list|getviewer Unsent drafts mail email create|set|rmeditor Draft write and soft-delete mail email previewviewer Compile a draft. Does not send mail template list|getviewer Templates mail template create|set|rmeditor Template write. Delete refuses a series or a form that still points at it mail template previewviewer Compile a template. Does not send mail series list|getviewer Series and their steps mail series create|set|rm|activate|pauseeditor Series write. Activate needs at least one step mail series steps seteditor Replace the step list mail sendeditor Queue an inline message, a draft, a template, or a series enrolment mail sends list|getviewer The send ledger mail enrolments listviewer Who is in a series mail enrolments canceleditor Stop the rest of that enrolment mail suppress listviewer Addresses that will not be mailed mail suppress add|rmeditor Manual suppression modules forms set --confirm-templateeditor Attach one template to one form. Not part of mail modules forms set --confirmeditor Fixed confirmation on or off modules forms|signups|booking set --notify-emaileditor Staff address. Not a template modules signups set --confirmeditor Fixed 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 /settingsviewer settings PATCH /settingseditor settings. Audit site.mailings.settings.set GET /emailsviewer emails, nextCursor. Query q, limit, cursor, includeSent=1 POST /emailseditor 201 email. Audit site.mailings.email.create GET /emails/:idviewer email PATCH /emails/:ideditor email. Audit site.mailings.email.set DELETE /emails/:ideditor ok. Audit site.mailings.email.delete POST /emails/:id/previewviewer preview. A read, despite POST. Body may be {} POST /emails/:id/sendeditor batchId, accepted, skipped, held, sends. Audit site.mailings.email.send GET /templatesviewer templates, nextCursor POST /templateseditor 201 template. Audit site.mailings.template.create GET / PATCH / DELETE /templates/:idviewer / editor Same shape as emails. Audits site.mailings.template.set and .delete POST /templates/:id/previewviewer preview. A read, despite POST. Body may be {} POST /templates/:id/sendeditor Same send shape. Audit site.mailings.template.send GET /seriesviewer series POST /serieseditor series. Body { "name" }. Audit site.mailings.series.create GET / PATCH / DELETE /series/:idviewer / editor Patch name or status. Audits site.mailings.series.set and .delete PUT /series/:id/stepseditor series including steps. Audit site.mailings.series.steps POST /series/:id/sendeditor Send shape plus enrolmentIds. Audit site.mailings.series.send POST /sendeditor Inline one-off. Body includes subject, preheader, blocks, recipients. Audit site.mailings.send GET /sendsviewer sends, nextCursor. Query batchId, limit, cursor GET /sends/:idviewer send GET /enrolmentsviewer enrolments. Query seriesId, q, limit (max 100), offset POST /enrolments/:id/canceleditor Audit site.mailings.enrolment.cancel GET /suppressionsviewer suppressions POST /suppressionseditor 201. Body { "email" }. Reason stored as manual. Audit site.mailings.suppress.add DELETE /suppressions/:emaileditor Audit 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 send 403 Key is not a member of --site 404 Site not found. Unknown flag Exit 2. Nothing is sent No --site and no frontpage use Exit 2 Non-UUID id Rejected locally. Message names the noun (Document not found., Series not found., Send not found., Enrolment not found.) UUID from another site 404 for that noun Create without preview text or blocks 400 Preview text is required. or Add at least one content block. Token not listed in variables 400 Unknown template variable: … Variable name with a space or a capital 400 Variable names must be kebab or snake case 51st template, 51st draft, 26th series, 13th step 400 with the cap message (50, 50, 25, 12) Delete a template a series or a form still uses 409 Edit a draft that already sent 400 A sent email cannot be edited. Duplicate it into a new draft. Send with no postal line Rows are accepted and held. They are not delivered Send when the month is full 429 This month's email sends are used. More than 200 recipients, or --test with two 400 Too many recipients. Max 200 per send. or A test send can go to one recipient. Series send while paused, or with no steps 400 This series is not active. or This series has no steps. First step delay other than 0 400 The first series step always sends immediately. Step points at a draft 400 Series steps must point at templates. Image that is not https PNG, JPEG, or WebP 400 Images must be PNG, JPEG, or WebP on https. --confirm-template on booking or signupsUsage error. The flag is forms only Old CLI Unknown 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
- Store for the catalog, and the note that Buyer receipt lives in Store settings
- Modules for forms, signups, and booking
- People CRM
- Content & settings for inbox
- Rate limits
- Command reference
- Isolation & keys