@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 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
+ }