@astralibx/email-rule-engine 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -1,192 +1,192 @@
1
- # @astralibx/email-rule-engine
2
-
3
- Rule-based email automation engine with MJML + Handlebars templates, per-user throttling, and Redis distributed locking.
4
-
5
- ## What It Does
6
-
7
- - **Templates**: MJML + Handlebars email templates with CRUD, validation, preview, and variable extraction
8
- - **Rules**: Condition-based rules that target users and send templated emails
9
- - **Throttling**: Per-user daily/weekly limits with configurable minimum gap between emails
10
- - **Distributed Locking**: Redis-based locks prevent concurrent rule runs
11
- - **Run History**: Detailed logging of every rule execution with per-rule stats
12
- - **Draft Workflow**: Supports draft → approve → send pipeline via adapter
13
-
14
- ## Quick Start
15
-
16
- ```bash
17
- npm install @astralibx/email-rule-engine
18
- ```
19
-
20
- ```typescript
21
- import { createEmailRuleEngine } from '@astralibx/email-rule-engine';
22
- import mongoose from 'mongoose';
23
- import Redis from 'ioredis';
24
-
25
- const dbConnection = mongoose.createConnection('mongodb://localhost/myapp');
26
- const redis = new Redis();
27
-
28
- const engine = createEmailRuleEngine({
29
- db: { connection: dbConnection },
30
- redis: { connection: redis, keyPrefix: 'myapp:' },
31
-
32
- // Your platform values (used for enum validation in schemas)
33
- platforms: ['web', 'mobile', 'both'],
34
-
35
- adapters: {
36
- // Find users matching rule conditions — YOUR query logic
37
- queryUsers: async (target, limit) => {
38
- return db.collection('users')
39
- .find({ role: target.role })
40
- .limit(limit)
41
- .toArray();
42
- },
43
-
44
- // Transform user record into template variables
45
- resolveData: (user) => ({
46
- recipient: { name: user.name, email: user.email },
47
- platform: { name: 'My App', domain: 'myapp.com' }
48
- }),
49
-
50
- // Deliver the email (save as draft, send directly, queue — your choice)
51
- sendEmail: async (params) => {
52
- await myEmailService.send({
53
- to: params.identifierId,
54
- subject: params.subject,
55
- html: params.htmlBody,
56
- text: params.textBody
57
- });
58
- },
59
-
60
- // Select a sending account (return null to skip recipient)
61
- selectAgent: async (identifierId) => {
62
- const account = await myAccounts.findBest();
63
- return account ? { accountId: account.id } : null;
64
- },
65
-
66
- // Map email address to your identifier system
67
- findIdentifier: async (email) => {
68
- const id = await myIdentifiers.findByEmail(email);
69
- return id ? { id: id._id, contactId: id.contactId } : null;
70
- },
71
-
72
- // Optional: send test emails
73
- sendTestEmail: async (to, subject, html, text) => {
74
- await mySmtp.send({ to, subject, html, text });
75
- }
76
- },
77
-
78
- logger: console,
79
-
80
- options: {
81
- lockTTLMs: 30 * 60 * 1000, // 30 min lock timeout
82
- defaultMaxPerRun: 500 // max users per rule execution
83
- }
84
- });
85
-
86
- // Mount Express routes
87
- app.use('/api/email-rules', engine.routes);
88
-
89
- // Run rules via cron
90
- cron.schedule('0 9 * * *', () => engine.runner.runAllRules());
91
- ```
92
-
93
- ## API Routes
94
-
95
- Once mounted, the engine exposes these endpoints:
96
-
97
- ### Templates
98
- | Method | Path | Description |
99
- |--------|------|-------------|
100
- | GET | `/templates` | List all templates (filterable by category, audience, platform) |
101
- | POST | `/templates` | Create new template |
102
- | GET | `/templates/:id` | Get template by ID |
103
- | PUT | `/templates/:id` | Update template |
104
- | DELETE | `/templates/:id` | Delete template |
105
- | PATCH | `/templates/:id/toggle` | Toggle active/inactive |
106
- | POST | `/templates/:id/preview` | Render preview with sample data |
107
- | POST | `/templates/:id/test-email` | Send test email |
108
- | POST | `/templates/validate` | Validate MJML + Handlebars syntax |
109
- | POST | `/templates/preview` | Preview raw template (without saving) |
110
-
111
- ### Rules
112
- | Method | Path | Description |
113
- |--------|------|-------------|
114
- | GET | `/rules` | List all rules (with populated template info) |
115
- | POST | `/rules` | Create new rule |
116
- | GET | `/rules/:id` | Get rule by ID |
117
- | PATCH | `/rules/:id` | Update rule |
118
- | DELETE | `/rules/:id` | Delete (or disable if has send history) |
119
- | PATCH | `/rules/:id/toggle` | Toggle active/inactive |
120
- | POST | `/rules/:id/dry-run` | Count matching users without sending |
121
- | GET | `/rules/run-history` | Get execution history |
122
-
123
- ### Runner
124
- | Method | Path | Description |
125
- |--------|------|-------------|
126
- | POST | `/runner` | Trigger manual rule run (fire-and-forget) |
127
- | GET | `/runner/status` | Get latest run result |
128
-
129
- ### Settings
130
- | Method | Path | Description |
131
- |--------|------|-------------|
132
- | GET | `/settings/throttle` | Get throttle configuration |
133
- | PATCH | `/settings/throttle` | Update throttle limits |
134
-
135
- ## Config Reference
136
-
137
- ### `EmailRuleEngineConfig`
138
-
139
- | Field | Type | Required | Description |
140
- |-------|------|----------|-------------|
141
- | `db.connection` | `mongoose.Connection` | Yes | Mongoose connection for rule engine collections |
142
- | `db.collectionPrefix` | `string` | No | Prefix for collection names (e.g., `'myapp_'`) |
143
- | `redis.connection` | `Redis` | Yes | ioredis instance for distributed locking |
144
- | `redis.keyPrefix` | `string` | No | Prefix for Redis keys (e.g., `'myapp:'`) |
145
- | `platforms` | `string[]` | No | Valid platform values for schema validation |
146
- | `adapters` | object | Yes | Project-specific callbacks (see below) |
147
- | `logger` | `LogAdapter` | No | Logger with info/warn/error methods |
148
- | `options.lockTTLMs` | `number` | No | Lock timeout in ms (default: 30 min) |
149
- | `options.defaultMaxPerRun` | `number` | No | Default user limit per rule (default: 500) |
150
-
151
- ### Adapters
152
-
153
- | Adapter | Signature | Description |
154
- |---------|-----------|-------------|
155
- | `queryUsers` | `(target, limit) => Promise<Record[]>` | Find users matching rule conditions |
156
- | `resolveData` | `(user) => Record` | Map user to template variables |
157
- | `sendEmail` | `(params) => Promise<void>` | Deliver or draft the email |
158
- | `selectAgent` | `(identifierId) => Promise<{accountId} \| null>` | Pick sending account |
159
- | `findIdentifier` | `(email) => Promise<{id, contactId} \| null>` | Map email to identifier |
160
- | `sendTestEmail` | `(to, subject, html, text) => Promise<void>` | Optional: send test emails |
161
-
162
- ## Handlebars Helpers
163
-
164
- Built-in template helpers available in all templates:
165
-
166
- | Helper | Usage | Output |
167
- |--------|-------|--------|
168
- | `currency` | `{{currency 1500}}` | `₹1,500` |
169
- | `formatDate` | `{{formatDate date}}` | `13 Mar 2026` |
170
- | `capitalize` | `{{capitalize "hello"}}` | `Hello` |
171
- | `lowercase` | `{{lowercase "HELLO"}}` | `hello` |
172
- | `uppercase` | `{{uppercase "hello"}}` | `HELLO` |
173
- | `join` | `{{join items ", "}}` | `a, b, c` |
174
- | `pluralize` | `{{pluralize count "item" "items"}}` | `items` |
175
- | `eq/neq/gt/lt/gte/lte` | `{{#if (eq a b)}}` | Comparison |
176
- | `not` | `{{#if (not val)}}` | Negation |
177
-
178
- ## Collections Created
179
-
180
- The engine creates these MongoDB collections (prefixed if configured):
181
-
182
- | Collection | Purpose | TTL |
183
- |------------|---------|-----|
184
- | `email_templates` | MJML + Handlebars templates | - |
185
- | `email_rules` | Automation rules with conditions | - |
186
- | `email_rule_sends` | Per-user send tracking (dedup) | - |
187
- | `email_rule_run_logs` | Execution history | 90 days |
188
- | `email_throttle_config` | Throttle settings (singleton) | - |
189
-
190
- ## License
191
-
192
- MIT
1
+ # @astralibx/email-rule-engine
2
+
3
+ Rule-based email automation engine with MJML + Handlebars templates, per-user throttling, and Redis distributed locking.
4
+
5
+ ## What It Does
6
+
7
+ - **Templates**: MJML + Handlebars email templates with CRUD, validation, preview, and variable extraction
8
+ - **Rules**: Condition-based rules that target users and send templated emails
9
+ - **Throttling**: Per-user daily/weekly limits with configurable minimum gap between emails
10
+ - **Distributed Locking**: Redis-based locks prevent concurrent rule runs
11
+ - **Run History**: Detailed logging of every rule execution with per-rule stats
12
+ - **Draft Workflow**: Supports draft → approve → send pipeline via adapter
13
+
14
+ ## Quick Start
15
+
16
+ ```bash
17
+ npm install @astralibx/email-rule-engine
18
+ ```
19
+
20
+ ```typescript
21
+ import { createEmailRuleEngine } from '@astralibx/email-rule-engine';
22
+ import mongoose from 'mongoose';
23
+ import Redis from 'ioredis';
24
+
25
+ const dbConnection = mongoose.createConnection('mongodb://localhost/myapp');
26
+ const redis = new Redis();
27
+
28
+ const engine = createEmailRuleEngine({
29
+ db: { connection: dbConnection },
30
+ redis: { connection: redis, keyPrefix: 'myapp:' },
31
+
32
+ // Your platform values (used for enum validation in schemas)
33
+ platforms: ['web', 'mobile', 'both'],
34
+
35
+ adapters: {
36
+ // Find users matching rule conditions — YOUR query logic
37
+ queryUsers: async (target, limit) => {
38
+ return db.collection('users')
39
+ .find({ role: target.role })
40
+ .limit(limit)
41
+ .toArray();
42
+ },
43
+
44
+ // Transform user record into template variables
45
+ resolveData: (user) => ({
46
+ recipient: { name: user.name, email: user.email },
47
+ platform: { name: 'My App', domain: 'myapp.com' }
48
+ }),
49
+
50
+ // Deliver the email (save as draft, send directly, queue — your choice)
51
+ sendEmail: async (params) => {
52
+ await myEmailService.send({
53
+ to: params.identifierId,
54
+ subject: params.subject,
55
+ html: params.htmlBody,
56
+ text: params.textBody
57
+ });
58
+ },
59
+
60
+ // Select a sending account (return null to skip recipient)
61
+ selectAgent: async (identifierId) => {
62
+ const account = await myAccounts.findBest();
63
+ return account ? { accountId: account.id } : null;
64
+ },
65
+
66
+ // Map email address to your identifier system
67
+ findIdentifier: async (email) => {
68
+ const id = await myIdentifiers.findByEmail(email);
69
+ return id ? { id: id._id, contactId: id.contactId } : null;
70
+ },
71
+
72
+ // Optional: send test emails
73
+ sendTestEmail: async (to, subject, html, text) => {
74
+ await mySmtp.send({ to, subject, html, text });
75
+ }
76
+ },
77
+
78
+ logger: console,
79
+
80
+ options: {
81
+ lockTTLMs: 30 * 60 * 1000, // 30 min lock timeout
82
+ defaultMaxPerRun: 500 // max users per rule execution
83
+ }
84
+ });
85
+
86
+ // Mount Express routes
87
+ app.use('/api/email-rules', engine.routes);
88
+
89
+ // Run rules via cron
90
+ cron.schedule('0 9 * * *', () => engine.runner.runAllRules());
91
+ ```
92
+
93
+ ## API Routes
94
+
95
+ Once mounted, the engine exposes these endpoints:
96
+
97
+ ### Templates
98
+ | Method | Path | Description |
99
+ |--------|------|-------------|
100
+ | GET | `/templates` | List all templates (filterable by category, audience, platform) |
101
+ | POST | `/templates` | Create new template |
102
+ | GET | `/templates/:id` | Get template by ID |
103
+ | PUT | `/templates/:id` | Update template |
104
+ | DELETE | `/templates/:id` | Delete template |
105
+ | PATCH | `/templates/:id/toggle` | Toggle active/inactive |
106
+ | POST | `/templates/:id/preview` | Render preview with sample data |
107
+ | POST | `/templates/:id/test-email` | Send test email |
108
+ | POST | `/templates/validate` | Validate MJML + Handlebars syntax |
109
+ | POST | `/templates/preview` | Preview raw template (without saving) |
110
+
111
+ ### Rules
112
+ | Method | Path | Description |
113
+ |--------|------|-------------|
114
+ | GET | `/rules` | List all rules (with populated template info) |
115
+ | POST | `/rules` | Create new rule |
116
+ | GET | `/rules/:id` | Get rule by ID |
117
+ | PATCH | `/rules/:id` | Update rule |
118
+ | DELETE | `/rules/:id` | Delete (or disable if has send history) |
119
+ | PATCH | `/rules/:id/toggle` | Toggle active/inactive |
120
+ | POST | `/rules/:id/dry-run` | Count matching users without sending |
121
+ | GET | `/rules/run-history` | Get execution history |
122
+
123
+ ### Runner
124
+ | Method | Path | Description |
125
+ |--------|------|-------------|
126
+ | POST | `/runner` | Trigger manual rule run (fire-and-forget) |
127
+ | GET | `/runner/status` | Get latest run result |
128
+
129
+ ### Settings
130
+ | Method | Path | Description |
131
+ |--------|------|-------------|
132
+ | GET | `/settings/throttle` | Get throttle configuration |
133
+ | PATCH | `/settings/throttle` | Update throttle limits |
134
+
135
+ ## Config Reference
136
+
137
+ ### `EmailRuleEngineConfig`
138
+
139
+ | Field | Type | Required | Description |
140
+ |-------|------|----------|-------------|
141
+ | `db.connection` | `mongoose.Connection` | Yes | Mongoose connection for rule engine collections |
142
+ | `db.collectionPrefix` | `string` | No | Prefix for collection names (e.g., `'myapp_'`) |
143
+ | `redis.connection` | `Redis` | Yes | ioredis instance for distributed locking |
144
+ | `redis.keyPrefix` | `string` | No | Prefix for Redis keys (e.g., `'myapp:'`) |
145
+ | `platforms` | `string[]` | No | Valid platform values for schema validation |
146
+ | `adapters` | object | Yes | Project-specific callbacks (see below) |
147
+ | `logger` | `LogAdapter` | No | Logger with info/warn/error methods |
148
+ | `options.lockTTLMs` | `number` | No | Lock timeout in ms (default: 30 min) |
149
+ | `options.defaultMaxPerRun` | `number` | No | Default user limit per rule (default: 500) |
150
+
151
+ ### Adapters
152
+
153
+ | Adapter | Signature | Description |
154
+ |---------|-----------|-------------|
155
+ | `queryUsers` | `(target, limit) => Promise<Record[]>` | Find users matching rule conditions |
156
+ | `resolveData` | `(user) => Record` | Map user to template variables |
157
+ | `sendEmail` | `(params) => Promise<void>` | Deliver or draft the email |
158
+ | `selectAgent` | `(identifierId) => Promise<{accountId} \| null>` | Pick sending account |
159
+ | `findIdentifier` | `(email) => Promise<{id, contactId} \| null>` | Map email to identifier |
160
+ | `sendTestEmail` | `(to, subject, html, text) => Promise<void>` | Optional: send test emails |
161
+
162
+ ## Handlebars Helpers
163
+
164
+ Built-in template helpers available in all templates:
165
+
166
+ | Helper | Usage | Output |
167
+ |--------|-------|--------|
168
+ | `currency` | `{{currency 1500}}` | `₹1,500` |
169
+ | `formatDate` | `{{formatDate date}}` | `13 Mar 2026` |
170
+ | `capitalize` | `{{capitalize "hello"}}` | `Hello` |
171
+ | `lowercase` | `{{lowercase "HELLO"}}` | `hello` |
172
+ | `uppercase` | `{{uppercase "hello"}}` | `HELLO` |
173
+ | `join` | `{{join items ", "}}` | `a, b, c` |
174
+ | `pluralize` | `{{pluralize count "item" "items"}}` | `items` |
175
+ | `eq/neq/gt/lt/gte/lte` | `{{#if (eq a b)}}` | Comparison |
176
+ | `not` | `{{#if (not val)}}` | Negation |
177
+
178
+ ## Collections Created
179
+
180
+ The engine creates these MongoDB collections (prefixed if configured):
181
+
182
+ | Collection | Purpose | TTL |
183
+ |------------|---------|-----|
184
+ | `email_templates` | MJML + Handlebars templates | - |
185
+ | `email_rules` | Automation rules with conditions | - |
186
+ | `email_rule_sends` | Per-user send tracking (dedup) | - |
187
+ | `email_rule_run_logs` | Execution history | 90 days |
188
+ | `email_throttle_config` | Throttle settings (singleton) | - |
189
+
190
+ ## License
191
+
192
+ MIT
@@ -7,21 +7,21 @@ exports.TemplateRenderService = void 0;
7
7
  const handlebars_1 = __importDefault(require("handlebars"));
