@fufulog/brevomorphic-cms-sdk 1.0.0
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/INTEGRATION.md +101 -0
- package/README.md +336 -0
- package/dist/brevoClient.d.ts +53 -0
- package/dist/brevoClient.js +141 -0
- package/dist/controller.d.ts +46 -0
- package/dist/controller.js +173 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/repository.d.ts +34 -0
- package/dist/repository.js +224 -0
- package/dist/service.d.ts +41 -0
- package/dist/service.js +185 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.js +1 -0
- package/env.example +25 -0
- package/package.json +35 -0
- package/prd.md +135 -0
- package/react/BrevoWysiwyg.css +233 -0
- package/react/BrevoWysiwyg.tsx +388 -0
- package/src/brevoClient.ts +171 -0
- package/src/controller.ts +186 -0
- package/src/index.ts +5 -0
- package/src/repository.ts +229 -0
- package/src/service.ts +221 -0
- package/src/types.ts +77 -0
- package/svelte/BrevoWysiwyg.svelte +572 -0
- package/tests/sdk.test.ts +239 -0
- package/tsconfig.json +16 -0
package/INTEGRATION.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Brevo CMS SDK - Host Integration Reference Notes
|
|
2
|
+
|
|
3
|
+
These integration notes document the implementation patterns, ESM target configurations, and routing setups utilized when embedding the `brevoCMS` SDK into a React/Vite/Express workspace.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Environment Configurations
|
|
8
|
+
|
|
9
|
+
Make sure to define these keys in the host environment (`.env`):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Brevo SMTP Configuration
|
|
13
|
+
BREVO_API_KEY="xkeysib-..."
|
|
14
|
+
DEFAULT_SENDER_EMAIL="info@yourdomain.com"
|
|
15
|
+
DEFAULT_SENDER_NAME="Your Brand Name"
|
|
16
|
+
DB_TYPE="firestore" # or 'postgres' | 'mysql'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. ESM Target Module Fixes
|
|
22
|
+
|
|
23
|
+
If your host application is configured to run as an ES Module (`"type": "module"` in `package.json`), you must build the `@fufulog/brevomorphic-cms-sdk` to match ESM:
|
|
24
|
+
|
|
25
|
+
1. Add `"type": "module"` to `brevoCMS/package.json` so TypeScript compiles native ES imports/exports.
|
|
26
|
+
2. Compile/rebuild the package:
|
|
27
|
+
```bash
|
|
28
|
+
npm run build
|
|
29
|
+
```
|
|
30
|
+
This ensures the output in `dist/` is compiled with standard `export` and `import` syntax instead of CommonJS `require`/`exports`, preventing `"exports is not defined"` errors in Vite SSR environments.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 3. Dev Server Proxy Routing
|
|
35
|
+
|
|
36
|
+
In Vite environments using Express API middlewares, Vite must be explicitly instructed to forward the CMS endpoints to the Express instance.
|
|
37
|
+
|
|
38
|
+
Update the API routing middleware check in `vite.config.ts` to intercept `/api/cms`:
|
|
39
|
+
```typescript
|
|
40
|
+
if (req.url && (
|
|
41
|
+
req.url.startsWith('/api/payments') ||
|
|
42
|
+
req.url.startsWith('/api/scanner') ||
|
|
43
|
+
req.url.startsWith('/api/cms') // Ensure this is captured
|
|
44
|
+
)) {
|
|
45
|
+
// Forward to Express server module...
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
Failing to include this causes Vite to fall back to serving the SPA's `index.html` (returning `<!doctype html...`), resulting in JSON parse errors.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 4. API Response Mapping Guardrails
|
|
53
|
+
|
|
54
|
+
The SDK returns templates matching Brevo's naming conventions, returning properties `templateId` and `templateName`.
|
|
55
|
+
When listing and selecting templates in your React dashboard UI, ensure you map these keys to local frontend properties:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const handleSelectTemplate = async (template) => {
|
|
59
|
+
const res = await fetch(`/api/cms/templates/${template.id}`);
|
|
60
|
+
const json = await res.json();
|
|
61
|
+
if (json.success) {
|
|
62
|
+
const t = json.data;
|
|
63
|
+
setEditingTemplate({
|
|
64
|
+
id: t.templateId, // Map templateId -> id
|
|
65
|
+
name: t.templateName, // Map templateName -> name
|
|
66
|
+
subject: t.subject,
|
|
67
|
+
htmlContent: t.htmlContent,
|
|
68
|
+
isActive: t.isActive,
|
|
69
|
+
eventName: t.eventName,
|
|
70
|
+
sender: t.sender,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 5. Event Trigger Implementation
|
|
79
|
+
|
|
80
|
+
Trigger outbound event-driven transactional mailings by passing a recipient email and variables context:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { cmsService } from './server/cms.js';
|
|
84
|
+
|
|
85
|
+
// Inside Checkout / Payment Success Handlers:
|
|
86
|
+
await cmsService.sendEventEmail('order.completed', email, {
|
|
87
|
+
customerName: profile.name,
|
|
88
|
+
orderId: order.id,
|
|
89
|
+
totalAmount: order.total,
|
|
90
|
+
currency: order.currency,
|
|
91
|
+
itemsCount: order.items.length,
|
|
92
|
+
appUrl: 'https://yourdomain.com'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Inside User Registration Handlers:
|
|
96
|
+
await cmsService.sendEventEmail('user.welcome', email, {
|
|
97
|
+
customerName: profile.name,
|
|
98
|
+
signupDate: new Date().toLocaleDateString()
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
The SDK runs outbound event-check safety checks. If the template is unassigned, missing, or inactive (`isActive` is false), the send is safely ignored without breaking transaction flows.
|
package/README.md
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# Brevo CMS Email Bootstrap SDK
|
|
2
|
+
|
|
3
|
+
A modular, lightweight, and robust Node.js SDK to synchronize and bootstrap Brevo email templates directly with local application backend events.
|
|
4
|
+
|
|
5
|
+
It provides an out-of-the-box system that syncs metadata from Brevo SMTP templates, saves target-event routing parameters in a local mapping database, runs Just-In-Time (JIT) sync, enforces visual guardrails, and exports plug-and-play Express controllers.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## How to Install
|
|
10
|
+
|
|
11
|
+
### 1. Install the SDK
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @fufulog/brevomorphic-cms-sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### 2. Set Environment Variables
|
|
18
|
+
|
|
19
|
+
Copy the example env and fill in your credentials:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cp node_modules/@fufulog/brevomorphic-cms-sdk/env.example .env
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Required variables:
|
|
26
|
+
|
|
27
|
+
| Variable | Description |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `BREVO_API_KEY` | Your Brevo API key (`xkeysib-...`) |
|
|
30
|
+
| `DEFAULT_SENDER_EMAIL` | Verified sender email address |
|
|
31
|
+
| `DB_TYPE` | `postgres`, `mysql`, or `firestore` |
|
|
32
|
+
| `DATABASE_URL` | Connection string (SQL) or Firebase credentials (Firestore) |
|
|
33
|
+
|
|
34
|
+
### 3. Create the Mapping Table
|
|
35
|
+
|
|
36
|
+
Run the appropriate schema migration for your database (see [Database Setup](#1-database-setup) below).
|
|
37
|
+
|
|
38
|
+
### 4. Bootstrap in Your App
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
import { EmailTemplateService, EmailTemplateController } from '@fufulog/brevomorphic-cms-sdk';
|
|
42
|
+
import express from 'express';
|
|
43
|
+
|
|
44
|
+
const emailService = new EmailTemplateService({
|
|
45
|
+
brevoApiKey: process.env.BREVO_API_KEY,
|
|
46
|
+
defaultSender: { email: process.env.DEFAULT_SENDER_EMAIL },
|
|
47
|
+
dbType: 'postgres', // or 'mysql' | 'firestore'
|
|
48
|
+
dbClient: yourDbClient,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const app = express();
|
|
52
|
+
app.use(express.json());
|
|
53
|
+
app.use('/api/cms', new EmailTemplateController(emailService).getRouter());
|
|
54
|
+
|
|
55
|
+
app.listen(3000);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 5. (Optional) Add the WYSIWYG Editor Component
|
|
59
|
+
|
|
60
|
+
The SDK ships drop-in WYSIWYG editor components for **Svelte** and **React**. Copy the component files from the SDK into your frontend project:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Svelte
|
|
64
|
+
cp node_modules/@fufulog/brevomorphic-cms-sdk/svelte/BrevoWysiwyg.svelte src/components/
|
|
65
|
+
|
|
66
|
+
# React
|
|
67
|
+
cp node_modules/@fufulog/brevomorphic-cms-sdk/react/BrevoWysiwyg.tsx src/components/
|
|
68
|
+
cp node_modules/@fufulog/brevomorphic-cms-sdk/react/BrevoWysiwyg.css src/components/
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
See [WYSIWYG Editor Components](#6-wysiwyg-editor-components) for full usage details.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Features
|
|
76
|
+
|
|
77
|
+
- **JIT Local Mapping Engine:** Automatic initialization of local routing templates if a new template is created directly on Brevo.
|
|
78
|
+
- **Outbound Send Guardrail:** Built-in safeguards that silently ignore disabled templates to protect transaction processing systems.
|
|
79
|
+
- **Multi-Database Support:** Native integration with **PostgreSQL**, **MySQL**, and **Google Cloud Firestore NoSQL** with zero code changes.
|
|
80
|
+
- **Plug-and-Play Routing:** Ready-to-mount Express Router exposing CRUD, activation toggles, verified sender lookups, and webhook routes.
|
|
81
|
+
- **WYSIWYG Editor Components:** Bundled Svelte and React editor components with source toggle, formatting toolbar, and Brevo variable injection.
|
|
82
|
+
- **Type Safety:** Full TypeScript declarations (`.d.ts`) included out of the box.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 1. Database Setup
|
|
87
|
+
|
|
88
|
+
### SQL (PostgreSQL & MySQL)
|
|
89
|
+
Create the mapping ledger table using the appropriate schema block. By default, the SDK looks for the `email_event_templates` table.
|
|
90
|
+
|
|
91
|
+
#### **PostgreSQL**
|
|
92
|
+
```sql
|
|
93
|
+
CREATE TABLE email_event_templates (
|
|
94
|
+
id SERIAL PRIMARY KEY,
|
|
95
|
+
template_id INT NOT NULL UNIQUE, -- Bridges to Brevo template ID
|
|
96
|
+
event_name VARCHAR(255) DEFAULT '', -- Links backend trigger key (e.g., ticket.preapproved)
|
|
97
|
+
is_active BOOLEAN DEFAULT false, -- Master toggle for active sends
|
|
98
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
99
|
+
);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### **MySQL**
|
|
103
|
+
```sql
|
|
104
|
+
CREATE TABLE email_event_templates (
|
|
105
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
106
|
+
template_id INT NOT NULL UNIQUE,
|
|
107
|
+
event_name VARCHAR(255) DEFAULT '',
|
|
108
|
+
is_active TINYINT(1) DEFAULT 0,
|
|
109
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
110
|
+
);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### NoSQL (Firestore)
|
|
114
|
+
No collection schema is required. Simply create a document collection named `email_event_templates` (customizable) in your Firestore database. The SDK will automatically structure documents with:
|
|
115
|
+
- **Document ID:** Stringified template ID (e.g. `"42"`)
|
|
116
|
+
- **Fields:**
|
|
117
|
+
- `template_id`: `number`
|
|
118
|
+
- `event_name`: `string`
|
|
119
|
+
- `is_active`: `boolean`
|
|
120
|
+
- `updated_at`: `timestamp`
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 2. Bootstrapping the SDK
|
|
125
|
+
|
|
126
|
+
To bootstrap the SDK in your application, instantiate `EmailTemplateService` by supplying a database driver instance (Postgres Pool, MySQL connection, or Firestore client instance) and the Brevo configuration.
|
|
127
|
+
|
|
128
|
+
### **Recipe A: PostgreSQL (using `pg` Pool)**
|
|
129
|
+
```javascript
|
|
130
|
+
import pg from 'pg';
|
|
131
|
+
import { EmailTemplateService } from '@fufulog/brevomorphic-cms-sdk';
|
|
132
|
+
|
|
133
|
+
const pgPool = new pg.Pool({
|
|
134
|
+
connectionString: process.env.DATABASE_URL
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Configure the SDK using a wrapper matching { query: (sql, params) => Promise<any> }
|
|
138
|
+
const emailService = new EmailTemplateService({
|
|
139
|
+
brevoApiKey: process.env.BREVO_API_KEY,
|
|
140
|
+
defaultSender: {
|
|
141
|
+
name: "Billing Desk",
|
|
142
|
+
email: "billing@yourdomain.com"
|
|
143
|
+
},
|
|
144
|
+
dbType: 'postgres',
|
|
145
|
+
dbClient: pgPool, // Standard pg Pool works natively
|
|
146
|
+
tableNameOrCollection: 'email_event_templates'
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### **Recipe B: MySQL (using `mysql2/promise` Pool)**
|
|
151
|
+
```javascript
|
|
152
|
+
import mysql from 'mysql2/promise';
|
|
153
|
+
import { EmailTemplateService } from '@fufulog/brevomorphic-cms-sdk';
|
|
154
|
+
|
|
155
|
+
const mysqlPool = await mysql.createPool({
|
|
156
|
+
host: process.env.DB_HOST,
|
|
157
|
+
user: process.env.DB_USER,
|
|
158
|
+
password: process.env.DB_PASSWORD,
|
|
159
|
+
database: process.env.DB_NAME
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const emailService = new EmailTemplateService({
|
|
163
|
+
brevoApiKey: process.env.BREVO_API_KEY,
|
|
164
|
+
defaultSender: {
|
|
165
|
+
name: "Customer Support",
|
|
166
|
+
email: "support@yourdomain.com"
|
|
167
|
+
},
|
|
168
|
+
dbType: 'mysql',
|
|
169
|
+
dbClient: mysqlPool, // Standard mysql2 promise pool works natively
|
|
170
|
+
tableNameOrCollection: 'email_event_templates'
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### **Recipe C: Firestore NoSQL (using `firebase-admin/firestore`)**
|
|
175
|
+
```javascript
|
|
176
|
+
import { initializeApp, cert } from 'firebase-admin/app';
|
|
177
|
+
import { getFirestore } from 'firebase-admin/firestore';
|
|
178
|
+
import { EmailTemplateService } from '@fufulog/brevomorphic-cms-sdk';
|
|
179
|
+
|
|
180
|
+
// Initialize Firebase SDK
|
|
181
|
+
initializeApp({
|
|
182
|
+
credential: cert(JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_JSON))
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const firestoreDb = getFirestore();
|
|
186
|
+
|
|
187
|
+
const emailService = new EmailTemplateService({
|
|
188
|
+
brevoApiKey: process.env.BREVO_API_KEY,
|
|
189
|
+
defaultSender: {
|
|
190
|
+
name: "System Gateway",
|
|
191
|
+
email: "noreply@yourdomain.com"
|
|
192
|
+
},
|
|
193
|
+
dbType: 'firestore',
|
|
194
|
+
dbClient: firestoreDb, // Standard Firestore client works natively
|
|
195
|
+
tableNameOrCollection: 'email_event_templates'
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 3. Mounting the Routing Layer (Express Server)
|
|
202
|
+
|
|
203
|
+
Instantiate `EmailTemplateController` by passing your bootstrapped `EmailTemplateService` and mounting the routes directly inside your Express router.
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
import express from 'express';
|
|
207
|
+
import { EmailTemplateController } from '@fufulog/brevomorphic-cms-sdk';
|
|
208
|
+
|
|
209
|
+
const app = express();
|
|
210
|
+
app.use(express.json());
|
|
211
|
+
|
|
212
|
+
// Create and mount CMS routes
|
|
213
|
+
const emailController = new EmailTemplateController(emailService);
|
|
214
|
+
app.use('/api/cms', emailController.getRouter());
|
|
215
|
+
|
|
216
|
+
// Start Server
|
|
217
|
+
app.listen(3000, () => {
|
|
218
|
+
console.log('🚀 Brevo CMS mounting on http://localhost:3000/api/cms');
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### **Exposed Routes**
|
|
223
|
+
| Method | Route | Description |
|
|
224
|
+
| --- | --- | --- |
|
|
225
|
+
| **GET** | `/api/cms/templates` | Lists templates with combined remote + local database JIT synchronization. |
|
|
226
|
+
| **GET** | `/api/cms/templates/:id` | Returns visual parameters, subject, HTML code block, and links for a specific template. |
|
|
227
|
+
| **POST** | `/api/cms/templates` | Creates a new draft template on Brevo & hooks up blank local DB mappings. |
|
|
228
|
+
| **PUT** | `/api/cms/templates/:id` | Updates name, subject, HTML markup body, and local target event assignment. |
|
|
229
|
+
| **POST** | `/api/cms/templates/:id/toggle` | Activates/deactivates template remotely on Brevo and locally inside DB mapping. |
|
|
230
|
+
| **GET** | `/api/cms/senders` | Pulls verified sender dropdown configurations from Brevo. |
|
|
231
|
+
| **POST** | `/api/cms/send` | Triggers test sends for a specific mapping. |
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 4. Operational Workflows (Outbound Sends)
|
|
236
|
+
|
|
237
|
+
In your application code, whenever a business operation completes (e.g. user welcome, ticket pre-approval), call the SDK directly to send an event email.
|
|
238
|
+
|
|
239
|
+
The SDK automatically runs event guardrails: if no active template is associated with the event, or if it has been deactivated on the dashboard, it is ignored safely.
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
// Welcome Event Trigger
|
|
243
|
+
app.post('/api/users/signup', async (req, res) => {
|
|
244
|
+
const newUser = await createUser(req.body);
|
|
245
|
+
|
|
246
|
+
// Trigger event-driven outbound send.
|
|
247
|
+
// If template is inactive (is_active = false) or event mapping doesn't exist, it skips it.
|
|
248
|
+
const outcome = await emailService.sendEventEmail('user.welcome', newUser.email, {
|
|
249
|
+
customer_name: newUser.name,
|
|
250
|
+
signup_date: new Date().toLocaleDateString()
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
console.log(outcome.message); // Logs status outcome safely
|
|
254
|
+
res.status(201).json({ success: true, user: newUser });
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 5. Visual Form Validation Guards
|
|
261
|
+
|
|
262
|
+
When calling `emailService.updateTemplate(id, data)` or hitting `PUT /api/cms/templates/:id`, the SDK automatically protects Brevo's API by enforcing boundary conditions:
|
|
263
|
+
- **Blank Names:** Rejected. Form fields require unique template name keys.
|
|
264
|
+
- **Short HTML:** Brevo servers reject payloads with missing layouts. The SDK validates that raw HTML blocks must be longer than 10 characters before transmitting.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 6. WYSIWYG Editor Components
|
|
269
|
+
|
|
270
|
+
The SDK ships drop-in WYSIWYG editor components for **Svelte** and **React**. These are zero-dependency source files you copy into your frontend project — no extra `npm install` required.
|
|
271
|
+
|
|
272
|
+
### Key Features
|
|
273
|
+
|
|
274
|
+
- **Formatting Toolbar:** Bold, Italic, Underline, Strikethrough, Headings, Lists, Alignment, Links
|
|
275
|
+
- **Source Toggle:** Switch between rich-text and raw HTML editing
|
|
276
|
+
- **Variable Injector:** Insert Brevo Twig expressions (`{{ params.name }}`) at cursor position
|
|
277
|
+
- **Twig Bracket Protection:** Does **not** escape `{`, `}`, or `%` — safe for Brevo's template engine
|
|
278
|
+
|
|
279
|
+
### Svelte Usage
|
|
280
|
+
|
|
281
|
+
```svelte
|
|
282
|
+
<script>
|
|
283
|
+
import BrevoWysiwyg from './BrevoWysiwyg.svelte';
|
|
284
|
+
|
|
285
|
+
let htmlContent = '';
|
|
286
|
+
|
|
287
|
+
const variables = [
|
|
288
|
+
{ label: 'Customer Name', key: 'params.name' },
|
|
289
|
+
{ label: 'Ticket Code', key: 'params.ticket_code' },
|
|
290
|
+
{ label: 'Signup Date', key: 'params.signup_date' }
|
|
291
|
+
];
|
|
292
|
+
</script>
|
|
293
|
+
|
|
294
|
+
<BrevoWysiwyg
|
|
295
|
+
bind:value={htmlContent}
|
|
296
|
+
{variables}
|
|
297
|
+
placeholder="Design your email layout..."
|
|
298
|
+
on:change={(e) => console.log('HTML changed:', e.detail)}
|
|
299
|
+
/>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### React Usage
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
import { useState } from 'react';
|
|
306
|
+
import BrevoWysiwyg from './BrevoWysiwyg';
|
|
307
|
+
|
|
308
|
+
function TemplateEditor() {
|
|
309
|
+
const [htmlContent, setHtmlContent] = useState('');
|
|
310
|
+
|
|
311
|
+
const variables = [
|
|
312
|
+
{ label: 'Customer Name', key: 'params.name' },
|
|
313
|
+
{ label: 'Ticket Code', key: 'params.ticket_code' },
|
|
314
|
+
{ label: 'Signup Date', key: 'params.signup_date' }
|
|
315
|
+
];
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<BrevoWysiwyg
|
|
319
|
+
value={htmlContent}
|
|
320
|
+
onChange={setHtmlContent}
|
|
321
|
+
variables={variables}
|
|
322
|
+
placeholder="Design your email layout..."
|
|
323
|
+
/>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Props
|
|
329
|
+
|
|
330
|
+
| Prop | Type | Default | Description |
|
|
331
|
+
| --- | --- | --- | --- |
|
|
332
|
+
| `value` | `string` | `''` | Raw HTML content (two-way bind in Svelte, controlled in React) |
|
|
333
|
+
| `onChange` | `(html: string) => void` | — | Callback when content changes (React only) |
|
|
334
|
+
| `variables` | `{ label: string, key: string }[]` | `[]` | Available Brevo template variables |
|
|
335
|
+
| `placeholder` | `string` | `'Start designing...'` | Placeholder text |
|
|
336
|
+
| `disabled` | `boolean` | `false` | Disables editing |
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { BrevoTemplate, BrevoSender } from './types.js';
|
|
2
|
+
export declare class BrevoClient {
|
|
3
|
+
private client;
|
|
4
|
+
constructor(apiKey: string);
|
|
5
|
+
/**
|
|
6
|
+
* Fetch all templates from Brevo.
|
|
7
|
+
* By default, limits templates but we can fetch them all.
|
|
8
|
+
*/
|
|
9
|
+
getTemplates(limit?: number, offset?: number): Promise<BrevoTemplate[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Fetch a single template by its Brevo ID
|
|
12
|
+
*/
|
|
13
|
+
getTemplate(id: number): Promise<BrevoTemplate>;
|
|
14
|
+
/**
|
|
15
|
+
* Create a new SMTP template on Brevo
|
|
16
|
+
*/
|
|
17
|
+
createTemplate(params: {
|
|
18
|
+
templateName: string;
|
|
19
|
+
subject: string;
|
|
20
|
+
sender: {
|
|
21
|
+
name?: string;
|
|
22
|
+
email: string;
|
|
23
|
+
};
|
|
24
|
+
htmlContent: string;
|
|
25
|
+
isActive?: boolean;
|
|
26
|
+
}): Promise<number>;
|
|
27
|
+
/**
|
|
28
|
+
* Update an SMTP template on Brevo
|
|
29
|
+
*/
|
|
30
|
+
updateTemplate(id: number, params: {
|
|
31
|
+
templateName: string;
|
|
32
|
+
subject: string;
|
|
33
|
+
sender: {
|
|
34
|
+
name?: string;
|
|
35
|
+
email: string;
|
|
36
|
+
id?: number;
|
|
37
|
+
};
|
|
38
|
+
htmlContent: string;
|
|
39
|
+
isActive: boolean;
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Get all active and verified sender profiles from Brevo
|
|
43
|
+
*/
|
|
44
|
+
getSenders(): Promise<BrevoSender[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Send a transactional template email via Brevo SMTP API
|
|
47
|
+
*/
|
|
48
|
+
sendEmail(params: {
|
|
49
|
+
templateId: number;
|
|
50
|
+
to: string;
|
|
51
|
+
variables?: Record<string, any>;
|
|
52
|
+
}): Promise<void>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
export class BrevoClient {
|
|
3
|
+
client;
|
|
4
|
+
constructor(apiKey) {
|
|
5
|
+
if (!apiKey) {
|
|
6
|
+
throw new Error('Brevo API key is required');
|
|
7
|
+
}
|
|
8
|
+
this.client = axios.create({
|
|
9
|
+
baseURL: 'https://api.brevo.com/v3',
|
|
10
|
+
headers: {
|
|
11
|
+
'api-key': apiKey,
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
'Accept': 'application/json',
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Fetch all templates from Brevo.
|
|
19
|
+
* By default, limits templates but we can fetch them all.
|
|
20
|
+
*/
|
|
21
|
+
async getTemplates(limit = 100, offset = 0) {
|
|
22
|
+
try {
|
|
23
|
+
const response = await this.client.get(`/smtp/templates?limit=${limit}&offset=${offset}`);
|
|
24
|
+
const templates = response.data.templates || [];
|
|
25
|
+
return templates.map((t) => ({
|
|
26
|
+
id: t.id,
|
|
27
|
+
name: t.name,
|
|
28
|
+
subject: t.subject,
|
|
29
|
+
isActive: t.isActive,
|
|
30
|
+
htmlContent: t.htmlContent,
|
|
31
|
+
sender: t.sender ? { id: t.sender.id, name: t.sender.name, email: t.sender.email } : undefined,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const msg = error.response?.data?.message || error.message;
|
|
36
|
+
throw new Error(`Failed to fetch templates from Brevo: ${msg}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Fetch a single template by its Brevo ID
|
|
41
|
+
*/
|
|
42
|
+
async getTemplate(id) {
|
|
43
|
+
try {
|
|
44
|
+
const response = await this.client.get(`/smtp/templates/${id}`);
|
|
45
|
+
const t = response.data;
|
|
46
|
+
return {
|
|
47
|
+
id: t.id,
|
|
48
|
+
name: t.name,
|
|
49
|
+
subject: t.subject,
|
|
50
|
+
isActive: t.isActive,
|
|
51
|
+
htmlContent: t.htmlContent,
|
|
52
|
+
sender: t.sender ? { id: t.sender.id, name: t.sender.name, email: t.sender.email } : undefined,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const msg = error.response?.data?.message || error.message;
|
|
57
|
+
throw new Error(`Failed to fetch template #${id} from Brevo: ${msg}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create a new SMTP template on Brevo
|
|
62
|
+
*/
|
|
63
|
+
async createTemplate(params) {
|
|
64
|
+
try {
|
|
65
|
+
const response = await this.client.post('/smtp/templates', {
|
|
66
|
+
templateName: params.templateName,
|
|
67
|
+
subject: params.subject,
|
|
68
|
+
sender: params.sender,
|
|
69
|
+
htmlContent: params.htmlContent,
|
|
70
|
+
isActive: params.isActive ?? false,
|
|
71
|
+
});
|
|
72
|
+
return response.data.id;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const msg = error.response?.data?.message || error.message;
|
|
76
|
+
throw new Error(`Failed to create template on Brevo: ${msg}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Update an SMTP template on Brevo
|
|
81
|
+
*/
|
|
82
|
+
async updateTemplate(id, params) {
|
|
83
|
+
try {
|
|
84
|
+
// Brevo PUT updates require sending sender as object { name, email } (or sender ID)
|
|
85
|
+
const payload = {
|
|
86
|
+
templateName: params.templateName,
|
|
87
|
+
subject: params.subject,
|
|
88
|
+
htmlContent: params.htmlContent,
|
|
89
|
+
isActive: params.isActive,
|
|
90
|
+
};
|
|
91
|
+
if (params.sender) {
|
|
92
|
+
payload.sender = {
|
|
93
|
+
email: params.sender.email,
|
|
94
|
+
};
|
|
95
|
+
if (params.sender.name) {
|
|
96
|
+
payload.sender.name = params.sender.name;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
await this.client.put(`/smtp/templates/${id}`, payload);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
const msg = error.response?.data?.message || error.message;
|
|
103
|
+
throw new Error(`Failed to update template #${id} on Brevo: ${msg}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get all active and verified sender profiles from Brevo
|
|
108
|
+
*/
|
|
109
|
+
async getSenders() {
|
|
110
|
+
try {
|
|
111
|
+
const response = await this.client.get('/senders');
|
|
112
|
+
const senders = response.data.senders || [];
|
|
113
|
+
return senders.map((s) => ({
|
|
114
|
+
id: s.id,
|
|
115
|
+
name: s.name,
|
|
116
|
+
email: s.email,
|
|
117
|
+
active: s.active,
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const msg = error.response?.data?.message || error.message;
|
|
122
|
+
throw new Error(`Failed to fetch senders from Brevo: ${msg}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Send a transactional template email via Brevo SMTP API
|
|
127
|
+
*/
|
|
128
|
+
async sendEmail(params) {
|
|
129
|
+
try {
|
|
130
|
+
await this.client.post('/smtp/email', {
|
|
131
|
+
templateId: params.templateId,
|
|
132
|
+
to: [{ email: params.to }],
|
|
133
|
+
params: params.variables || {},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const msg = error.response?.data?.message || error.message;
|
|
138
|
+
throw new Error(`Failed to send event email via Brevo: ${msg}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Request, Response, Router } from 'express';
|
|
2
|
+
import { EmailTemplateService } from './service.js';
|
|
3
|
+
export declare class EmailTemplateController {
|
|
4
|
+
private service;
|
|
5
|
+
constructor(service: EmailTemplateService);
|
|
6
|
+
/**
|
|
7
|
+
* Generates a fully configured Express Router.
|
|
8
|
+
* Can be directly mounted into any Express app: `app.use('/api/cms', controller.getRouter())`
|
|
9
|
+
*/
|
|
10
|
+
getRouter(): Router;
|
|
11
|
+
/**
|
|
12
|
+
* GET /templates
|
|
13
|
+
* Lists all templates synchronized from Brevo and mapped locally.
|
|
14
|
+
*/
|
|
15
|
+
listTemplates(req: Request, res: Response): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* GET /templates/:id
|
|
18
|
+
* Fetches a detailed template (including HTML body content) by Brevo ID.
|
|
19
|
+
*/
|
|
20
|
+
getTemplate(req: Request, res: Response): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* POST /templates
|
|
23
|
+
* Instantiates a new draft template on Brevo and registers local mapping.
|
|
24
|
+
*/
|
|
25
|
+
createTemplate(req: Request, res: Response): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* PUT /templates/:id
|
|
28
|
+
* Validates and updates a template configuration both on Brevo and the local DB.
|
|
29
|
+
*/
|
|
30
|
+
updateTemplate(req: Request, res: Response): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* POST /templates/:id/toggle
|
|
33
|
+
* Flips active/inactive status across Brevo and DB.
|
|
34
|
+
*/
|
|
35
|
+
toggleTemplate(req: Request, res: Response): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* GET /senders
|
|
38
|
+
* Lists verified sender profiles from Brevo.
|
|
39
|
+
*/
|
|
40
|
+
listSenders(req: Request, res: Response): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* POST /send
|
|
43
|
+
* Triggers an email send for a given backend event.
|
|
44
|
+
*/
|
|
45
|
+
sendEventEmail(req: Request, res: Response): Promise<void>;
|
|
46
|
+
}
|