frontpage-host 0.2.9 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -2
- package/CUSTOMER-INBOX.md +95 -10
- package/MAIL.md +85 -0
- package/PAYMENTS.md +41 -0
- package/PEOPLE.md +3 -3
- package/README.md +5 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/mail.js +100 -15
- package/dist/commands/payments.js +6 -1
- package/dist/completions/generate.js +3 -0
- package/dist/registry.js +22 -17
- package/llms.txt +5 -1
- package/package.json +3 -2
package/AGENTS.md
CHANGED
|
@@ -56,7 +56,7 @@ frontpage whoami --json
|
|
|
56
56
|
frontpage doctor --json
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
- Keys are minted only in the Frontpage UI (
|
|
59
|
+
- Keys are minted only in the Frontpage UI (Workspace → account menu → Developers). Prefix `fp_live_`.
|
|
60
60
|
- Keys act as the minting user on **every** site they can access. Treat like passwords.
|
|
61
61
|
- `PUBLIC_CLI_ENABLED` is server-side; the CLI cannot turn it on. Bare “not found” on `/api/v1/me` usually means the flag is off (exit 5).
|
|
62
62
|
|
|
@@ -266,8 +266,14 @@ Scale and internal sites, after owner activation. No pilot allowlist. Use `front
|
|
|
266
266
|
Payments, invoices and recurring billing: see packaged [PAYMENTS.md](./PAYMENTS.md), `frontpage help payments`, and https://frontpage.host/developers/payments. Version 0.2.0 removes writable currency, Store checkout mode and Store-owned money commands; Store stays cart-only. Preview/review before sends; keep stable operation keys on retries. Autopay requires the customer’s own verified authorization.
|
|
267
267
|
|
|
268
268
|
|
|
269
|
-
Offerings own every standalone Pay now payment. Use `frontpage payments buttons list|get|create|update|archive|restore`; create the saved offering before adding its returned `data-fp-pay` markup. Supports `--receipt-template-id UUID`, `--default-receipt`, `--active true|false`, combined price/details updates and JSON payloads. Archive deletes from active checkout but preserves history. The website agent uses the same service automatically for ordinary payment-button requests. Store stays cart-only; invoices stay saved customer-addressed records. Full instructions: PAYMENTS.md and https://frontpage.host/developers/payments.
|
|
269
|
+
Offerings own every standalone Pay now payment. Use `frontpage payments buttons list|get|create|update|archive|restore|receipt-preview`; create the saved offering before adding its returned `data-fp-pay` markup. Supports `--receipt-template-id UUID`, `--default-receipt`, `--active true|false`, combined price/details updates and JSON payloads. Archive deletes from active checkout but preserves history. The website agent uses the same service automatically for ordinary payment-button requests. Store stays cart-only; invoices stay saved customer-addressed records. Create/update/restore return setup.status and the existing Stripe handoff, without enabling Store or overriding Payments off. Preview a bound receipt with buttons receipt-preview; it never sends or charges. Digital delivery uses approved file links in the saved purchase receipt; keep the offering inactive until resources are ready. Full instructions: PAYMENTS.md and https://frontpage.host/developers/payments.
|
|
270
270
|
|
|
271
271
|
Currency is owned by Payments and read by Store, Offerings and invoices. Offering create/get/update responses include exact receiptVariables. For custom confirmations, declare those names with existing mail template commands, attach receiptTemplateId, and verify both records. Only include expressly selected answers; keep private answers excluded. Forms still use input-name tokens and confirmTemplateId.
|
|
272
272
|
|
|
273
273
|
Customer Inbox setup: enable website chat through Inbox settings; Frontpage public sites load it without a file edit or publish. Other websites use the explicit widget snippet. `get website` reports managed addresses/public availability. Website knowledge is default-on, read-only public retrieval; `websiteKnowledge.enabled:false` disables it. Source toggles, attention filtering and receipt cursors use the documented versioned resources.
|
|
274
|
+
|
|
275
|
+
Customer Inbox team guidance: administrators may set `guidanceEmail` through the versioned settings write. The owner toolbar has the same setting. Teammates reply as the configured mailbox; no teammate account or separate mailbox activation is needed. Do not set it to the public Inbox address. Conversation management uses close/reopen; do not use read/trash/restore.
|
|
276
|
+
|
|
277
|
+
## Emails workspace workflows
|
|
278
|
+
|
|
279
|
+
See [MAIL.md](./MAIL.md) for the complete save contract, reusable people picker directory, saved campaign recipients, Off-series enrollment and resume timing, all sender settings/null resets, suppression rules, Sent queries and details, and retry-safe examples. `frontpage mail save --file save.json` saves template and pending series together; it never sends. `frontpage mail people` reads the same site-scoped directory as the workspace. Use `frontpage help mail --json` for current commands and flags. No new CLI binary, API namespace or migration command is required. Hosted guides and article help must be updated through the Frontpage CLI.
|
package/CUSTOMER-INBOX.md
CHANGED
|
@@ -24,9 +24,9 @@ Reads use `get`. Writes use `put` or `post`, a UTF-8 JSON object file, and an 8
|
|
|
24
24
|
| `settings` | GET | Returns `version`, `value`, fixed model and multiplier |
|
|
25
25
|
| `settings` | PUT | `{"expectedVersion":0,"value":{"instructions":"Ask me if unsure."}}` |
|
|
26
26
|
|
|
27
|
-
Setup
|
|
27
|
+
Setup defaults private guidance to the verified administrator who requested it. An administrator can set `value.guidanceEmail` to a team mailbox, such as `support@example.com`; no separate activation link or teammate Frontpage account is required. Teammates reply as that exact mailbox. Private routes and receiving authentication still protect replies. Changing the destination revokes old private routes and cancels queued mail to them. `guidanceToMe:true` restores your verified account email. The destination cannot be the Inbox’s own public support alias. The agent manages new conversations by default after setup; the platform's Inbox switches control transport and automation independently of site activation. Set `value.enabled:false` for manual handling. Settings changes cancel stale automatic jobs and queued automatic sends.
|
|
28
28
|
|
|
29
|
-
Settings allow `enabled`, `instructions`, `tools`, `maxSteps` (1–12), `maxOutputTokens` (256–2400), and `maxInputBytes` (4000–64000). Supported tools: `knowledge_read`, `knowledge_update`, `customer_read`, `verify_customer`, `customer_update`, `notes_read`, `note_add`, `ask_owner`, `reply_customer`, `wait`, `finish`. Keep `wait` and `finish` enabled. Mailbox verification remains available whenever customer-data tools are enabled; it cannot be bypassed by settings. Defaults: 8 steps, 1200 output tokens per call, 32000 bytes of public text. Model
|
|
29
|
+
Settings allow `enabled`, `instructions`, `tools`, `maxSteps` (1–12), `maxOutputTokens` (256–2400), and `maxInputBytes` (4000–64000). Supported tools: `knowledge_read`, `knowledge_update`, `customer_read`, `verify_customer`, `customer_update`, `notes_read`, `note_add`, `ask_owner`, `reply_customer`, `wait`, `finish`. Keep `wait` and `finish` enabled. Mailbox verification remains available whenever customer-data tools are enabled; it cannot be bypassed by settings. Defaults: 8 steps, 1200 output tokens per call, 32000 bytes of public text. Model and billing multiplier are fixed. Only an administrator can choose the private guidance mailbox; the support agent cannot choose recipients. The worker also enforces platform concurrency, deadlines and input limits.
|
|
30
30
|
|
|
31
31
|
`maxContextTokens` (12000–96000, default 32000) sets a conservative per-call input plus output allowance. UTF-8 text bytes, tool definitions, instructions, transcript and image capacity count toward admission; exact provider tokens still determine billing. Optional older history and preloaded notes drop first. Essential evidence is never clipped: if it cannot fit, the job pauses for review. History stays stored without a lifetime message-count stop. The agent receives up to 40 recent messages plus required current input and owner guidance, and can retrieve older messages in bounded pages under its existing `knowledge_read` grant.
|
|
32
32
|
|
|
@@ -83,7 +83,7 @@ Lock a knowledge note with `locked:true`. Neither the support agent nor the owne
|
|
|
83
83
|
| `files?messageId=<id>` | GET | Bound message files; editor/admin only |
|
|
84
84
|
| `files/<id>` | GET | Short-lived download grant; editor/admin only |
|
|
85
85
|
|
|
86
|
-
One verified customer has one continuous thread per site, across direct email, replies and verified website chat. Email subject changes do not create new cases after identity is proven. Fresh mail to the public alias remains provisional until confirmation, even when From and DMARC match an existing customer. Recipient-bound secret reply routes continue the established thread. Typed email, display names and subjects never authorize merging or history access. Closing reopens that same thread on the next message
|
|
86
|
+
One verified customer has one continuous thread per site, across direct email, replies and verified website chat. Email subject changes do not create new cases after identity is proven. Fresh mail to the public alias remains provisional until confirmation, even when From and DMARC match an existing customer. Recipient-bound secret reply routes continue the established thread. Typed email, display names and subjects never authorize merging or history access. Closing reopens that same thread on the next message. Legacy trashed threads can be reopened from Closed. Inbox joins retained history without moving messages, files or paid receipts. Old conversation links and recipient-bound reply addresses resolve to the current thread. Public chat includes customer-visible email replies and excludes owner guidance and notes.
|
|
87
87
|
|
|
88
88
|
Use `timeline=true` for combined history. Its opaque `nextCursor` is a message ID, not a sequence or offset; pass it unchanged as `after` or `before`. `latest=true` returns the latest bounded page, and `before=<cursor>` loads older messages. A cursor must belong to that conversation and audience. Sequence-based reads without `timeline=true` remain available for the root segment; use the combined timeline for every linked segment. The CLI export follows the complete combined timeline. The widget opens the latest 50 public messages and offers **Load earlier messages**, so a returning customer does not wait for years of history to download. The support agent's `history_read` tool likewise follows its returned `nextBefore` message ID.
|
|
89
89
|
|
|
@@ -272,7 +272,7 @@ Update settings using the current `expectedVersion`:
|
|
|
272
272
|
}
|
|
273
273
|
```
|
|
274
274
|
|
|
275
|
-
Use exact HTTPS origins without paths or credentials. This controls cross-origin browser access, not owner permissions. Required intake fields collect details; they do not prove email ownership. The customer must confirm the emailed link before email continuation or authenticated People access. A
|
|
275
|
+
Use exact HTTPS origins without paths or credentials. This controls cross-origin browser access, not owner permissions. Required intake fields collect details; they do not prove email ownership. The customer must confirm the emailed link before email continuation or authenticated People access. A compact verification prompt appears only while confirmation needs action. Public business questions can continue while private customer/account data stay locked. After confirmation, the widget removes the prompt and enables email follow-up automatically; there is no separate continuation switch. The branded HTML email uses the shared transactional renderer and its Confirm email button opens app.frontpage.host/customer-inbox/confirm. GET never consumes a proof; an explicit same-origin POST reaches Inbox through a signed server bridge. Confirmation rotates the visitor credential. Recovery uses a new email proof and returns no history based on a typed address alone.
|
|
276
276
|
|
|
277
277
|
The agent reads help rules in its support prompt and uses `ask_owner`. Work caps remain server-enforced. Owner copies are private emails of future agent replies. Replying gives private guidance unless the owner explicitly requests takeover. Neither copies nor guidance put owner and customer on a shared CC thread.
|
|
278
278
|
|
|
@@ -316,11 +316,10 @@ Use `frontpage-host` 0.2.2 or later to export every linked history segment throu
|
|
|
316
316
|
| `conversations?state=active` | GET | Open threads; also `closed`, `trash` or `all` |
|
|
317
317
|
| `conversations/<id>/messages?latest=true&limit=50&audience=all` | GET | Latest page in message order |
|
|
318
318
|
| `conversations/<id>/messages?before=<sequence>&limit=50&audience=all` | GET | Earlier page; do not combine with `after` or `latest` |
|
|
319
|
-
| `conversations/<id>/state` | POST | `{"action":"close","expectedVersion":0}`; actions: `close`, `reopen
|
|
320
|
-
| `conversations/<id>/state` | POST | Mark read with current state version and `"sequence":42` |
|
|
319
|
+
| `conversations/<id>/state` | POST | `{"action":"close","expectedVersion":0}`; actions: `close`, `reopen` |
|
|
321
320
|
| `conversations/<id>/revoke-sessions` | POST | `{}`; revoke existing visitor credentials |
|
|
322
321
|
|
|
323
|
-
|
|
322
|
+
Close retains history and file references. Reopen does not erase prior handling decisions. State writes reject stale versions. Closing cancels queued sends and refuses unresolved provider outcomes. New customer input reopens a closed thread. Legacy trashed threads appear under Closed and can be reopened there. Visitor revocation also disables recovery of that session.
|
|
324
323
|
|
|
325
324
|
Export the full authorized history and file manifest, one JSON record per line:
|
|
326
325
|
|
|
@@ -338,7 +337,7 @@ Inbox sends count in the site's existing email allowance. Provider-accepted send
|
|
|
338
337
|
|
|
339
338
|
Email capture keeps the original MIME message, attachments and provider receipt. Email and chat image uploads keep the original file and a smaller agent preview. Repeated upload/message receipts retain the same file/message IDs. Validated raster customer images up to 12 megapixels use the existing public bucket. Larger or undecodable files remain downloadable and require human review. Other files and private mail originals use authenticated downloads over encrypted bytes in that same bucket. No Blob firewall changes or separate bucket are part of this release.
|
|
340
339
|
|
|
341
|
-
The existing backup process enumerates the whole bucket. The existing reaper scans Inbox files in bounded pages and asks Inbox for exact retained references; it does not read message bodies. Unknown reference results protect Inbox files while legacy cleanup continues. Live history, Trash, capture holds and original/preview links retain their files. The existing archive/restore/purge coordinator claims a file in Inbox before changing bytes. A claim blocks new links and remains held after an unknown outcome. Accepted email custody, message links,
|
|
340
|
+
The existing backup process enumerates the whole bucket. The existing reaper scans Inbox files in bounded pages and asks Inbox for exact retained references; it does not read message bodies. Unknown reference results protect Inbox files while legacy cleanup continues. Live and closed history, legacy Trash, capture holds and original/preview links retain their files. The existing archive/restore/purge coordinator claims a file in Inbox before changing bytes. A claim blocks new links and remains held after an unknown outcome. Accepted email custody, message links, closed/legacy trashed conversations and capture holds protect files; abandoned chat uploads can follow the existing grace/archive rules. Unknown file records stay protected. Site finalization freezes Inbox writes before moving its bytes through the same archive process. Broader shared-reaper performance work remains a later phase.
|
|
342
341
|
|
|
343
342
|
|
|
344
343
|
On app logout or account change, call `window.dispatchEvent(new Event("frontpage-inbox:reset"))`. This clears the displayed history, stored chat credential and pending input. The next request fetches a fresh assertion from `data-identity-url`; authenticated app sessions require the same current app identity on every request. Do not use a long-lived static assertion for an app with account switching. Verification returns to the same origin and the stored page path. Query strings and arbitrary redirect URLs are not accepted.
|
|
@@ -360,7 +359,7 @@ The support agent has its own system prompt plus the versioned private business
|
|
|
360
359
|
| `customer_update` | `name`/`phone`, `evidenceMessageId` | Enrich from an explicit customer statement; cannot change identity or permission. |
|
|
361
360
|
| `notes_read` | Optional `before` | Read only this verified customer's factual People notes, 25 per page. |
|
|
362
361
|
| `note_add` | `text` | Save a private factual action note in People, with an idempotent receipt. Do not store hidden reasoning. |
|
|
363
|
-
| `ask_owner` | `question` | Save the private question in the thread, email the
|
|
362
|
+
| `ask_owner` | `question` | Save the private question in the thread, email the administrator-authorized guidance mailbox and pause. Guidance does not take over. |
|
|
364
363
|
| `reply_customer` | `text` | Save a public answer and queue email to the bound verified customer when available. Never choose recipients. |
|
|
365
364
|
| `wait` | `reason` | Pause safely without sending. |
|
|
366
365
|
| `finish` | None | Complete this bounded job. |
|
|
@@ -375,6 +374,92 @@ The installed widget loads two public static scripts: `/customer-inbox/widget.js
|
|
|
375
374
|
|
|
376
375
|
The workspace uses a short authenticated stream through its cookie bridge; the installed widget uses authenticated fetch with its existing visitor/app-identity headers. Streams carry empty invalidation hints, never message text, private counters, recipients or file metadata. Clients re-read their authorized history/status. One restricted Postgres listener per Inbox process wakes the existing outbox publisher and fans out hints. Notifications are best-effort; durable rows, leases, recovery scans and receipt reconciliation remain authoritative. Disconnected clients refresh and use bounded fallback polling. No token enters a stream URL, and each short stream reauthorizes on reconnect.
|
|
377
376
|
|
|
378
|
-
A new unverified chat with an email requests a generic verification email automatically. It does not send private history or assume that typing an address proves identity. The owner can still answer in chat while the visitor verifies. Confirmation rotates the session and binds future public replies to that email; if the latest customer input already has an unsent public answer, Inbox queues that one answer once. It does not mail a backlog or an answer that predates a newer customer question.
|
|
377
|
+
A new unverified chat with an email requests a generic verification email automatically. It does not send private history or assume that typing an address proves identity. The owner can still answer in chat while the visitor verifies. Confirmation rotates the session and binds future public replies to that email; if the latest customer input already has an unsent public answer, Inbox queues that one answer once. It does not mail a backlog or an answer that predates a newer customer question. Browser replies enter the existing mail outbox with a 30-second grace period. The open, visible widget acknowledges public replies in its transcript viewport using its current verified session. This cancels only that customer’s queued, unattempted follow-up email; the reply stays in history and still counts as answered. Hidden/minimized chat, lost connections and failed acknowledgments leave email queued. The existing publisher sends it after the grace period, normally on its next sweep (up to about 15 seconds later, plus provider time). Direct inbound-email replies keep immediate delivery. No extra continuation switch or presence service is required. A late read cannot cancel an already attempted or uncertain send; retries keep the same provider identity and accounting receipt.
|
|
379
378
|
|
|
380
379
|
The workspace shows customer name/email first, latest public message in each row, and a copyable reference in conversation details. Conversation list and history scroll independently inside a bounded viewport. Settings use shared workspace controls and a presentation-only preview built from the same renderer as the installed widget. Preview changes send no email or model request. Internal note history/locks/revert stay on the note's own page.
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
## Shared team guidance
|
|
383
|
+
|
|
384
|
+
Read settings and use the returned version. This example assumes `version: 3`:
|
|
385
|
+
|
|
386
|
+
```json
|
|
387
|
+
{"expectedVersion":3,"value":{"guidanceEmail":"support@example.com"}}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
frontpage customer-inbox put settings --file ./team-guidance.json --request-key team_guidance_01
|
|
392
|
+
frontpage customer-inbox get settings
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
The settings response has `version: 4`, `value.guidanceOwnerEmail: "support@example.com"`, and the authorizing administrator in `value.guidanceOwnerId`. Other settings remain unchanged. Repeating the exact write with the same key does not apply it twice. A stale version with a new key returns `settings_changed`. Editors cannot change the mailbox. This is the private team destination, distinct from the public address customers email.
|
|
396
|
+
|
|
397
|
+
When the agent asks for help, anyone with shared-mailbox access can reply as `support@example.com`. Guidance remains private and keeps the agent handling the customer. An explicit takeover request changes handling; the customer relay stays within Inbox. Replies from an unrelated address or a revoked private route cannot act as team guidance. The audit records the actual sending mailbox; a shared sender does not identify the individual teammate.
|
|
398
|
+
|
|
399
|
+
## Simple conversation management
|
|
400
|
+
|
|
401
|
+
Use Open and Closed. Close preserves history; a later customer message reopens the same conversation. Legacy trashed conversations appear under Closed and can be reopened. Mark read and Trash are no longer supported management actions.
|
|
402
|
+
|
|
403
|
+
```json
|
|
404
|
+
{"action":"close","expectedVersion":2}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
frontpage customer-inbox post conversations/CONVERSATION_ID/state --file ./close.json --request-key close_conversation_01
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Read the conversation’s current `stateVersion` first. The response increments it and sets `closedAt`; reopening clears `closedAt` and any legacy trash marker. Closing does not transfer handling or delete messages. Take over before sending a human customer reply; private guidance does not take over.
|
|
412
|
+
|
|
413
|
+
## Setup and knowledge behavior
|
|
414
|
+
|
|
415
|
+
Claim the public support address before relying on account confirmation, owner guidance or email replies. General browser chat can work without it. Missing setup returns `address_required`; the widget explains that account confirmation is unavailable and keeps customer data locked. Finish setup, then retry the saved request from Inbox.
|
|
416
|
+
|
|
417
|
+
The support agent receives a dedicated system prompt, private business instructions, bounded conversation context and permitted tool schemas. It reads saved business notes with `knowledge_read`, live public pages with `website_search` / `website_read`, and saved approved webpage snapshots with `knowledge_read {sourceId}`. Search excerpts require a complete read before relying on them. Website search does not create internal notes. Only separate server-authorized learning from team guidance or approved source revisions may update unlocked shared knowledge. Customer assertions never authorize those writes.
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
## Compact chat and workspace controls
|
|
421
|
+
|
|
422
|
+
- The chat header says “We’ll reply here, or email you if you’re away.” Confirmed
|
|
423
|
+
email needs no permanent success banner. The file and send buttons use icons
|
|
424
|
+
with accessible labels; Enter sends and Shift+Enter adds a line. The message
|
|
425
|
+
input grows within a fixed bound. Selected files can be removed before sending.
|
|
426
|
+
- Before a support address exists, Chat & settings shows **Set up your support
|
|
427
|
+
email** and **Create support address**. After assignment, it shows the active
|
|
428
|
+
address, its site, **Copy address**, and **Change address**. The replacement
|
|
429
|
+
form opens only after Change. Saving a replacement stops new mail to the old
|
|
430
|
+
alias; it does not delete conversation history. Reserved aliases stay protected.
|
|
431
|
+
- While the agent handles a conversation, the composer offers **Guide your agent**
|
|
432
|
+
and **Private note**. After takeover it offers **Reply to customer** and
|
|
433
|
+
**Private note**. Guidance, public reply and note drafts have separate keys;
|
|
434
|
+
changing control never makes a private draft public. A hand marks **Take over**;
|
|
435
|
+
the return arrow marks **Return to agent**. Only the current owner can send a
|
|
436
|
+
manual public reply. Existing API/CLI guidance and control operations remain available.
|
|
437
|
+
|
|
438
|
+
### Widget read acknowledgment
|
|
439
|
+
|
|
440
|
+
The installed widget uses this automatically; owners do not need to call it.
|
|
441
|
+
The endpoint accepts only the current origin-bound verified browser credential
|
|
442
|
+
(and a current app identity assertion when the session belongs to an app).
|
|
443
|
+
|
|
444
|
+
```http
|
|
445
|
+
POST /chat/sites/<site-id>/seen
|
|
446
|
+
Origin: https://your-approved-website.example
|
|
447
|
+
Authorization: Bearer <verified-visitor-session>
|
|
448
|
+
Idempotency-Key: seen_<unique-request-key>
|
|
449
|
+
Content-Type: application/json
|
|
450
|
+
|
|
451
|
+
{"messageIds":["11111111-1111-4111-8111-111111111111"]}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
```json
|
|
455
|
+
{"acknowledged":true}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Use one to fifty unique public message IDs from this customer’s conversation.
|
|
459
|
+
The session, site, origin, current email, app binding and public audience must
|
|
460
|
+
match. A typed but unverified email returns `verification_required`; a foreign
|
|
461
|
+
conversation or private message returns `message_unavailable`. Mixed valid and
|
|
462
|
+
invalid IDs change nothing. Retries are harmless. This endpoint neither exposes
|
|
463
|
+
message content nor changes ownership, knowledge, model work or charges. Canceled
|
|
464
|
+
follow-ups show **Seen in chat · email not needed** in the workspace. If the request
|
|
465
|
+
fails, normal email delivery remains the fallback.
|
package/MAIL.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Emails: authoring, recipients and delivery
|
|
2
|
+
|
|
3
|
+
Use `frontpage help mail --json` for command discovery. The existing CLI binary and API namespace stay in place. Site roles and the CLI plan gate still apply. A viewer can read; an editor or owner can change email settings and content. These commands never grant platform admin rights or change a platform kill switch.
|
|
4
|
+
|
|
5
|
+
## Save an email and its series together
|
|
6
|
+
|
|
7
|
+
`frontpage mail save --file save.json --site YOUR_SITE --json` calls `POST /api/v1/sites/YOUR_SITE/mail/save`.
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"requestKey": "a-stable-key-for-this-save",
|
|
12
|
+
"document": {
|
|
13
|
+
"kind": "template",
|
|
14
|
+
"name": "Welcome",
|
|
15
|
+
"subject": "Welcome, {{first_name}}",
|
|
16
|
+
"preheader": "Here is your next step.",
|
|
17
|
+
"blocks": [{ "type": "paragraph", "text": "Thanks for joining us." }],
|
|
18
|
+
"variables": []
|
|
19
|
+
},
|
|
20
|
+
"series": { "name": "Welcome series", "steps": [] },
|
|
21
|
+
"append": true,
|
|
22
|
+
"delayMinutes": 1440
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This creates a shared template and an Off series with one step. It sends nothing and enrolls no one. The first step always has zero delay. Later step delays accept integer minutes from 0 through 525600; a series has at most 12 steps.
|
|
27
|
+
|
|
28
|
+
For an existing record, supply `id` and `expectedUpdatedAt` from `mail template get` / `mail email get` / `mail series get`. To edit an existing shared template from a series, send both the complete `document` and complete pending `series` in one save, omitting `append`. **Save & return saves both**, including pending names, steps, removals and waits. To add a new step, send a new template plus the source series and `append:true`.
|
|
29
|
+
|
|
30
|
+
The response reports `complete:true` and the saved records. Keep the exact payload and requestKey after a lost response. Repeating it returns the saved IDs without adding another template or step. Do not reuse a key for different content. A 409 means the saved version changed: retain the draft, read the current records, review the changes, and use a new key for the revised save. A failed transaction saves neither half. Subject, preview text, name and valid content are required. Saving never activates a series or sends email. Queued emails keep their saved content.
|
|
31
|
+
|
|
32
|
+
The workspace's Back button returns to the source series and keeps unfinished work. A test sends only to the signed-in account in the workspace, and does not complete a step task. CLI test recipients remain explicit and subject to the existing test restrictions.
|
|
33
|
+
|
|
34
|
+
## Campaign recipients
|
|
35
|
+
|
|
36
|
+
Include `audience` with a one_off document in the same `mail save` request. Supported keys are `personIds`, `groupIds`, `stageIds`, `filters`, and `addresses`. Use `audience:null` to clear the saved selection. Addresses normalize to lower case. Saving addresses never creates CRM people or records consent. Recipient-only edits count as changes and save with the campaign.
|
|
37
|
+
|
|
38
|
+
For an audience-only change, the existing `mail campaign audience --id DOCUMENT_ID --file audience.json` remains supported. Its JSON is `{"audience":{"personIds":["PERSON_ID"]}}`, or `{"audience":null}` to clear. Saved selection is intent; final send review resolves groups and filters again, checks permission and delivery restrictions, and freezes the actual recipients and content. Started campaigns cannot be edited or sent twice with a new key. Read `PEOPLE.md` for preview/send review and stable send keys.
|
|
39
|
+
|
|
40
|
+
## People directory and enrollment
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
frontpage mail people --sort newest --page 1 --site YOUR_SITE --json
|
|
44
|
+
frontpage mail people --q alex --source signup --availability eligible --sort name --page 1 --site YOUR_SITE --json
|
|
45
|
+
frontpage mail people --selected PERSON_ID,OTHER_PERSON_ID --selected-only --page 1 --site YOUR_SITE --json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Sort values: `newest`, `oldest`, `name`. Added order uses the CRM record's added date, not recent activity or the first historical event. Source values: form, booking, signup, payment, order. Availability: all, eligible, restricted. The response includes 20 rows per numbered page, total, added date, source and availability. Every query stays inside the authorized site. Search and availability apply before paging.
|
|
49
|
+
|
|
50
|
+
Use existing `frontpage people workspace series.preview --file preview.json` then `series.enrol --file batch.json` for reviewed CRM enrollment. Preview input: `{ "seriesId":"SERIES_ID", "personIds":["PERSON_ID"] }`. Each returned batch contains personIds and reviewToken; commit with those fields, seriesId and a stable requestKey. Reuse each batch's key after a network error. Receipts preserve partial outcomes. Enrollment saves immediately and never requires Save series afterward. Existing enrollment is deduplicated across all states; completed/cancelled people do not restart.
|
|
51
|
+
|
|
52
|
+
- **Off:** eligible people wait at step 1. Enrollment creates no queued message, first-template snapshot, due date or allowance reservation. No sender readiness or send meter is needed to record waiting people.
|
|
53
|
+
- **On:** the first email becomes ready, subject to normal permission, sender, kill switch, send window and allowance checks.
|
|
54
|
+
- **Turning Off:** pending series messages stop before provider handoff. A message already accepted by the provider cannot be recalled.
|
|
55
|
+
- **Turning On again:** new waiting people begin at step 1. Started people resume their next unsent step with its saved due date; an overdue step can send on the next pass. Each later wait starts after successful provider acceptance. Off time never skips a new person's first email or causes all later steps to send at once.
|
|
56
|
+
|
|
57
|
+
`mail series activate SERIES_ID` sets status active (On); `mail series pause SERIES_ID` sets paused (Off). Stored draft and paused both display Off. `mail series get/list` includes enrolledCount (waiting + current in progress/paused; excludes completed/cancelled) and waitingCount. `mail enrolments list` shows progress; cancel remains available while Off. The direct `mail send series` command also enrolls while Off and returns accepted enrollment IDs with no sends. Empty sends is not a delivery claim.
|
|
58
|
+
|
|
59
|
+
Selections with IDs use a read-only `POST /api/v1/sites/YOUR_SITE/mail/people` body to avoid URL limits. Ordinary directory queries use GET. Both call the same scoped query; neither writes CRM records.
|
|
60
|
+
|
|
61
|
+
## Settings and suppressions
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
frontpage mail settings set --header-align center --window-start 540 --window-end 1020 --hourly-cap 100 --site YOUR_SITE
|
|
65
|
+
frontpage mail settings set --file settings.json --site YOUR_SITE --json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The settings JSON accepts exactly replyTo, postalLine, fromName, timezone, sendWindowStart, sendWindowEnd, hourlyCap and headerAlign. Omitted fields stay unchanged. Null clears replyTo, postalLine, fromName or hourlyCap. Window values are integer minutes after midnight (0–1439), hourlyCap is null or 1–1000, timezone is an IANA time zone, and headerAlign is left/center/right. A business postal address is still required for actual marketing sends. `--hourly-cap null` clears that cap. A settings save, content edit, read, cancellation or switch Off does not depend on the send meter.
|
|
69
|
+
|
|
70
|
+
`mail suppress list|add|rm` uses the existing site suppression service. Adding stops that site's marketing campaigns/series and cancels active enrollments. Transactional receipts and support replies follow separate rules. Removing a suppression does not restore consent, resubscribe someone or restart cancelled series.
|
|
71
|
+
|
|
72
|
+
## Sent history
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
frontpage mail sends list --q alex --status delivered --type campaign --page 1 --limit 20 --site YOUR_SITE --json
|
|
76
|
+
frontpage mail sends get SEND_ID --site YOUR_SITE --json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Types: campaign, series, confirmation, template, test, inline. Search matches recipient/subject across all permitted rows. Status uses the stored ledger value (pending/claimed are queued). Count and numbered pages use the same site/filter scope, ordered by creation time then ID. Existing cursor pagination remains available; use either cursor or page. `createdAt` is queue creation, not delivery time. Details expose saved content, current status/error, attempts and source availability; preview sender/brand details use current settings. Deleted source records do not delete history. No resend/requeue action is added.
|
|
80
|
+
|
|
81
|
+
## Internal agent parity
|
|
82
|
+
|
|
83
|
+
The owner agent uses the same services through save_mail_editor, get_mail_people, get_mail_send, update_mail_series, set_mail_series_steps, set_mail_settings and the existing send/preview/enrollment tools. remove_mail_suppression matches the CLI's existing authorized removal. Discuss and Build register the same Email capabilities. Tool schemas and runtime instructions describe complete saves, Off enrollment, waiting/resume timing, all settings/null resets, history filters and retry handling. The agent never takes site authority from model input.
|
|
84
|
+
|
|
85
|
+
Hosted developer guides and article bodies must be updated through the supported Frontpage CLI, using current base revisions. Never edit a customer site's Git branch or invent a second API version for this cutover.
|
package/PAYMENTS.md
CHANGED
|
@@ -467,3 +467,44 @@ payment count, and average payment, grouped by currency. Gross is before fees an
|
|
|
467
467
|
refunds. Schedules are not received money. Metrics cover the full recorded period,
|
|
468
468
|
not only the capped Activity rows. Dates use verified successful-payment times;
|
|
469
469
|
older records without proof show that the date is unavailable.
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
## Standalone setup and digital download receipts
|
|
473
|
+
|
|
474
|
+
An Offering is a standalone payment. Creating, updating or restoring it requests
|
|
475
|
+
Payments setup if needed. The shared response includes `checkout:"standalone"`,
|
|
476
|
+
`requiresFulfillment:false`, the saved `button`, and `setup.status` plus the existing
|
|
477
|
+
signed-in owner Stripe handoff. Store availability, shipping and pickup do not gate
|
|
478
|
+
an Offering. Payments off stays off. A saved `button.active:false` is a draft;
|
|
479
|
+
accepting payments requires both an active Offering and `setup.status.ready:true`.
|
|
480
|
+
`get` reports the same readiness under `payment`. A repeated create with the same
|
|
481
|
+
ID returns 409; read that ID and update it rather than creating another payment.
|
|
482
|
+
|
|
483
|
+
For an e-book, consultation resource or other download, reuse uploaded files or
|
|
484
|
+
owner-approved URLs. One combined file or several files can work. Add those links
|
|
485
|
+
to an Emails template, attach it using `--receipt-template-id`, and preview it:
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
frontpage payments buttons create --id digital-bundle --title "E-book + workbook" --price-cents 2500 --active false --json
|
|
489
|
+
frontpage payments buttons update digital-bundle --receipt-template-id TEMPLATE_UUID --json
|
|
490
|
+
frontpage payments buttons receipt-preview digital-bundle --json
|
|
491
|
+
frontpage payments buttons update digital-bundle --active true --json
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
`receipt-preview` requires CLI 0.2.10 or later. The other commands work in earlier
|
|
495
|
+
versions. The owner must supply the real file links before enabling the Offering.
|
|
496
|
+
A thank-you-only template does not deliver a download. Public uploaded links can
|
|
497
|
+
be forwarded; this workflow does not create buyer-only or expiring downloads or
|
|
498
|
+
native email attachments. Never manually send a receipt or subscribe the buyer to
|
|
499
|
+
marketing. Verified payment triggers the existing receipt job and send limits.
|
|
500
|
+
|
|
501
|
+
The preview endpoint is `POST /api/v1/sites/{slug}/payments/buttons/{id}/receipt-preview`.
|
|
502
|
+
It requires editor access and accepts optional sample values in
|
|
503
|
+
`{"vars":{"buyer_name":"Casey"}}`. Values must use the Offering's returned
|
|
504
|
+
`receiptVariables`, with strings up to 32,000 characters. Excluded private answers
|
|
505
|
+
cannot be supplied. No customer lookup, send or charge occurs. The response is
|
|
506
|
+
`{"preview":true,"receipt":"custom","itemId":"digital-bundle","receiptTemplateId":"…","subject":"…","html":"…","text":"…"}`.
|
|
507
|
+
With no custom template it returns `receipt:"stock"` and explains the standard
|
|
508
|
+
purchase receipt; it does not fabricate a custom preview. Purchase previews use
|
|
509
|
+
the actual transactional renderer. Marketing's postal-address requirement remains
|
|
510
|
+
in place for marketing sends; its preview warning does not block purchase receipts.
|
package/PEOPLE.md
CHANGED
|
@@ -235,7 +235,7 @@ The simple editor offers Purchases, Activity, and Contact details. Existing adva
|
|
|
235
235
|
| set\_follow\_up | minutes | Set a follow-up after this many minutes. |
|
|
236
236
|
| clear\_follow\_up | — | Clear the person’s follow-up. |
|
|
237
237
|
| send\_template | templateId | Send a saved template if eligible. |
|
|
238
|
-
| start\_series | seriesId |
|
|
238
|
+
| start\_series | seriesId | Enroll in a saved series. Off waits at step 1; On starts sending. |
|
|
239
239
|
| cancel\_series | seriesId | Stop this group’s enrollment; requires a group/stage trigger. |
|
|
240
240
|
|
|
241
241
|
A rule accepts 1–10 actions. Add/remove/move actions target manual groups; automatic groups use conditions and owner exceptions. `fromStageIds` prevents a late event from resetting someone who has moved further through the funnel.
|
|
@@ -301,7 +301,7 @@ Timed rules use minutes: 2880 is two days. They run through the worker, not a br
|
|
|
301
301
|
|
|
302
302
|
## Attach an email to a group or stage
|
|
303
303
|
|
|
304
|
-
Use one action, **Attach an email**, then choose a saved email template or
|
|
304
|
+
Use one action, **Attach an email**, then choose a saved email template or a series with saved steps. An Off series enrolls people waiting at step 1; turning it On starts them. An attachment creates a normal editable rule on group entry or stage entry. Campaigns are one-off emails and cannot be selected for automation.
|
|
305
305
|
|
|
306
306
|
Save a reusable template as `welcome.json`:
|
|
307
307
|
|
|
@@ -645,6 +645,6 @@ frontpage people workspace series.preview --site YOUR_SITE --data '{"seriesId":"
|
|
|
645
645
|
|
|
646
646
|
To enrol, copy each returned batch's `personIds` and `reviewToken` into `series.enrol`, adding `seriesId` and a unique stable `requestKey`. Preview returns batches of five; the service accepts at most 25 per request. Reuse the original payload and key after a lost response. Results distinguish enrolled, already enrolled, skipped and failed. A new preview uses new keys. Never enrol or send without the user's explicit instruction.
|
|
647
647
|
|
|
648
|
-
Manual series
|
|
648
|
+
Manual series enrollment uses selected person IDs and saved steps. Off records real enrollment waiting at step 1, without a send, snapshot, timer or allowance reservation; no second Save series is needed. On starts the first email subject to sender, permission, send window and allowance checks. Turning On resumes existing people at their next unsent step with its due date intact; later waits start after successful sends. Review returns status, exclusions and nullable remainingAllowance (null while Off). It never activates a series or restarts existing, completed or cancelled enrollment, including a person whose email changed. Delivery rechecks saved restrictions. Contact groups and permission stay unchanged. Campaign recipient intent also supports explicit addresses and null to clear; saving never sends or grants consent.
|
|
649
649
|
|
|
650
650
|
People supplies first-name and unsubscribe values. If content uses order, booking or other caller-only variables, edit the independent campaign or choose another series. Do not invent values or send empty required context.
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Requires Node.js ≥ 20.
|
|
|
18
18
|
|
|
19
19
|
## 60-second path
|
|
20
20
|
|
|
21
|
-
1. In
|
|
21
|
+
1. In Workspace: account menu → **Developers** → create an API key (`fp_live_…`).
|
|
22
22
|
2. `frontpage login` (or `frontpage login "$FRONTPAGE_API_KEY"`).
|
|
23
23
|
3. `frontpage sites` — note the **subdomain** (not a custom domain).
|
|
24
24
|
4. `mkdir my-site && cd my-site && frontpage use <subdomain>`
|
|
@@ -90,3 +90,7 @@ frontpage store items get camp-hat --site my-site --json
|
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
A whole stock count of `0` means sold out; `unlimited` sends JSON null. Omit stock on update to keep it. To rename existing option types, pass an `optionNames` list with the same number of names through `items update --file`. To return to one default SKU, pass `{"optionNames":[]}`. The existing first live variant supplies the regular price, sale price (including no sale), and stock (including unlimited). Inspect the live variants first and read back the result; other live variants are archived. Use `variants add` to turn options on. Do not recreate the product.
|
|
93
|
+
|
|
94
|
+
## Emails workspace workflows
|
|
95
|
+
|
|
96
|
+
See [MAIL.md](./MAIL.md) for the complete save contract, reusable people picker directory, saved campaign recipients, Off-series enrollment and resume timing, all sender settings/null resets, suppression rules, Sent queries and details, and retry-safe examples. `frontpage mail save --file save.json` saves template and pending series together; it never sends. `frontpage mail people` reads the same site-scoped directory as the workspace. Use `frontpage help mail --json` for current commands and flags. No new CLI binary, API namespace or migration command is required. Hosted guides and article help must be updated through the Frontpage CLI.
|
package/dist/commands/login.js
CHANGED
|
@@ -18,7 +18,7 @@ export async function runLogin(args) {
|
|
|
18
18
|
return EXIT_USAGE;
|
|
19
19
|
}
|
|
20
20
|
if (!secret.startsWith("fp_live_")) {
|
|
21
|
-
console.error("Key should start with fp_live_. Create one in Developers (
|
|
21
|
+
console.error("Key should start with fp_live_. Create one in Developers (Workspace account menu).");
|
|
22
22
|
return EXIT_USAGE;
|
|
23
23
|
}
|
|
24
24
|
await setApiKey(secret);
|
package/dist/commands/mail.js
CHANGED
|
@@ -4,12 +4,13 @@ import { resolveSlug } from "../site.js";
|
|
|
4
4
|
import { CliHttpError, EXIT_OK, EXIT_USAGE, exitCodeForError, v1Delete, v1Get, v1Patch, v1Post, v1Put, } from "../http/client.js";
|
|
5
5
|
import { assertNoUnknownFlags, hasFlag, takeFlag } from "../argv.js";
|
|
6
6
|
import { emitErr, emitOk, isJson } from "../runtime.js";
|
|
7
|
-
const USAGE = "Usage: frontpage mail settings|email|template|series|send|sends|enrolments|suppress … [--site <slug>] [--json]";
|
|
7
|
+
const USAGE = "Usage: frontpage mail settings|email|template|series|save|people|send|sends|enrolments|suppress … [--site <slug>] [--json]";
|
|
8
8
|
const ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9
9
|
function requireId(work, kind) {
|
|
10
10
|
const fromFlag = takeFlag(work, "--id");
|
|
11
11
|
const positional = work.find((a) => !a.startsWith("-"));
|
|
12
|
-
const raw = fromFlag ??
|
|
12
|
+
const raw = fromFlag ??
|
|
13
|
+
(positional ? work.splice(work.indexOf(positional), 1)[0] : undefined);
|
|
13
14
|
if (!raw || !ID_RE.test(raw)) {
|
|
14
15
|
throw new CliHttpError(`${kind} not found.`, EXIT_USAGE, 404, "not_found");
|
|
15
16
|
}
|
|
@@ -35,7 +36,10 @@ function parseVars(pairs) {
|
|
|
35
36
|
return vars;
|
|
36
37
|
}
|
|
37
38
|
function parseRecipients(work) {
|
|
38
|
-
const tos = takeAll(work, "--to").flatMap((v) => v
|
|
39
|
+
const tos = takeAll(work, "--to").flatMap((v) => v
|
|
40
|
+
.split(",")
|
|
41
|
+
.map((s) => s.trim())
|
|
42
|
+
.filter(Boolean));
|
|
39
43
|
const name = takeFlag(work, "--name");
|
|
40
44
|
const vars = parseVars(takeAll(work, "--var"));
|
|
41
45
|
if (tos.length === 0) {
|
|
@@ -83,6 +87,45 @@ export async function runMail(args) {
|
|
|
83
87
|
if (group === "campaign")
|
|
84
88
|
return await runCampaign(slug, rest.slice(1));
|
|
85
89
|
const base = `/api/v1/sites/${encodeURIComponent(slug)}/mail`;
|
|
90
|
+
if (group === "save") {
|
|
91
|
+
const work = rest.slice(1), file = takeFlag(work, "--file");
|
|
92
|
+
assertNoUnknownFlags(work, "mail");
|
|
93
|
+
if (!file)
|
|
94
|
+
throw new CliHttpError("Use mail save --file save.json. Keep its requestKey and payload unchanged for retries.", EXIT_USAGE, 0, "bad_request");
|
|
95
|
+
const data = await v1Post(`${base}/save`, await readJsonFile(file));
|
|
96
|
+
if (isJson())
|
|
97
|
+
emitOk("mail", data);
|
|
98
|
+
else
|
|
99
|
+
console.log("Email and series changes saved.");
|
|
100
|
+
return EXIT_OK;
|
|
101
|
+
}
|
|
102
|
+
if (group === "people") {
|
|
103
|
+
const work = rest.slice(1), params = new URLSearchParams();
|
|
104
|
+
for (const flag of [
|
|
105
|
+
"q",
|
|
106
|
+
"source",
|
|
107
|
+
"sort",
|
|
108
|
+
"availability",
|
|
109
|
+
"page",
|
|
110
|
+
"selected",
|
|
111
|
+
]) {
|
|
112
|
+
const value = takeFlag(work, `--${flag}`);
|
|
113
|
+
if (value !== undefined)
|
|
114
|
+
params.set(flag, value);
|
|
115
|
+
}
|
|
116
|
+
if (hasFlag(work, "--selected-only"))
|
|
117
|
+
params.set("selectedOnly", "true");
|
|
118
|
+
assertNoUnknownFlags(work, "mail");
|
|
119
|
+
const data = params.has("selected")
|
|
120
|
+
? await v1Post(`${base}/people`, Object.fromEntries(params))
|
|
121
|
+
: await v1Get(`${base}/people?${params}`);
|
|
122
|
+
if (isJson())
|
|
123
|
+
emitOk("mail", data);
|
|
124
|
+
else
|
|
125
|
+
for (const p of (data.people ?? []))
|
|
126
|
+
console.log(`${p.id} ${p.email ?? ""} ${p.eligible ? "eligible" : "restricted"}`);
|
|
127
|
+
return EXIT_OK;
|
|
128
|
+
}
|
|
86
129
|
if (group === "settings") {
|
|
87
130
|
const sub = rest[1] ?? "get";
|
|
88
131
|
const work = rest.slice(2);
|
|
@@ -102,12 +145,29 @@ export async function runMail(args) {
|
|
|
102
145
|
const replyTo = takeFlag(work, "--reply-to");
|
|
103
146
|
const fromName = takeFlag(work, "--from-name");
|
|
104
147
|
const timezone = takeFlag(work, "--timezone");
|
|
148
|
+
const file = takeFlag(work, "--file"), headerAlign = takeFlag(work, "--header-align"), start = takeFlag(work, "--window-start"), end = takeFlag(work, "--window-end"), cap = takeFlag(work, "--hourly-cap");
|
|
149
|
+
const extra = file ? await readJsonFile(file) : {};
|
|
150
|
+
if (headerAlign !== undefined)
|
|
151
|
+
extra.headerAlign = headerAlign;
|
|
152
|
+
if (start !== undefined)
|
|
153
|
+
extra.sendWindowStart = Number(start);
|
|
154
|
+
if (end !== undefined)
|
|
155
|
+
extra.sendWindowEnd = Number(end);
|
|
156
|
+
if (cap !== undefined) {
|
|
157
|
+
if (cap !== "null" &&
|
|
158
|
+
(!Number.isInteger(Number(cap)) ||
|
|
159
|
+
Number(cap) < 1 ||
|
|
160
|
+
Number(cap) > 1000))
|
|
161
|
+
throw new CliHttpError("Hourly cap must be an integer from 1 to 1000, or null.", EXIT_USAGE, 0, "bad_request");
|
|
162
|
+
extra.hourlyCap = cap === "null" ? null : Number(cap);
|
|
163
|
+
}
|
|
105
164
|
assertNoUnknownFlags(work, "mail");
|
|
106
165
|
const data = await v1Patch(`${base}/settings`, {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
166
|
+
...extra,
|
|
167
|
+
...(postal !== undefined ? { postalLine: postal } : {}),
|
|
168
|
+
...(replyTo !== undefined ? { replyTo } : {}),
|
|
169
|
+
...(fromName !== undefined ? { fromName } : {}),
|
|
170
|
+
...(timezone !== undefined ? { timezone } : {}),
|
|
111
171
|
});
|
|
112
172
|
if (isJson())
|
|
113
173
|
emitOk("mail", data);
|
|
@@ -222,7 +282,8 @@ export async function runMail(args) {
|
|
|
222
282
|
if (isJson())
|
|
223
283
|
emitOk("mail", data);
|
|
224
284
|
else
|
|
225
|
-
console.log((data.preview ?? {}).subject ??
|
|
285
|
+
console.log((data.preview ?? {}).subject ??
|
|
286
|
+
"Preview ready.");
|
|
226
287
|
return EXIT_OK;
|
|
227
288
|
}
|
|
228
289
|
}
|
|
@@ -256,7 +317,9 @@ export async function runMail(args) {
|
|
|
256
317
|
assertNoUnknownFlags(work, "mail");
|
|
257
318
|
if (!name)
|
|
258
319
|
throw new CliHttpError("Usage: frontpage mail series create --name …", EXIT_USAGE, 0, "bad_request");
|
|
259
|
-
const data = await v1Post(`${base}/series`, {
|
|
320
|
+
const data = await v1Post(`${base}/series`, {
|
|
321
|
+
name,
|
|
322
|
+
});
|
|
260
323
|
if (isJson())
|
|
261
324
|
emitOk("mail", data);
|
|
262
325
|
else
|
|
@@ -310,7 +373,11 @@ export async function runMail(args) {
|
|
|
310
373
|
let steps = [];
|
|
311
374
|
if (file) {
|
|
312
375
|
const body = await readJsonFile(file);
|
|
313
|
-
steps = Array.isArray(body.steps)
|
|
376
|
+
steps = Array.isArray(body.steps)
|
|
377
|
+
? body.steps
|
|
378
|
+
: Array.isArray(body)
|
|
379
|
+
? body
|
|
380
|
+
: [];
|
|
314
381
|
}
|
|
315
382
|
else {
|
|
316
383
|
steps = stepFlags.map(parseStepFlag);
|
|
@@ -350,8 +417,14 @@ export async function runMail(args) {
|
|
|
350
417
|
const id = requireId(restKind, kind === "series" ? "Series" : "Document");
|
|
351
418
|
const recipients = parseRecipients(restKind);
|
|
352
419
|
assertNoUnknownFlags(restKind, "mail");
|
|
353
|
-
const path = kind === "series"
|
|
354
|
-
|
|
420
|
+
const path = kind === "series"
|
|
421
|
+
? `${base}/series/${id}/send`
|
|
422
|
+
: `${base}/${kind === "email" ? "emails" : "templates"}/${id}/send`;
|
|
423
|
+
const data = await v1Post(path, {
|
|
424
|
+
recipients,
|
|
425
|
+
wait,
|
|
426
|
+
test,
|
|
427
|
+
});
|
|
355
428
|
if (isJson())
|
|
356
429
|
emitOk("mail", data);
|
|
357
430
|
else
|
|
@@ -365,10 +438,16 @@ export async function runMail(args) {
|
|
|
365
438
|
const work = rest.slice(2);
|
|
366
439
|
if (sub === "list") {
|
|
367
440
|
const batchId = takeFlag(work, "--batch");
|
|
441
|
+
const filters = new URLSearchParams();
|
|
442
|
+
for (const flag of ["q", "status", "type", "page"]) {
|
|
443
|
+
const value = takeFlag(work, `--${flag}`);
|
|
444
|
+
if (value !== undefined)
|
|
445
|
+
filters.set(flag, value);
|
|
446
|
+
}
|
|
368
447
|
const limit = takeFlag(work, "--limit");
|
|
369
448
|
const cursor = takeFlag(work, "--cursor");
|
|
370
449
|
assertNoUnknownFlags(work, "mail");
|
|
371
|
-
const params =
|
|
450
|
+
const params = filters;
|
|
372
451
|
if (batchId)
|
|
373
452
|
params.set("batchId", batchId);
|
|
374
453
|
if (limit)
|
|
@@ -402,8 +481,13 @@ export async function runMail(args) {
|
|
|
402
481
|
const work = rest.slice(2);
|
|
403
482
|
if (sub === "list") {
|
|
404
483
|
const seriesId = takeFlag(work, "--series");
|
|
405
|
-
assertNoUnknownFlags(work, "mail");
|
|
406
484
|
const params = new URLSearchParams();
|
|
485
|
+
for (const name of ["q", "offset", "limit"]) {
|
|
486
|
+
const value = takeFlag(work, `--${name}`);
|
|
487
|
+
if (value !== undefined)
|
|
488
|
+
params.set(name, value);
|
|
489
|
+
}
|
|
490
|
+
assertNoUnknownFlags(work, "mail");
|
|
407
491
|
if (seriesId)
|
|
408
492
|
params.set("seriesId", seriesId);
|
|
409
493
|
const qs = params.toString();
|
|
@@ -437,7 +521,8 @@ export async function runMail(args) {
|
|
|
437
521
|
if (isJson())
|
|
438
522
|
emitOk("mail", data);
|
|
439
523
|
else {
|
|
440
|
-
const rows = data.suppressions ??
|
|
524
|
+
const rows = data.suppressions ??
|
|
525
|
+
[];
|
|
441
526
|
for (const row of rows)
|
|
442
527
|
console.log(`${row.email ?? ""} ${row.reason ?? ""}`);
|
|
443
528
|
}
|
|
@@ -185,6 +185,11 @@ export async function runPayments(args) {
|
|
|
185
185
|
method = "PATCH";
|
|
186
186
|
body = await payload(work);
|
|
187
187
|
}
|
|
188
|
+
else if (sub === "receipt-preview") {
|
|
189
|
+
path = `buttons/${idOf(work)}/receipt-preview`;
|
|
190
|
+
method = "POST";
|
|
191
|
+
body = await payload(work);
|
|
192
|
+
}
|
|
188
193
|
else if (sub === "archive" || sub === "restore") {
|
|
189
194
|
path = `buttons/${idOf(work)}/${sub}`;
|
|
190
195
|
method = "POST";
|
|
@@ -192,7 +197,7 @@ export async function runPayments(args) {
|
|
|
192
197
|
confirm(body, sub === "restore" ? "Restore this offering" : "Delete this offering from new payments");
|
|
193
198
|
}
|
|
194
199
|
else
|
|
195
|
-
usage("Use payments buttons list|get|create|update|archive|restore.");
|
|
200
|
+
usage("Use payments buttons list|get|create|update|archive|restore|receipt-preview.");
|
|
196
201
|
}
|
|
197
202
|
else if (group === "activity") {
|
|
198
203
|
const sub = work.shift() ?? "list";
|
|
@@ -28,6 +28,7 @@ _frontpage_completions() {
|
|
|
28
28
|
people) COMPREPLY=( $(compgen -W "list get add set note rm-note follow-up hide unhide" -- "\${cur}") ) ;;
|
|
29
29
|
routines) COMPREPLY=( $(compgen -W "list get add set pause resume rm run runs" -- "\${cur}") ) ;;
|
|
30
30
|
buttons) COMPREPLY=( $(compgen -W "list get create update archive restore" -- "\${cur}") ) ;;
|
|
31
|
+
mail) COMPREPLY=( $(compgen -W "save people campaign settings email template series send sends enrolments suppress --file --q --status --type --page --limit --cursor --source --sort --availability --selected --selected-only --offset --header-align --window-start --window-end --hourly-cap --site --json" -- "\${cur}") ) ;;
|
|
31
32
|
payments) COMPREPLY=( $(compgen -W "status settings connect availability activity buttons tax invoices recurring customers --file --operation-key --preview-hash --person --email --description --amount-cents --cadence --first-date --time-zone --due-days --no-open --site --json --yes" -- "\${cur}") ) ;;
|
|
32
33
|
store) COMPREPLY=( $(compgen -W "items variants categories settings orders import" -- "\${cur}") ) ;;
|
|
33
34
|
modules)
|
|
@@ -74,6 +75,7 @@ _frontpage() {
|
|
|
74
75
|
analytics) _values 'subcommand' dashboard summary query funnel timeseries compare sql ;;
|
|
75
76
|
people) _values 'subcommand' list get add set note rm-note follow-up hide unhide ;;
|
|
76
77
|
routines) _values 'subcommand' list get add set pause resume rm run runs ;;
|
|
78
|
+
mail) _values 'subcommand' save people campaign settings email template series send sends enrolments suppress ;;
|
|
77
79
|
buttons) _values 'subcommand' list get create update archive restore ;;
|
|
78
80
|
payments) _values 'subcommand' status settings connect availability activity buttons tax invoices recurring customers ;;
|
|
79
81
|
store) _values 'subcommand' items variants categories settings orders import ;;
|
|
@@ -114,6 +116,7 @@ export function generateFishCompletion() {
|
|
|
114
116
|
`complete -c frontpage -n "__fish_seen_subcommand_from analytics" -a "dashboard summary query funnel timeseries compare sql"`,
|
|
115
117
|
`complete -c frontpage -n "__fish_seen_subcommand_from people" -a "list get add set note rm-note follow-up hide unhide"`,
|
|
116
118
|
`complete -c frontpage -n "__fish_seen_subcommand_from routines" -a "list get add set pause resume rm run runs"`,
|
|
119
|
+
`complete -c frontpage -n "__fish_seen_subcommand_from mail" -a "save people campaign settings email template series send sends enrolments suppress"`,
|
|
117
120
|
`complete -c frontpage -n "__fish_seen_subcommand_from buttons" -a "list get create update archive restore"`,
|
|
118
121
|
`complete -c frontpage -n "__fish_seen_subcommand_from payments" -a "status settings connect availability activity buttons tax invoices recurring customers"
|
|
119
122
|
complete -c frontpage -n "__fish_seen_subcommand_from store" -a "items variants categories settings orders import"`,
|
package/dist/registry.js
CHANGED
|
@@ -38,9 +38,9 @@ export const COMMANDS = [
|
|
|
38
38
|
summary: "Save an API key to the OS keychain",
|
|
39
39
|
usage: "frontpage login [api-key]",
|
|
40
40
|
topics: ["auth"],
|
|
41
|
-
examples: [
|
|
41
|
+
examples: ["frontpage login", 'frontpage login "$FRONTPAGE_API_KEY"'],
|
|
42
42
|
flags: [
|
|
43
|
-
"FRONTPAGE_API_KEY env overrides keychain after login for that process"
|
|
43
|
+
"FRONTPAGE_API_KEY env overrides keychain after login for that process",
|
|
44
44
|
],
|
|
45
45
|
seeAlso: ["whoami", "doctor", "logout"],
|
|
46
46
|
},
|
|
@@ -71,7 +71,7 @@ export const COMMANDS = [
|
|
|
71
71
|
topics: ["auth", "sync"],
|
|
72
72
|
examples: ["frontpage use arbiter"],
|
|
73
73
|
dangers: [
|
|
74
|
-
"Clears baseCommitSha when switching sites — run pull again before push"
|
|
74
|
+
"Clears baseCommitSha when switching sites — run pull again before push",
|
|
75
75
|
],
|
|
76
76
|
seeAlso: ["link", "pull", "status"],
|
|
77
77
|
},
|
|
@@ -207,7 +207,7 @@ export const COMMANDS = [
|
|
|
207
207
|
examples: [
|
|
208
208
|
"frontpage open",
|
|
209
209
|
"frontpage open /updates",
|
|
210
|
-
"frontpage open --path /pricing"
|
|
210
|
+
"frontpage open --path /pricing",
|
|
211
211
|
],
|
|
212
212
|
flags: [
|
|
213
213
|
"Optional site-relative path appended to https://<slug>.frontpage.host",
|
|
@@ -424,14 +424,14 @@ export const COMMANDS = [
|
|
|
424
424
|
"frontpage customer-inbox get settings",
|
|
425
425
|
"frontpage customer-inbox put settings --file ./inbox-settings.json --request-key settings_revision_1",
|
|
426
426
|
"frontpage customer-inbox get conversations",
|
|
427
|
-
"frontpage customer-inbox get knowledge"
|
|
427
|
+
"frontpage customer-inbox get knowledge",
|
|
428
428
|
],
|
|
429
429
|
flags: [
|
|
430
430
|
"Internal plans and the site pilot apply",
|
|
431
431
|
"Writes require a stable --request-key and a JSON file; reuse both on retries",
|
|
432
432
|
"Human CLI access can guide, take over and reply; website-agent tools have management scope only",
|
|
433
433
|
"Luna and 10× AI billing are fixed",
|
|
434
|
-
"Full request examples: CUSTOMER-INBOX.md"
|
|
434
|
+
"Full request examples: CUSTOMER-INBOX.md",
|
|
435
435
|
],
|
|
436
436
|
seeAlso: ["people", "usage"],
|
|
437
437
|
},
|
|
@@ -449,7 +449,7 @@ export const COMMANDS = [
|
|
|
449
449
|
topics: ["content"],
|
|
450
450
|
examples: [
|
|
451
451
|
"frontpage routines list",
|
|
452
|
-
|
|
452
|
+
'frontpage routines add --cadence weekly --weekdays tue,thu,fri --hours 8am,12pm,4pm --timezone America/Chicago --name "Weekday check" --prompt "Review new form submissions and reply to each customer."',
|
|
453
453
|
"frontpage routines pause <id>",
|
|
454
454
|
"frontpage routines resume <id>",
|
|
455
455
|
"frontpage routines run <id>",
|
|
@@ -480,8 +480,8 @@ export const COMMANDS = [
|
|
|
480
480
|
"frontpage people list",
|
|
481
481
|
"frontpage people add --email alex@example.com --name Alex",
|
|
482
482
|
"frontpage people set <id> --status working",
|
|
483
|
-
|
|
484
|
-
|
|
483
|
+
'frontpage people set <id> --line1 "12 Main" --city Austin --region TX --postal 78701 --country US',
|
|
484
|
+
'frontpage people note <id> --body "Called them"',
|
|
485
485
|
"frontpage people follow-up <id> --at 2026-09-12T00:00:00Z",
|
|
486
486
|
"frontpage people hide <id>",
|
|
487
487
|
"frontpage people tag <id> --add vip",
|
|
@@ -549,7 +549,7 @@ export const COMMANDS = [
|
|
|
549
549
|
"status; settings; connect [--no-open]. Connect returns an existing signed-in admin browser handoff; it never accepts Stripe terms or completes onboarding.",
|
|
550
550
|
"availability preview|on|off. off reads affected counts before confirmed pause/void; a partial response stays off and requires another off request. on never resumes stopped billing or restores void invoices.",
|
|
551
551
|
"activity list|get <id> --source order|payment. invoices and recurring have separate get commands. Source IDs never collide.",
|
|
552
|
-
"buttons list [--limit 1..100 --offset N --archived true]; get|create|update|archive|restore. These manage Offerings. Create uses --id, --title, --price-cents, optional --amount open --min-cents. Create/update support --description, --active true|false, --receipt-template-id UUID or --default-receipt, and --file. Price, details and template save together. Archive deletes from new payments while retaining history; archive/restore require --yes. Only registered Offerings authorize Pay now; page markup and Store flags never do.",
|
|
552
|
+
"buttons list [--limit 1..100 --offset N --archived true]; get|create|update|archive|restore|receipt-preview. These manage Offerings. receipt-preview <id> [--file samples.json] renders the attached purchase receipt without sending or charging; samples.json can contain sample receipt variables under vars. Create uses --id, --title, --price-cents, optional --amount open --min-cents. Create/update support --description, --active true|false, --receipt-template-id UUID or --default-receipt, and --file. Price, details and template save together. Archive deletes from new payments while retaining history; archive/restore require --yes. Saves request Payments setup when needed and return setup.status; they never enable Store or override Payments off. Only registered Offerings authorize Pay now; page markup and Store flags never do.",
|
|
553
553
|
"Offering create/get/update JSON includes receiptVariables. Use exact names in existing mail templates, then attach --receipt-template-id. Included answers expose answer_<customizationId>; private answers are excluded. Currency is owned by Payments, not Store.",
|
|
554
554
|
"tax get; tax settings --file <file> --yes; tax registrations list|add --country US --state CA --yes|end <id> --yes. Supply the business's actual tax registrations. No automatic tax advice or raw Account Session secrets.",
|
|
555
555
|
"invoices preview|send --file <file>; list [--limit 1..100 --offset N --arrangement <id>]; get|refresh|resend|void <id>. send/resend need --operation-key; send needs --preview-hash; financial writes require --yes in scripts.",
|
|
@@ -573,10 +573,10 @@ export const COMMANDS = [
|
|
|
573
573
|
{
|
|
574
574
|
name: "mail",
|
|
575
575
|
summary: "Emails: campaign drafts, People audiences, templates, series, and sends",
|
|
576
|
-
usage: "frontpage mail settings|email|template|series|campaign|send|sends|enrolments|suppress … [--site <slug>] [--json]",
|
|
576
|
+
usage: "frontpage mail save|people|settings|email|template|series|campaign|send|sends|enrolments|suppress … [--site <slug>] [--json]",
|
|
577
577
|
topics: ["content"],
|
|
578
578
|
examples: [
|
|
579
|
-
|
|
579
|
+
'frontpage mail settings set --postal "100 Main St" --reply-to you@example.com',
|
|
580
580
|
"frontpage mail email create --file draft.json --name Hours",
|
|
581
581
|
"frontpage mail send --file email.json --to ada@example.com --wait",
|
|
582
582
|
"frontpage mail send template <id> --to ada@example.com --var first_name=Ada",
|
|
@@ -584,6 +584,11 @@ export const COMMANDS = [
|
|
|
584
584
|
"frontpage mail suppress add --email bounce@example.com",
|
|
585
585
|
],
|
|
586
586
|
flags: [
|
|
587
|
+
"save --file save.json: one retry-safe email/series save; existing records need expectedUpdatedAt. See MAIL.md.",
|
|
588
|
+
"people --q --source --sort newest|oldest|name --availability all|eligible|restricted --page --selected --selected-only.",
|
|
589
|
+
"settings set --file settings.json supports all eight fields and null resets; --header-align, --window-start, --window-end, --hourly-cap null|1..1000.",
|
|
590
|
+
"sends list --q --status --type campaign|series|confirmation|template|test|inline --page 1 --limit 20.",
|
|
591
|
+
"Series status active means On; paused/draft mean Off. Enrollment while Off waits at step 1, without sending. Existing people resume their next unsent step.",
|
|
587
592
|
"campaign preview|audience|send|get. --id DRAFT_ID, --file audience.json ({audience:{groupIds|stageIds|personIds|filters}}). send requires --request-key; reuse it after a network error. Templates/series are for automation; campaigns have separate drafts. See PEOPLE.md.",
|
|
588
593
|
"email list is unsent drafts only. Sent one-offs are mail sends list",
|
|
589
594
|
"send --file is an inline one-off (no saved document)",
|
|
@@ -617,10 +622,10 @@ export const COMMANDS = [
|
|
|
617
622
|
examples: [
|
|
618
623
|
"frontpage seo get /",
|
|
619
624
|
"frontpage seo get --path /updates",
|
|
620
|
-
|
|
625
|
+
'frontpage seo set --path / --title "Home"',
|
|
621
626
|
],
|
|
622
627
|
flags: [
|
|
623
|
-
"get accepts positional path or --path (flag wins); unknown leftover flags fail closed"
|
|
628
|
+
"get accepts positional path or --path (flag wins); unknown leftover flags fail closed",
|
|
624
629
|
],
|
|
625
630
|
},
|
|
626
631
|
{
|
|
@@ -694,7 +699,7 @@ export const TOPICS = {
|
|
|
694
699
|
# Binary is: frontpage
|
|
695
700
|
# Do NOT install the unrelated npm package named "frontpage"
|
|
696
701
|
|
|
697
|
-
1. Create a key:
|
|
702
|
+
1. Create a key: Workspace → account menu → Developers (fp_live_…)
|
|
698
703
|
2. frontpage login # or: frontpage login "$FRONTPAGE_API_KEY"
|
|
699
704
|
3. frontpage sites # use the subdomain column
|
|
700
705
|
4. mkdir work && cd work && frontpage use <subdomain>
|
|
@@ -712,7 +717,7 @@ export const TOPICS = {
|
|
|
712
717
|
Agents: read AGENTS.md + frontpage help site-building; use --json for structured I/O.
|
|
713
718
|
More: frontpage help auth | sync | site-building | agent | content | history | isolation | exit-codes`,
|
|
714
719
|
auth: `Authentication
|
|
715
|
-
1. Create a key in
|
|
720
|
+
1. Create a key in Workspace: account menu → Developers
|
|
716
721
|
2. frontpage login
|
|
717
722
|
3. frontpage sites
|
|
718
723
|
4. frontpage use <subdomain> (e.g. asimov-press, not a custom domain)
|
|
@@ -838,7 +843,7 @@ export const TOPICS = {
|
|
|
838
843
|
# Store catalog + live storefront: https://frontpage.host/developers/store
|
|
839
844
|
# Integer cents only. $49.99 is 4999. stock --add/--remove are signed delta.
|
|
840
845
|
# Markup: data-fp-store-grid / data-fp-store-product / data-fp-cart. One item.astro, not one file per product.
|
|
841
|
-
frontpage mail settings|email|template|series|send|sends|enrolments|suppress
|
|
846
|
+
frontpage mail save|people|settings|email|template|series|send|sends|enrolments|suppress
|
|
842
847
|
frontpage seo get [/path|--path /path] | seo set --path /
|
|
843
848
|
frontpage redirects list|set|rm --from /old --to /new
|
|
844
849
|
frontpage articles list|content|create-section|create-article|set-draft|…
|
package/llms.txt
CHANGED
|
@@ -42,8 +42,12 @@ Customer Inbox (Scale and internal sites): CUSTOMER-INBOX.md. Settings, widget,
|
|
|
42
42
|
Payments, invoices and recurring billing: see packaged [PAYMENTS.md](./PAYMENTS.md), `frontpage help payments`, and https://frontpage.host/developers/payments. Version 0.2.0 removes writable currency, Store checkout mode and Store-owned money commands; Store stays cart-only. Preview/review before sends; keep stable operation keys on retries. Autopay requires the customer’s own verified authorization.
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
Offerings own every standalone Pay now payment. Use `frontpage payments buttons list|get|create|update|archive|restore`; create the saved offering before adding its returned `data-fp-pay` markup. Supports `--receipt-template-id UUID`, `--default-receipt`, `--active true|false`, combined price/details updates and JSON payloads. Archive deletes from active checkout but preserves history. The website agent uses the same service automatically for ordinary payment-button requests. Store stays cart-only; invoices stay saved customer-addressed records. Full instructions: PAYMENTS.md and https://frontpage.host/developers/payments.
|
|
45
|
+
Offerings own every standalone Pay now payment. Use `frontpage payments buttons list|get|create|update|archive|restore|receipt-preview`; create the saved offering before adding its returned `data-fp-pay` markup. Supports `--receipt-template-id UUID`, `--default-receipt`, `--active true|false`, combined price/details updates and JSON payloads. Archive deletes from active checkout but preserves history. The website agent uses the same service automatically for ordinary payment-button requests. Store stays cart-only; invoices stay saved customer-addressed records. Create/update/restore return setup.status and the existing Stripe handoff, without enabling Store or overriding Payments off. Preview a bound receipt with buttons receipt-preview; it never sends or charges. Digital delivery uses approved file links in the saved purchase receipt; keep the offering inactive until resources are ready. Full instructions: PAYMENTS.md and https://frontpage.host/developers/payments.
|
|
46
46
|
|
|
47
47
|
Payments owns Stripe currency. Read Offering receiptVariables before creating a confirmation template; attach it through Payments. Forms use submitted input names. Full receipt contract: PAYMENTS.md.
|
|
48
48
|
|
|
49
49
|
Customer Inbox: Frontpage website chat uses one settings toggle, no website edit/publish. get website shows public availability/managed addresses. websiteKnowledge.enabled controls default-on support website reads; no source or private-preview access. Source-ID toggles, attention filtering and held receipt pagination are supported. See CUSTOMER-INBOX.md.
|
|
50
|
+
|
|
51
|
+
## Emails workspace workflows
|
|
52
|
+
|
|
53
|
+
See [MAIL.md](./MAIL.md) for the complete save contract, reusable people picker directory, saved campaign recipients, Off-series enrollment and resume timing, all sender settings/null resets, suppression rules, Sent queries and details, and retry-safe examples. `frontpage mail save --file save.json` saves template and pending series together; it never sends. `frontpage mail people` reads the same site-scoped directory as the workspace. Use `frontpage help mail --json` for current commands and flags. No new CLI binary, API namespace or migration command is required. Hosted guides and article help must be updated through the Frontpage CLI.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frontpage-host",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Frontpage CLI
|
|
3
|
+
"version": "0.2.11",
|
|
4
|
+
"description": "Frontpage CLI \u2014 manage your site from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"frontpage": "dist/bin.js"
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"AGENTS.md",
|
|
13
13
|
"llms.txt",
|
|
14
14
|
"PEOPLE.md",
|
|
15
|
+
"MAIL.md",
|
|
15
16
|
"CUSTOMER-INBOX.md",
|
|
16
17
|
"PAYMENTS.md"
|
|
17
18
|
],
|