Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions tools/v2/individual/email-template-library/docs/contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Email Template Library Core Contract

This document describes the folder-local core service for future hook and UI
work. It does not authorize any main app integration.

## Public API

Import from `tools/v2/individual/email-template-library/index.mjs`.

```js
import { createTemplateService } from "./index.mjs";
```

## Template Shape

```js
{
id: "tmpl-follow-up",
name: "Polite follow-up",
categoryId: "cat-follow-up",
subject: "Following up on {{topic}}",
body: "Hi {{firstName}}, I wanted to follow up on {{topic}}.",
variables: [
{ key: "firstName", label: "First name" },
{ key: "topic", label: "Topic" }
],
tags: ["follow-up", "customer"],
updatedAt: "2026-06-02"
}
```

Rules:

- IDs use only letters, numbers, `_`, and `-`.
- `subject` and `body` are required.
- Variables use `{{variableKey}}` placeholders.
- Every placeholder must be declared in `variables`.
- Rendering substitutes only declared variables and reports missing values.

## Service Methods

`createTemplateService({ templates, categories })` returns:

- `getTemplates()`
- `getCategories()`
- `getTemplate(id)`
- `saveTemplate(template)`
- `deleteTemplate(id)`
- `searchTemplates({ query, categoryId, limit })`
- `renderTemplate(id, values)`

The service stores data in local memory only and returns cloned values to avoid
accidental fixture mutation.

## State Shapes

`createTemplateListState` and `searchTemplates` return:

```js
{
status: "success" | "empty" | "error",
isLoading: false,
error: null,
query: "invoice",
templates: [],
totalCount: 0
}
```

`createLoadingState(query)` returns:

```js
{
status: "loading",
isLoading: true,
error: null,
query: "invoice",
templates: [],
totalCount: 0
}
```

## Error State

Validation errors use `EmailTemplateLibraryError` and include a field when the
error is field-specific:

```js
{
status: "error",
error: {
message: "template subject is required",
field: "subject"
}
}
```

## Known Limitations

- Data is in-memory only.
- No template persistence, user account sync, analytics, or remote search exists.
- No live network calls or production mail content are introduced.
- Hooks and UI components are future issues.
- Main app compose integration is out of scope for this core issue.
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{
"metadata": {
"tool": "email-template-library",
"fixtureVersion": 1,
"scope": "folder-local",
"dataPolicy": [
"Synthetic email templates only.",
"No real recipients, credentials, wallet values, or production mail content.",
"No live network references are required to run tests."
]
},
"categories": [
{
"id": "cat-follow-up",
"name": "Follow-up"
},
{
"id": "cat-billing",
"name": "Billing"
},
{
"id": "cat-support",
"name": "Support"
}
],
"templates": [
{
"id": "tmpl-follow-up",
"name": "Polite follow-up",
"categoryId": "cat-follow-up",
"subject": "Following up on {{topic}}",
"body": "Hi {{firstName}}, I wanted to follow up on {{topic}}. Please let me know if you have any questions.",
"variables": [
{
"key": "firstName",
"label": "First name"
},
{
"key": "topic",
"label": "Topic"
}
],
"tags": ["follow-up", "customer", "polite"],
"updatedAt": "2026-06-02"
},
{
"id": "tmpl-invoice-copy",
"name": "Invoice copy request",
"categoryId": "cat-billing",
"subject": "Invoice copy for {{invoiceMonth}}",
"body": "Hi {{firstName}}, I attached the invoice copy for {{invoiceMonth}}. Reply here if anything looks off.",
"variables": [
{
"key": "firstName",
"label": "First name"
},
{
"key": "invoiceMonth",
"label": "Invoice month"
}
],
"tags": ["invoice", "billing", "receipt"],
"updatedAt": "2026-06-04"
},
{
"id": "tmpl-support-resolution",
"name": "Support resolution",
"categoryId": "cat-support",
"subject": "Resolution for {{caseId}}",
"body": "Hi {{firstName}}, we resolved case {{caseId}}. The fix was applied and no further action is needed.",
"variables": [
{
"key": "firstName",
"label": "First name"
},
{
"key": "caseId",
"label": "Case ID"
}
],
"tags": ["support", "case", "resolution"],
"updatedAt": "2026-06-06"
}
],
"searchCases": [
{
"id": "search-billing",
"input": {
"query": "invoice receipt",
"limit": 2
},
"expectedFirstId": "tmpl-invoice-copy"
},
{
"id": "search-follow-up",
"input": {
"query": "polite customer follow up",
"limit": 2
},
"expectedFirstId": "tmpl-follow-up"
},
{
"id": "search-empty",
"input": {
"query": "banana telescope",
"limit": 2
},
"expectedFirstId": null
}
]
}
16 changes: 16 additions & 0 deletions tools/v2/individual/email-template-library/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export {
EmailTemplateLibraryError,
LIMITS,
sanitizeText,
extractTemplateVariables,
validateTemplateVariable,
validateTemplate,
validateCategory,
renderTemplateContent,
scoreTemplate,
createTemplateListState,
createLoadingState,
createEmptyState,
createErrorState,
createTemplateService,
} from "./services/template-service.mjs";
Loading