8
8
  const mjml_1 = __importDefault(require("mjml"));
9
9
  const html_to_text_1 = require("html-to-text");
10
- const MJML_BASE_OPEN = `<mjml>
11
- <mj-head>
12
- <mj-attributes>
13
- <mj-all font-family="Arial, sans-serif" />
14
- <mj-text font-size="15px" color="#333333" line-height="1.6" />
15
- </mj-attributes>
16
- </mj-head>
17
- <mj-body background-color="#ffffff">
18
- <mj-section padding="20px">
19
- <mj-column>
10
+ const MJML_BASE_OPEN = `<mjml>
11
+ <mj-head>
12
+ <mj-attributes>
13
+ <mj-all font-family="Arial, sans-serif" />
14
+ <mj-text font-size="15px" color="#333333" line-height="1.6" />
15
+ </mj-attributes>
16
+ </mj-head>
17
+ <mj-body background-color="#ffffff">
18
+ <mj-section padding="20px">
19
+ <mj-column>
20
20
  <mj-text>`;
21
- const MJML_BASE_CLOSE = ` </mj-text>
22
- </mj-column>
23
- </mj-section>
24
- </mj-body>
21
+ const MJML_BASE_CLOSE = ` </mj-text>
22
+ </mj-column>
23
+ </mj-section>
24
+ </mj-body>
25
25
  </mjml>`;
