agents/openai.yaml
interface:
display_name: "Sent Template Builder UI"
short_description: "Audit cross-channel template builder UX"
default_prompt: "Use $template-builder-ui to audit this template builder flow and payload mapping."
references/template-status-handling.md
# Template status handling
## Resource states
Sent template resources currently surface `DRAFT`, `PENDING`, `APPROVED`, `REJECTED`, and `PAUSED`. Use an unknown state in the UI because contracts evolve.
## Provider webhook states
WhatsApp approval events use this envelope:
```json
{
"field": "templates",
"timestamp": "2026-08-09T12:00:00Z",
"payload": {
"account_id": "00000000-0000-0000-0000-000000000000",
"template_id": "11111111-1111-1111-1111-111111111111",
"template_name": "order_update",
"whatsapp_template_id": "2222222222222222",
"status": "CATEGORY_UPDATED",
"language": "en_US",
"category": "MARKETING",
"channel": "whatsapp",
"reason": "Provider category update"
}
}
```
Template events have `field: "templates"` and no `sub_type` or `event`. Known provider values include `PENDING`, `APPROVED`, `REJECTED`, and `CATEGORY_UPDATED`; values such as `PAUSED` and `DISABLED` may be forwarded verbatim.
## UI behavior
| Value | UI response |
| --- | --- |
| `DRAFT` | Editable; offer validate and submit actions. |
| `PENDING` | Lock provider-reviewed fields and show submission time. |
| `APPROVED` | Show usable status and immutable submitted content. |
| `REJECTED` | Show the reason and create a revision path. |
| `PAUSED` / `DISABLED` | Block new WhatsApp usage and surface remediation. |
| `CATEGORY_UPDATED` | Show old/new category when known and re-evaluate pricing/policy UX. |
| Unknown | Preserve raw value, use a neutral badge, and avoid destructive assumptions. |
Verify webhook signatures, deduplicate transitions, retrieve the current resource before overwriting local state, and tolerate delayed or out-of-order deliveries. Polling may be used as recovery, not as evidence that invented webhook event names exist.
references/template-ui-wireflows.md
# Template builder wireflows
## New draft
`intent → multiChannel body → variables → overrides → optional components → previews → validation → draft save`
- The internal draft may have a product label, but serialization drops it because create does not accept top-level `name`.
- Variable insertion writes the placeholder and entity together.
- Switching a channel override off preserves it locally until the user confirms deletion.
## Validate
`serialize → local linter → fix field errors → sandbox request → reconcile server response`
- Mark Meta `components[]` imports as unconverted and block Sent submission.
- Preserve the last valid server response separately from the working draft.
- Server normalization should appear as a reviewable diff.
## Submit for review
`draft → final preview → confirmation → submit_for_review=true → PENDING or returned state`
Do not autosubmit. The confirmation displays category, language, samples, buttons, overrides, and warnings.
## Status changes
`DRAFT → PENDING → APPROVED | REJECTED | PAUSED | unknown`
This is a UI model, not a promise of an exhaustive provider state machine. Template webhooks can forward `CATEGORY_UPDATED`, `DISABLED`, or future values. Render unknown states and keep raw values.
## Rejection recovery
`webhook/poll → retrieve current resource → show reason → fork editable revision → lint → sandbox → confirm resubmission`
Avoid destructive in-place edits when content is locked. Keep the submitted version and revision history visible.
## RCS preview
`text → zero-to-four suggestion chips → device preview`
Do not add rich-card, carousel, or attachment branches to current Sent workflows. If a mockup illustrates future capability, label it roadmap-only and exclude it from serialized requests.
## Routing simulator
If the product includes a send simulator, keep routing semantics explicit:
- no `channel` / `["sent"]`: automatic routing and fallback;
- one explicit channel: pinned;
- two or more explicit channels: broadcast with one message per recipient/channel pair.
references/template-validation-matrix.md
# Template validation matrix
This matrix maps UI controls to the body of `POST /v3/templates`.
## Request root
| Field | Client rule |
| --- | --- |
| `definition` | Required object. |
| `category` | Optional `UTILITY`, `MARKETING`, or `AUTHENTICATION`. |
| `language` | Optional locale such as `en_US`. |
| `creation_source` | Optional string. |
| `submit_for_review` | Boolean; default to false in the editor. |
| `sandbox` | Boolean; use true for validation. |
Reject top-level `name`, `channels`, `body`, `header`, `buttons`, and `components`. A `components[]` import is Meta Cloud API source and requires conversion.
## Definition
| Field | Rule |
| --- | --- |
| `body.multiChannel` | Required body content. |
| `body.sms`, `body.whatsapp`, `body.rcs` | Optional complete overrides using the same content schema. |
| `header.template` | Optional, maximum 60 characters. |
| `footer.template` | Optional, maximum 60 characters, no variables. |
| Body `template` | Required non-empty text, maximum 1,024 characters. |
| `definitionVersion` | Optional string; preserve when editing. |
| `authenticationConfig` | Only for `AUTHENTICATION`. |
Every placeholder uses `{{id:type}}`, for example `{{0:variable}}`. Each must map one-to-one to a variable with a non-negative integer `id`, readable `name`, matching `type`, and non-empty `props.sample`. IDs are unique within a body.
## Buttons
| Type | Total allowed | Required properties |
| --- | ---: | --- |
| `QUICK_REPLY` | Up to the overall total of 10 | `text`, `quickReplyType` |
| `URL` | 2 | `text`, `urlType`, `url` |
| `VOICE_CALL` | 1 | `text`, `countryCode`, `phoneNumber` |
| `PHONE_NUMBER` | 1 | `text`, `countryCode`, `phoneNumber` |
| `COPY_CODE` | 1 | `text`, `offerCode` |
There are at most 10 buttons in total, and button text is at most 25 characters. Quick replies and CTA buttons may coexist.
## Authentication
`authenticationConfig` accepts `addSecurityRecommendation` and optional `codeExpirationMinutes` from 1 through 90. Authentication templates should contain one code variable and one copy-code action, with no promotion, unrelated media, URL, or call action.
## Channel-specific product rules
| Channel | Current UI capability |
| --- | --- |
| SMS | Plain text preview and segment estimate. |
| WhatsApp | Header, body, footer, variables, and supported buttons. |
| RCS | Text and up to four suggestion chips. |
Do not expose current Sent controls for RCS rich cards, carousels, or media attachments. They are roadmap features. Do not require an SMS fallback body; automatic routing is a send-time choice made by omitting `channel` or using `["sent"]`.
## Server round trip
Client validation is advisory. Serialize the exact Sent request, run the bundled linter, validate with `sandbox: true`, and reconcile server errors by field. Never silently rewrite submitted copy.
SKILL.md
---
name: template-builder-ui
description: Designs and audits tenant-facing Sent template builders, previews, validation, lifecycle UX, and API payload mapping. Use for template editor forms, variables, channel overrides, WhatsApp review, RCS suggestion chips, authentication templates, and safe submission flows.
---
# Sent Template Builder UI
Design the interface around Sent's v3 `definition` contract. The UI may import Meta material, but its canonical saved and submitted model must never be Meta's `components[]` payload.
## Product model
Use one draft object with:
- optional `category` and `language`;
- required `definition.body.multiChannel`;
- optional complete body overrides for `sms`, `whatsapp`, and `rcs`;
- optional `definition.header`, `footer`, `buttons`, `definitionVersion`, and `authenticationConfig`;
- submission controls for `creation_source`, `submit_for_review`, and `sandbox`.
Do not expose top-level create fields named `name`, `channels`, `body`, `header`, or `buttons`. If the product needs an internal display label, keep it outside the Sent create payload.
## Recommended editor sequence
1. Capture intent and category.
2. Write the `multiChannel` body.
3. Insert variables as structured entities.
4. Add optional per-channel overrides.
5. Add header, footer, and buttons where supported.
6. Review live previews and accessibility.
7. Validate locally and with `sandbox: true`.
8. Save a draft, then explicitly submit for provider review.
Category should not block the first keystroke, but it must be visible before submission because it affects authentication rules and WhatsApp policy review.
## Variable UX
Inserting a variable creates both:
- a placeholder such as `{{0:variable}}`; and
- a matching entity with `id`, `name`, `type`, and `props.sample`.
Renumber atomically when variables move. Never let users edit placeholder syntax independently of the entity table. Show a clear error for naked `{{1}}` or IDs without definitions.
## Validation matrix
Apply the exact rules in [references/template-validation-matrix.md](references/template-validation-matrix.md), including:
- a 1,024-character maximum for every body;
- 60 characters for header and footer;
- no footer variables;
- 10 buttons total;
- button types `QUICK_REPLY`, `URL`, `VOICE_CALL`, `PHONE_NUMBER`, and `COPY_CODE` with their per-type limits;
- no invented quick-reply-versus-CTA exclusivity;
- `authenticationConfig` and authentication restrictions;
- complete, independently valid channel overrides.
Run the bundled `waba-template-author` linter against serialized JSON. Server validation remains authoritative.
## Channel previews
### SMS
Preview plain text and estimated GSM/UCS-2 segments. Make clear that segment estimates affect billing and are not template body limits.
### WhatsApp
Preview header, body, footer, and buttons. Show sample values, category, language, and provider-review impact.
### RCS
Current Sent RCS guidance supports text plus up to four suggestion chips. Rich cards, carousels, and media attachments are roadmap capabilities, not current Sent builder controls. Do not generate capability declarations for unavailable features.
Channel routing belongs to the send flow, not the template editor. If routing is shown in a simulator:
- omitted `channel` or `["sent"]` means automatic routing and fallback;
- `["rcs"]` pins RCS with no cross-channel fallback;
- multiple explicit values mean broadcast and separate billable messages.
Never describe an explicit RCS-plus-SMS array as ordered fallback.
## Save and review behavior
Use `sandbox: true` for validation. Save with `submit_for_review: false`. Before switching it to `true`, show:
- category and language;
- rendered previews with sample values;
- channel overrides;
- button actions;
- any warnings;
- the fact that provider review is an external state change.
Do not autosubmit on save.
## Lifecycle UX
Resource status values currently include `DRAFT`, `PENDING`, `APPROVED`, `REJECTED`, and `PAUSED`. Keep an unknown-state renderer.
WhatsApp template webhook events use `field: "templates"`, no `sub_type`, and no `event`; the status is `payload.status`. Provider values can include `CATEGORY_UPDATED`, `DISABLED`, and other future strings. See [references/template-status-handling.md](references/template-status-handling.md).
## Accessibility and failure recovery
- Associate every error with a field and a summary.
- Do not rely on preview color alone.
- Preserve user edits after validation failures.
- Keep raw JSON inspection available for advanced users.
- Label imported Meta JSON as “Meta Cloud API source” until converted.
- Provide a diff for server normalization and provider-driven category/status changes.
Use [references/template-ui-wireflows.md](references/template-ui-wireflows.md) for state transitions. Use `waba-template-author` for copy and policy judgment, `sent-templates` for existing-resource operations, and `rcs-agent-onboarding` for RCS launch readiness.