cds-error-outbox 1.0.1 → 1.0.2

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.
@@ -0,0 +1,29 @@
1
+ name: Test & Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ test-and-publish:
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Set up Node.js
15
+ uses: actions/setup-node@v4
16
+ with:
17
+ node-version: '22'
18
+ registry-url: 'https://registry.npmjs.org'
19
+
20
+ - name: Install dependencies
21
+ run: npm ci
22
+
23
+ - name: Run tests
24
+ run: npm test
25
+
26
+ - name: Publish to npm
27
+ run: npm publish --access public
28
+ env:
29
+ NODE_AUTH_TOKEN: ${{ secrets.npm_token }}
package/README.md CHANGED
@@ -1,311 +1,311 @@
1
- <div align="center">
2
-
3
- # CDS Error Outbox
4
-
5
- **Automatic error capture, deduplication & email alerting for SAP CAP applications.**
6
-
7
- <br>
8
-
9
- [![npm](https://img.shields.io/npm/v/cds-error-outbox?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/cds-error-outbox)
10
- [![SAP CAP](https://img.shields.io/badge/SAP%20CAP-plugin-009FDB?style=for-the-badge&logo=sap&logoColor=white)](https://cap.cloud.sap)
11
- [![MIT](https://img.shields.io/badge/license-MIT-22c55e?style=for-the-badge&logo=opensourceinitiative&logoColor=white)](./LICENSE)
12
- [![zero deps](https://img.shields.io/badge/deps-zero-6366f1?style=for-the-badge&logo=nodedotjs&logoColor=white)](#)
13
-
14
- [![Microsoft O365](https://img.shields.io/badge/Microsoft%20365-mail%20ready-0078D4?style=flat-square&logo=microsoft&logoColor=white)](#o365--microsoft-graph-api)
15
- [![SMTP](https://img.shields.io/badge/SMTP-nodemailer-EA4335?style=flat-square&logo=gmail&logoColor=white)](#smtp)
16
- [![SQLite](https://img.shields.io/badge/SQLite-supported-003B57?style=flat-square&logo=sqlite&logoColor=white)](#db-entity)
17
- [![SAP HANA](https://img.shields.io/badge/SAP%20HANA-supported-1B6B3A?style=flat-square&logo=sap&logoColor=white)](#db-entity)
18
- [![SAP BTP](https://img.shields.io/badge/SAP%20BTP-Cloud%20Foundry-0FAAFF?style=flat-square&logo=sap&logoColor=white)](#environment-variables)
19
-
20
- </div>
21
-
22
- ---
23
-
24
- **[Installation](#installation) · [Configuration](#configuration) · [Email Providers](#email-providers) · [How it works](#how-it-works) · [DB Entity](#db-entity) · [Environment variables](#environment-variables) · [Project structure](#project-structure)**
25
-
26
- ---
27
-
28
- ## Features
29
-
30
- - Hooks into **all CAP services** automatically — zero manual wiring required
31
- - Persists errors to a CDS-managed `error.outbox.Errors` entity
32
- - **Deduplicates** via `SHA-256(message + service + action)` — increments a counter instead of flooding the DB
33
- - Sends **batched HTML email reports** on a configurable interval
34
- - Pluggable providers: **O365 (Microsoft Graph)**, **SMTP (nodemailer)**, **Mock (dev/test)**
35
- - **Non-blocking** — fire-and-forget capture, the request pipeline is never delayed
36
- - **Resilient** — all internal failures are caught, logged, and swallowed; the app never crashes
37
-
38
- ---
39
-
40
- ## Installation
41
-
42
- ```bash
43
- npm install cds-error-outbox
44
- ```
45
-
46
- Because `package.json` declares `"cds": { "plugin": true }`, CAP automatically loads `index.js` at startup.
47
-
48
- > **Note:** Due to how CAP resolves symlinked local packages, you may need to explicitly require the plugin so it is loaded at startup.
49
- >
50
- > **Option A — with a custom `server.js`:**
51
- > ```js
52
- > require("cds-error-outbox"); // ← add as the very first line
53
- > // ... rest of your server.js
54
- > ```
55
- >
56
- > **Option B — without a custom `server.js`** (most common):
57
- > Add the require at the top of any service file, e.g. `srv/admin-service.js`:
58
- > ```js
59
- > require("cds-error-outbox"); // ← add at the top
60
- > // ... rest of your service
61
- > ```
62
-
63
- Then register the DB model in your project (e.g. in `db/schema.cds`):
64
-
65
- ```cds
66
- using from 'cds-error-outbox/db/model';
67
- ```
68
-
69
- And deploy:
70
-
71
- ```bash
72
- cds deploy --to sqlite # local development
73
- cds build # production (BTP, HANA)
74
- ```
75
-
76
- ---
77
-
78
- ## Configuration
79
-
80
- Add the following to your project's `package.json` under `cds.requires`, or to `.cdsrc.json`:
81
-
82
- ```json
83
- {
84
- "cds": {
85
- "requires": {
86
- "errorOutbox": {
87
- "enabled": true,
88
- "interval": 300000,
89
- "batchSize": 50,
90
- "dedup": {
91
- "enabled": true,
92
- "windowMinutes": 10
93
- },
94
- "mail": {
95
- "provider": "o365",
96
- "tenantId": "<YOUR_TENANT_ID>",
97
- "clientId": "<YOUR_CLIENT_ID>",
98
- "clientSecret": "<YOUR_CLIENT_SECRET>",
99
- "from": "errors@yourcompany.com",
100
- "to": "devops@yourcompany.com"
101
- }
102
- }
103
- }
104
- }
105
- }
106
- ```
107
-
108
- ### Configuration reference
109
-
110
- | Key | Type | Default | Description |
111
- | --------------------- | ----------- | -------- | -------------------------------------- |
112
- | `enabled` | boolean | `true` | Enable/disable the entire plugin |
113
- | `interval` | number (ms) | `300000` | Batch email job frequency |
114
- | `batchSize` | number | `50` | Max errors per email batch |
115
- | `dedup.enabled` | boolean | `true` | Enable hash-based deduplication |
116
- | `dedup.windowMinutes` | number | `10` | Rolling dedup window in minutes |
117
- | `mail.provider` | string | `'mock'` | `'o365'` \| `'smtp'` \| `'mock'` |
118
- | `mail.from` | string | `''` | Sender address |
119
- | `mail.to` | string | `''` | Recipient(s), comma-separated |
120
- | `mail.tenantId` | string | `''` | Azure AD tenant ID (O365 only) |
121
- | `mail.clientId` | string | `''` | Azure AD app client ID (O365 only) |
122
- | `mail.clientSecret` | string | `''` | Azure AD app client secret (O365 only) |
123
- | `mail.smtp.host` | string | `''` | SMTP host (SMTP only) |
124
- | `mail.smtp.port` | number | `587` | SMTP port (SMTP only) |
125
- | `mail.smtp.secure` | boolean | `false` | Use TLS (SMTP only) |
126
- | `mail.smtp.auth.user` | string | `''` | SMTP username (SMTP only) |
127
- | `mail.smtp.auth.pass` | string | `''` | SMTP password (SMTP only) |
128
-
129
- ---
130
-
131
- ## Email Providers
132
-
133
- ### `mock` _(default — development/testing)_
134
-
135
- Logs the email subject and metadata to the console. No external calls. Use this during local development.
136
-
137
- ### `o365` — Microsoft Graph API
138
-
139
- Uses the [Microsoft Graph `sendMail` API](https://learn.microsoft.com/en-us/graph/api/user-sendmail) with an OAuth 2.0 **client credentials** flow. No user login is required. Zero additional npm dependencies.
140
-
141
- #### O365 Setup
142
-
143
- 1. Go to [Azure Portal → App Registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps) and click **New registration**
144
- 2. Note the **Application (client) ID** and **Directory (tenant) ID**
145
- 3. Go to **Certificates & Secrets** → **New client secret** — note the secret **Value** (shown once)
146
- 4. Go to **API Permissions** → **Add a permission** → **Microsoft Graph** → **Application permissions**
147
- - Add: `Mail.Send`
148
- 5. Click **Grant admin consent** for your organisation
149
- 6. The `mail.from` address must be a **licensed Exchange Online mailbox** that the app registration has permission to send from
150
- 7. Configure `cds.requires.errorOutbox.mail`:
151
-
152
- ```json
153
- {
154
- "provider": "o365",
155
- "tenantId": "<Directory (tenant) ID>",
156
- "clientId": "<Application (client) ID>",
157
- "clientSecret": "<Client Secret value>",
158
- "from": "errors@yourcompany.com",
159
- "to": "devops@yourcompany.com"
160
- }
161
- ```
162
-
163
- > **Security:** Never commit `clientSecret` to source control.
164
- >
165
- > The O365 provider reads credentials from **env variables as a fallback** — values in `package.json` take priority, but any missing field is automatically picked up from the environment:
166
- >
167
- > ```bash
168
- > export CDS_REQUIRES_ERROROUTBOX_MAIL_TENANTID="xxxx"
169
- > export CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTID="xxxx"
170
- > export CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTSECRET="xxxx"
171
- > export CDS_REQUIRES_ERROROUTBOX_MAIL_FROM="errors@company.com"
172
- > export CDS_REQUIRES_ERROROUTBOX_MAIL_TO="devops@company.com"
173
- > ```
174
- >
175
- > On BTP / Cloud Foundry use `cf set-env <app> CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTSECRET "<value>"` instead of putting the secret in `manifest.yml`.
176
-
177
- ### `smtp`
178
-
179
- Requires `nodemailer` (optional peer dependency):
180
-
181
- ```bash
182
- npm install nodemailer
183
- ```
184
-
185
- ```json
186
- {
187
- "provider": "smtp",
188
- "from": "errors@yourcompany.com",
189
- "to": "devops@yourcompany.com",
190
- "smtp": {
191
- "host": "smtp.yourcompany.com",
192
- "port": 587,
193
- "secure": false,
194
- "auth": { "user": "smtp-user", "pass": "smtp-password" }
195
- }
196
- }
197
- ```
198
-
199
- ---
200
-
201
- ## How it works
202
-
203
- ```
204
- CAP service throws an error
205
-
206
-
207
- srv.handle() wrapper — fire-and-forget via setImmediate (non-blocking)
208
-
209
-
210
- SHA-256( message | service | action )
211
-
212
- ┌────┴────┐
213
- │ │
214
- duplicate? new?
215
- │ │
216
- ▼ ▼
217
- UPDATE INSERT
218
- count+1 count=1
219
- lastSeen firstSeen/lastSeen
220
-
221
-
222
- setInterval every `interval` ms
223
-
224
-
225
- SELECT sent=false LIMIT batchSize (oldest first)
226
-
227
-
228
- Format HTML (grouped by service)
229
-
230
-
231
- provider.send(...)
232
-
233
- ▼ (only on success)
234
- UPDATE sent=true WHERE ID IN [...]
235
- ```
236
-
237
- ---
238
-
239
- ## DB Entity
240
-
241
- The plugin automatically adds the following entity to your project's database schema:
242
-
243
- ```cds
244
- namespace error.outbox;
245
-
246
- entity Errors {
247
- key ID : UUID;
248
- hash : String(64);
249
- service : String;
250
- action : String;
251
- message : LargeString;
252
- stack : LargeString;
253
- count : Integer;
254
- firstSeen : Timestamp;
255
- lastSeen : Timestamp;
256
- sent : Boolean default false;
257
- }
258
- ```
259
-
260
- ---
261
-
262
- ## Environment variables
263
-
264
- CAP maps nested config paths to environment variables. You can override any value at runtime without changing `package.json`:
265
-
266
- ```bash
267
- CDS_REQUIRES_ERROROUTBOX_ENABLED=true
268
- CDS_REQUIRES_ERROROUTBOX_INTERVAL=60000
269
- CDS_REQUIRES_ERROROUTBOX_MAIL_PROVIDER=o365
270
- CDS_REQUIRES_ERROROUTBOX_MAIL_TENANTID=...
271
- CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTID=...
272
- CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTSECRET=...
273
- CDS_REQUIRES_ERROROUTBOX_MAIL_FROM=errors@yourcompany.com
274
- CDS_REQUIRES_ERROROUTBOX_MAIL_TO=devops@yourcompany.com
275
- ```
276
-
277
- ---
278
-
279
- ## Project structure
280
-
281
- ```
282
- cds-error-outbox/
283
- ├── package.json ← CAP plugin declaration (cds.plugin: true)
284
- ├── index.js ← Entry point — calls bootstrap.initialize()
285
-
286
- ├── config/
287
- │ └── defaults.js ← Default config values
288
-
289
- ├── lib/
290
- │ ├── bootstrap.js ← Init orchestration (cds lifecycle hooks)
291
- │ ├── config.js ← Deep merge + config loader (singleton)
292
- │ ├── interceptor.js ← srv.handle() wrapper (fire-and-forget)
293
- │ ├── dedup.js ← SHA-256 hash + DB upsert logic
294
- │ ├── scheduler.js ← Interval batch job
295
- │ └── formatter.js ← HTML email builder
296
-
297
- ├── providers/
298
- │ ├── index.js ← Provider factory
299
- │ ├── o365.js ← Microsoft Graph API (zero extra deps)
300
- │ ├── smtp.js ← nodemailer wrapper (optional peer dep)
301
- │ └── mock.js ← Console logger (dev/test)
302
-
303
- └── db/
304
- └── model.cds ← error.outbox.Errors entity
305
- ```
306
-
307
- ---
308
-
309
- ## License
310
-
311
- MIT
1
+ <div align="center">
2
+
3
+ # CDS Error Outbox
4
+
5
+ **Automatic error capture, deduplication & email alerting for SAP CAP applications.**
6
+
7
+ <br>
8
+
9
+ [![npm](https://img.shields.io/npm/v/cds-error-outbox?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/cds-error-outbox)
10
+ [![SAP CAP](https://img.shields.io/badge/SAP%20CAP-plugin-009FDB?style=for-the-badge&logo=sap&logoColor=white)](https://cap.cloud.sap)
11
+ [![MIT](https://img.shields.io/badge/license-MIT-22c55e?style=for-the-badge&logo=opensourceinitiative&logoColor=white)](./LICENSE)
12
+ [![zero deps](https://img.shields.io/badge/deps-zero-6366f1?style=for-the-badge&logo=nodedotjs&logoColor=white)](#)
13
+
14
+ [![Microsoft O365](https://img.shields.io/badge/Microsoft%20365-mail%20ready-0078D4?style=flat-square&logo=microsoft&logoColor=white)](#o365--microsoft-graph-api)
15
+ [![SMTP](https://img.shields.io/badge/SMTP-nodemailer-EA4335?style=flat-square&logo=gmail&logoColor=white)](#smtp)
16
+ [![SQLite](https://img.shields.io/badge/SQLite-supported-003B57?style=flat-square&logo=sqlite&logoColor=white)](#db-entity)
17
+ [![SAP HANA](https://img.shields.io/badge/SAP%20HANA-supported-1B6B3A?style=flat-square&logo=sap&logoColor=white)](#db-entity)
18
+ [![SAP BTP](https://img.shields.io/badge/SAP%20BTP-Cloud%20Foundry-0FAAFF?style=flat-square&logo=sap&logoColor=white)](#environment-variables)
19
+
20
+ </div>
21
+
22
+ ---
23
+
24
+ **[Installation](#installation) · [Configuration](#configuration) · [Email Providers](#email-providers) · [How it works](#how-it-works) · [DB Entity](#db-entity) · [Environment variables](#environment-variables) · [Project structure](#project-structure)**
25
+
26
+ ---
27
+
28
+ ## Features
29
+
30
+ - Hooks into **all CAP services** automatically — zero manual wiring required
31
+ - Persists errors to a CDS-managed `error.outbox.Errors` entity
32
+ - **Deduplicates** via `SHA-256(message + service + action)` — increments a counter instead of flooding the DB
33
+ - Sends **batched HTML email reports** on a configurable interval
34
+ - Pluggable providers: **O365 (Microsoft Graph)**, **SMTP (nodemailer)**, **Mock (dev/test)**
35
+ - **Non-blocking** — fire-and-forget capture, the request pipeline is never delayed
36
+ - **Resilient** — all internal failures are caught, logged, and swallowed; the app never crashes
37
+
38
+ ---
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ npm install cds-error-outbox
44
+ ```
45
+
46
+ Because `package.json` declares `"cds": { "plugin": true }`, CAP automatically loads `index.js` at startup.
47
+
48
+ > **Note:** Due to how CAP resolves symlinked local packages, you may need to explicitly require the plugin so it is loaded at startup.
49
+ >
50
+ > **Option A — with a custom `server.js`:**
51
+ > ```js
52
+ > require("cds-error-outbox"); // ← add as the very first line
53
+ > // ... rest of your server.js
54
+ > ```
55
+ >
56
+ > **Option B — without a custom `server.js`** (most common):
57
+ > Add the require at the top of any service file, e.g. `srv/admin-service.js`:
58
+ > ```js
59
+ > require("cds-error-outbox"); // ← add at the top
60
+ > // ... rest of your service
61
+ > ```
62
+
63
+ Then register the DB model in your project (e.g. in `db/schema.cds`):
64
+
65
+ ```cds
66
+ using from 'cds-error-outbox/db/model';
67
+ ```
68
+
69
+ And deploy:
70
+
71
+ ```bash
72
+ cds deploy --to sqlite # local development
73
+ cds build # production (BTP, HANA)
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Configuration
79
+
80
+ Add the following to your project's `package.json` under `cds.requires`, or to `.cdsrc.json`:
81
+
82
+ ```json
83
+ {
84
+ "cds": {
85
+ "requires": {
86
+ "errorOutbox": {
87
+ "enabled": true,
88
+ "interval": 300000,
89
+ "batchSize": 50,
90
+ "dedup": {
91
+ "enabled": true,
92
+ "windowMinutes": 10
93
+ },
94
+ "mail": {
95
+ "provider": "o365",
96
+ "tenantId": "<YOUR_TENANT_ID>",
97
+ "clientId": "<YOUR_CLIENT_ID>",
98
+ "clientSecret": "<YOUR_CLIENT_SECRET>",
99
+ "from": "errors@yourcompany.com",
100
+ "to": "devops@yourcompany.com"
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Configuration reference
109
+
110
+ | Key | Type | Default | Description |
111
+ | --------------------- | ----------- | -------- | -------------------------------------- |
112
+ | `enabled` | boolean | `true` | Enable/disable the entire plugin |
113
+ | `interval` | number (ms) | `300000` | Batch email job frequency |
114
+ | `batchSize` | number | `50` | Max errors per email batch |
115
+ | `dedup.enabled` | boolean | `true` | Enable hash-based deduplication |
116
+ | `dedup.windowMinutes` | number | `10` | Rolling dedup window in minutes |
117
+ | `mail.provider` | string | `'mock'` | `'o365'` \| `'smtp'` \| `'mock'` |
118
+ | `mail.from` | string | `''` | Sender address |
119
+ | `mail.to` | string | `''` | Recipient(s), comma-separated |
120
+ | `mail.tenantId` | string | `''` | Azure AD tenant ID (O365 only) |
121
+ | `mail.clientId` | string | `''` | Azure AD app client ID (O365 only) |
122
+ | `mail.clientSecret` | string | `''` | Azure AD app client secret (O365 only) |
123
+ | `mail.smtp.host` | string | `''` | SMTP host (SMTP only) |
124
+ | `mail.smtp.port` | number | `587` | SMTP port (SMTP only) |
125
+ | `mail.smtp.secure` | boolean | `false` | Use TLS (SMTP only) |
126
+ | `mail.smtp.auth.user` | string | `''` | SMTP username (SMTP only) |
127
+ | `mail.smtp.auth.pass` | string | `''` | SMTP password (SMTP only) |
128
+
129
+ ---
130
+
131
+ ## Email Providers
132
+
133
+ ### `mock` _(default — development/testing)_
134
+
135
+ Logs the email subject and metadata to the console. No external calls. Use this during local development.
136
+
137
+ ### `o365` — Microsoft Graph API
138
+
139
+ Uses the [Microsoft Graph `sendMail` API](https://learn.microsoft.com/en-us/graph/api/user-sendmail) with an OAuth 2.0 **client credentials** flow. No user login is required. Zero additional npm dependencies.
140
+
141
+ #### O365 Setup
142
+
143
+ 1. Go to [Azure Portal → App Registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps) and click **New registration**
144
+ 2. Note the **Application (client) ID** and **Directory (tenant) ID**
145
+ 3. Go to **Certificates & Secrets** → **New client secret** — note the secret **Value** (shown once)
146
+ 4. Go to **API Permissions** → **Add a permission** → **Microsoft Graph** → **Application permissions**
147
+ - Add: `Mail.Send`
148
+ 5. Click **Grant admin consent** for your organisation
149
+ 6. The `mail.from` address must be a **licensed Exchange Online mailbox** that the app registration has permission to send from
150
+ 7. Configure `cds.requires.errorOutbox.mail`:
151
+
152
+ ```json
153
+ {
154
+ "provider": "o365",
155
+ "tenantId": "<Directory (tenant) ID>",
156
+ "clientId": "<Application (client) ID>",
157
+ "clientSecret": "<Client Secret value>",
158
+ "from": "errors@yourcompany.com",
159
+ "to": "devops@yourcompany.com"
160
+ }
161
+ ```
162
+
163
+ > **Security:** Never commit `clientSecret` to source control.
164
+ >
165
+ > The O365 provider reads credentials from **env variables as a fallback** — values in `package.json` take priority, but any missing field is automatically picked up from the environment:
166
+ >
167
+ > ```bash
168
+ > export CDS_REQUIRES_ERROROUTBOX_MAIL_TENANTID="xxxx"
169
+ > export CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTID="xxxx"
170
+ > export CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTSECRET="xxxx"
171
+ > export CDS_REQUIRES_ERROROUTBOX_MAIL_FROM="errors@company.com"
172
+ > export CDS_REQUIRES_ERROROUTBOX_MAIL_TO="devops@company.com"
173
+ > ```
174
+ >
175
+ > On BTP / Cloud Foundry use `cf set-env <app> CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTSECRET "<value>"` instead of putting the secret in `manifest.yml`.
176
+
177
+ ### `smtp`
178
+
179
+ Requires `nodemailer` (optional peer dependency):
180
+
181
+ ```bash
182
+ npm install nodemailer
183
+ ```
184
+
185
+ ```json
186
+ {
187
+ "provider": "smtp",
188
+ "from": "errors@yourcompany.com",
189
+ "to": "devops@yourcompany.com",
190
+ "smtp": {
191
+ "host": "smtp.yourcompany.com",
192
+ "port": 587,
193
+ "secure": false,
194
+ "auth": { "user": "smtp-user", "pass": "smtp-password" }
195
+ }
196
+ }
197
+ ```
198
+
199
+ ---
200
+
201
+ ## How it works
202
+
203
+ ```
204
+ CAP service throws an error
205
+
206
+
207
+ srv.handle() wrapper — fire-and-forget via setImmediate (non-blocking)
208
+
209
+
210
+ SHA-256( message | service | action )
211
+
212
+ ┌────┴────┐
213
+ │ │
214
+ duplicate? new?
215
+ │ │
216
+ ▼ ▼
217
+ UPDATE INSERT
218
+ count+1 count=1
219
+ lastSeen firstSeen/lastSeen
220
+
221
+
222
+ setInterval every `interval` ms
223
+
224
+
225
+ SELECT sent=false LIMIT batchSize (oldest first)
226
+
227
+
228
+ Format HTML (grouped by service)
229
+
230
+
231
+ provider.send(...)
232
+
233
+ ▼ (only on success)
234
+ UPDATE sent=true WHERE ID IN [...]
235
+ ```
236
+
237
+ ---
238
+
239
+ ## DB Entity
240
+
241
+ The plugin automatically adds the following entity to your project's database schema:
242
+
243
+ ```cds
244
+ namespace error.outbox;
245
+
246
+ entity Errors {
247
+ key ID : UUID;
248
+ hash : String(64);
249
+ service : String;
250
+ action : String;
251
+ message : LargeString;
252
+ stack : LargeString;
253
+ count : Integer;
254
+ firstSeen : Timestamp;
255
+ lastSeen : Timestamp;
256
+ sent : Boolean default false;
257
+ }
258
+ ```
259
+
260
+ ---
261
+
262
+ ## Environment variables
263
+
264
+ CAP maps nested config paths to environment variables. You can override any value at runtime without changing `package.json`:
265
+
266
+ ```bash
267
+ CDS_REQUIRES_ERROROUTBOX_ENABLED=true
268
+ CDS_REQUIRES_ERROROUTBOX_INTERVAL=60000
269
+ CDS_REQUIRES_ERROROUTBOX_MAIL_PROVIDER=o365
270
+ CDS_REQUIRES_ERROROUTBOX_MAIL_TENANTID=...
271
+ CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTID=...
272
+ CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTSECRET=...
273
+ CDS_REQUIRES_ERROROUTBOX_MAIL_FROM=errors@yourcompany.com
274
+ CDS_REQUIRES_ERROROUTBOX_MAIL_TO=devops@yourcompany.com
275
+ ```
276
+
277
+ ---
278
+
279
+ ## Project structure
280
+
281
+ ```
282
+ cds-error-outbox/
283
+ ├── package.json ← CAP plugin declaration (cds.plugin: true)
284
+ ├── index.js ← Entry point — calls bootstrap.initialize()
285
+
286
+ ├── config/
287
+ │ └── defaults.js ← Default config values
288
+
289
+ ├── lib/
290
+ │ ├── bootstrap.js ← Init orchestration (cds lifecycle hooks)
291
+ │ ├── config.js ← Deep merge + config loader (singleton)
292
+ │ ├── interceptor.js ← srv.handle() wrapper (fire-and-forget)
293
+ │ ├── dedup.js ← SHA-256 hash + DB upsert logic
294
+ │ ├── scheduler.js ← Interval batch job
295
+ │ └── formatter.js ← HTML email builder
296
+
297
+ ├── providers/
298
+ │ ├── index.js ← Provider factory
299
+ │ ├── o365.js ← Microsoft Graph API (zero extra deps)
300
+ │ ├── smtp.js ← nodemailer wrapper (optional peer dep)
301
+ │ └── mock.js ← Console logger (dev/test)
302
+
303
+ └── db/
304
+ └── model.cds ← error.outbox.Errors entity
305
+ ```
306
+
307
+ ---
308
+
309
+ ## License
310
+
311
+ MIT