26
26
  const DATE_FORMAT_OPTIONS = {
27
27
  day: 'numeric',
package/package.json CHANGED
@@ -1,49 +1,49 @@
1
- {
2
- "name": "@astralibx/email-rule-engine",
3
- "version": "1.0.0",
4
- "description": "Rule-based email automation engine with MJML + Handlebars templates, throttling, and distributed locking",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "files": [
8
- "dist"
9
- ],
10
- "scripts": {
11
- "build": "tsc",
12
- "test": "jest",
13
- "prepublishOnly": "npm run build",
14
- "clean": "rm -rf dist"
15
- },
16
- "keywords": [
17
- "email",
18
- "automation",
19
- "rule-engine",
20
- "mjml",
21
- "handlebars",
22
- "throttle"
23
- ],
24
- "license": "MIT",
25
- "peerDependencies": {
26
- "express": "^4.18.0 || ^5.0.0",
27
- "handlebars": "^4.7.0",
28
- "html-to-text": "^9.0.0",
29
- "ioredis": "^5.0.0",
30
- "mjml": "^4.0.0",
31
- "mongoose": "^7.0.0 || ^8.0.0"
32
- },
33
- "devDependencies": {
34
- "@types/jest": "^29.5.0",
35
- "@types/express": "^5.0.0",
36
- "@types/html-to-text": "^9.0.4",
37
- "@types/mjml": "^4.7.4",
38
- "@types/node": "^22.0.0",
39
- "express": "^5.0.0",
40
- "handlebars": "^4.7.8",
41
- "html-to-text": "^9.0.5",
42
- "ioredis": "^5.4.2",
43
- "mjml": "^4.15.3",
44
- "mongoose": "^8.12.1",
45
- "jest": "^29.7.0",
46
- "ts-jest": "^29.2.0",
47
- "typescript": "^5.8.2"
48
- }
49
- }
1
+ {
2
+ "name": "@astralibx/email-rule-engine",
3
+ "version": "1.0.1",
4
+ "description": "Rule-based email automation engine with MJML + Handlebars templates, throttling, and distributed locking",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "test": "jest",
13
+ "prepublishOnly": "npm run build",
14
+ "clean": "rm -rf dist"
15
+ },
16
+ "keywords": [
17
+ "email",
18
+ "automation",
19
+ "rule-engine",
20
+ "mjml",
21
+ "handlebars",
22
+ "throttle"
23
+ ],
24
+ "license": "MIT",
25
+ "peerDependencies": {
26
+ "express": "^4.18.0 || ^5.0.0",
27
+ "handlebars": "^4.7.0",
28
+ "html-to-text": "^9.0.0",
29
+ "ioredis": "^5.0.0",
30
+ "mjml": "^4.0.0",
31
+ "mongoose": "^7.0.0 || ^8.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/jest": "^29.5.0",
35
+ "@types/express": "^5.0.0",
36
+ "@types/html-to-text": "^9.0.4",
37
+ "@types/mjml": "^4.7.4",
38
+ "@types/node": "^22.0.0",
39
+ "express": "^5.0.0",
40
+ "handlebars": "^4.7.8",
41
+ "html-to-text": "^9.0.5",
42
+ "ioredis": "^5.4.2",
43
+ "mjml": "^4.15.3",
44
+ "mongoose": "^8.12.1",
45
+ "jest": "^29.7.0",
46
+ "ts-jest": "^29.2.0",
47
+ "typescript": "^5.8.2"
48
+ }
49
+ }