# Design — Approval Email Alert (per-lifecycle-event template picker)

**Status:** Draft for review
**Date:** 2026-07-01
**Goal:** the Salesforce-style approval **Email Alert** experience — on an approval
process, an admin picks an **email template per lifecycle event** (Initial
Submission, Final Approval, Final Rejection, Recall, …). Realized the
standard-compliant way: the **capability is a Flow** (registered content), the
**templates are an L2 catalog**, and a thin **L0 front-end "Email Alert"
convenience** scaffolds the Flow so admins never hand-build one.
**Relation:** [#3530](https://github.com/pingcap-inc/nexuscrm-pingcap-pack/issues/3530),
[#3556](https://github.com/pingcap-inc/nexuscrm-pingcap-pack/issues/3556),
[#2267](https://github.com/pingcap-inc/nexuscrm-pingcap-pack/issues/2267) (Flow-native email),
upstream `docs/design/l0-l1-standard.md`.
Artifacts: `scripts/salesforce_migration/approval_email/`.

---

## 0. Bottom line

Verified against the built ref `4937bb74` (what staging runs): the approval
engine dispatches **`FieldUpdate` and `Flow`**; the legacy **`Alert`** case is
**disabled — it fails closed** (`legacyWorkflowAlertRuntimeDisabledFailure`,
"after WorkflowAlert cutover"). Email on an approval is now sent by a **Flow**
(`executeApprovalFlowNotification` → the Flow's `sendEmail` node → an
`_System_EmailTemplate`).

Per the L0/L1 standard, that is exactly right and we do **not** re-add a backend
`Alert` arm. The standard's calibration table already rules this case:

> **"Send email for a Lead" → verb + seam L0; binding L1 metadata** … *"no new Go
> registry"* — source `issue-2267-flow-native-email-notifications.md`.

And **T3**: *"a uniform feature buildable on existing engines is registered
content, however generic it looks."* The `sendEmail` verb and the `Flow` approval
seam already exist (L0); the email-alert **behaviour is registered content
(L1)** — a Flow. What's missing is only **content** (templates) and **authoring
ergonomics** (a picker). So:

- **L2 — template catalog** (74 migrated templates + 4 Account approval templates + 11 per-object folders) → the Send Email picker is no longer empty.
- **L0 (front-end only) — an "Email Alert" convenience** in the approval action UI: pick a template per lifecycle event; it **scaffolds a Flow** under the hood (no backend arm, no resurrected Alert runtime).

## 1. Before (BF) — verified current build

```text
Approval process → lifecycle section (Initial Submission / Final Approval / …)
   → + Add Action → Action Type = Field Update | Flow            (no "Email Alert")
   to email: choose Flow → build a Flow with a Send Email node
             → Email Template picker → EMPTY ("Select template…")
```
- Backend: `Alert` fails closed; `Flow` is the live email path.
- Front-end: `ALLOWED_ACTION_TYPES = ['FieldUpdate','Flow']`; the `Alert` type,
  `WorkflowAlert` model, and `EmailAlertForm.tsx` still ship but are unreachable.
- No templates seeded → the Flow **Send Email** picker (`SendEmailPanel.tsx`,
  `emailTemplateAPI.getTemplates()`) has nothing to offer.

## 2. After (AF) — SFDC-style per-event email alert

```text
Approval process → Initial Submission Actions → + Add Action → Action Type = Email Alert
   → Email Template: [ Quote Submitted ▾ ]   (searchable, 11 per-object folders)
   → Recipients / Sender → Save
        ⇩ scaffolds under the hood
   a Flow  (Send Email node → chosen template, recipients)  +  a Flow action bound to "Submit"
Final Approval Actions   → Email Alert → [ Quote Approved ▾ ] → …
Final Rejection Actions  → Email Alert → [ Quote Rejected ▾ ] → …
```
- Same UX as Salesforce: **one template per lifecycle event**. The admin never
  sees or builds a Flow; the convenience emits a Flow (ActionType=`Flow`) — the
  **live, supported** path — not a disabled `Alert` action.

## 3. Standard classification (why this shape)

| Piece | Layer | Basis (l0-l1-standard.md) |
|---|---|---|
| `sendEmail` verb + `Flow` approval seam + render engine + `_System_EmailTemplate`/`ReportFolder` tables | **L0** (exists) | §1 engine kinds = orchestration (flow/approval); calibration "send email verb + seam L0" |
| The email-alert **behaviour** (Flow with a Send Email node bound to a lifecycle event) | **L1/L2** registered content | **T3** "buildable on existing engines → registered content"; calibration "binding L1 metadata, no new Go registry" |
| **Template catalog** (74 migrated templates + 4 Account approval templates + 11 folders) | **L2** tenant content | tenant templates/recipients; PingCAP business policy |
| **"Email Alert" authoring convenience** (per-event picker that scaffolds a Flow) | **L0 front-end** | generic, app-neutral authoring UI that emits registered content; **no** backend arm, **no** vocabulary branch |
| A new backend **`Alert` dispatch arm** | ❌ **forbidden** | T3 anti-pattern — duplicate engine branch ("Mario circuit soldered onto the motherboard"); "no new Go registry" |

## 4. What NexusCRM already has (do NOT rebuild) — L0

`Flow` approval action + `executeApprovalFlowNotification`; the `sendEmail` verb
+ `SendEmailPanel` template picker (`getTemplates()`, all active, no object
filter); `ReportFolder` (Type='Email') folders (`getFolders()`); native
`{{Record.*}}` rendering; `_System_EmailTemplate` table. **No backend change.**

## 5. Deliverables & layers

- **D1 — Template catalog (L2, pack seed).** 74 migrated `_System_EmailTemplate` +
  4 Account approval `_System_EmailTemplate` +
  11 per-object `ReportFolder` (Type='Email'). The broad catalog remains in
  `deploy-baseline.seed.yaml` for staging/test bootstrap. The production-safe
  Account approval subset (folder plus Submitted/Approved/Rejected/Recalled)
  is declared in `deployments/offline/runtime/reference-data/manifest.json` and
  applied by the same pack reference-data path in staging and prod; **no
  final-pack rebuild**. Populates the Send Email picker (and the future Email
  Alert picker) for every object. **Ready.**
- **D2 — "Email Alert" front-end convenience (L0, upstream NexusCRM front-end).**
  Add `'Alert'`-labelled option back to the approval action picker
  (`ProcessActionsSection.tsx`) — but on save it **emits a `Flow`**: create/att­ach
  a Flow whose Send Email node uses the chosen template + recipients, bound to the
  section's `ActionEvent` (Submit / FinalApproval / FinalRejection / …). Reuse the
  existing `EmailAlertForm.tsx` for the form; **do not** write ActionType=`Alert`
  rows (they fail closed). Generic, app-neutral. Delivered via fork + cross-repo
  PR (`chendu-dotcom/NexusCRM`).
- **D3 — (optional) pack-owned notification Flows (L2).** For Quote/Account, ship
  ready-made notification Flows + Flow actions so those approvals email out of the
  box. Otherwise admins self-serve via D2.

## 6. Deploy plan

| # | Change | Layer | Delivery | Depends on |
|---|---|---|---|---|
| 1 | Template catalog (74 migrated + 4 Account approval + 11 folders) | **L2** | broad staging/test catalog: `deploy-baseline.seed.yaml`; durable prod Account approval subset: Seed Dispatcher → `pack_reference_data_v1` (no rebuild) | — |
| 2 | "Email Alert" convenience (scaffolds a Flow) | **L0 front-end** | upstream fork PR → NexusCRM `main` → published L0 artifact → selected by the signed resolved BOM | — (works best with #1) |
| 3 | (opt.) Quote/Account notification Flows + Flow actions | **L2** | pack PR (seed) | #1 |
| 4 | UAT staging-inspection case | **L2** | pack PR (`staging-inspection/config/cases`) | #1 (#2 = browser check) |

**Sequence:** ship **#1 now** (independent, unblocks the *existing* Flow Send
Email picker immediately). **#2** is the upstream front-end convenience for
SFDC-parity ergonomics; it needs no backend change and lands via the normal
upstream-ref bump. #3/#4 follow.
**No backend/runtime engine change anywhere.** #1 has no upstream dependency; #2
is upstream **front-end only**.

## 7. Phasing

- **Phase 1 — catalog (L2), now.** Templates selectable in the Flow Send Email
  node + Setup → Email Templates. Admins can already wire approval email via a
  Flow action.
- **Phase 2 — "Email Alert" convenience (L0 front-end).** SFDC-parity per-event
  picker that scaffolds the Flow. The headline UX.
- **Phase 3 — `{{Approval.RequestUrl}}` / cross-object rendering (#2267); remaining
  approvals (#3556); HTML templates.**

## 8. Template catalog (D1) — per-object folders

Lead 18 · General 14 · PoC/Cloud 13 · Opportunity 7 · Order 6 · Quote 5 · Account 4 ·
Invoice 4 · Contact 1 · Contract 1 · Support 1 = **74** (see `migrated/TEMPLATE_CATALOG.md`).
Merge fields translated `{!Obj.Field}` → `{{Record.Field}}` (record data renders today);
`{!Obj.Link}` → `{{Record.Link}}` (unconfirmed) and `{!ApprovalRequest.Internal_URL}` →
`{{Approval.RequestUrl}}` (#2267) are flagged, not blockers.

## 9. Risks

| Item | Note |
|---|---|
| D2 is an upstream L0 front-end change | needs upstream buy-in + fork PR; but small and generic (produces a Flow). |
| Flow proliferation | each Email Alert scaffolds a Flow; use deterministic naming + dedup, or a shared parameterized notification Flow (Flow-input template) as a follow-up. |
| cross-object / link / approval-url merge fields | render caveats (#2267); not selection blockers. |

## 10. Verification / UAT

1. **Catalog (D1):** Setup → Email Templates shows 78 active templates in 11
   folders; the Flow Send Email picker (and, post-D2, the Email Alert picker) is
   non-empty and searchable by object name.
2. **Convenience (D2):** on an approval, Initial Submission → + Add Action →
   **Email Alert** → pick a template → Save → a Flow action is created bound to
   `Submit`; repeat for Final Approval / Final Rejection with distinct templates.
3. **Staging-inspection UAT case** (`staging-inspection/config/cases/…`): asserts
   the deterministic server contract — catalog present + selectable — with the
   per-event picker itself flagged as a browser check (per the connect-case
   precedent). See `approval-email-template-catalog-api.json`.
