cds-error-outbox
A CAP plugin that captures errors raised by CAP services into a database entity, deduplicates them so a repeating failure is one alert, and mails it over SMTP or Microsoft Graph.
The gap between an error reaching a client and anyone knowing it happened. Deduplication is the part that makes it usable: the same failure repeating does not become the same mail repeating. Zero runtime dependencies, peer @sap/cds@sap/cdsThe CAP Node.js runtime, which compiles the CDS model, serves the services declared in it, answers generic requests before any handler runs, and loads every cds-plugin.sap official, cap >= 7.
Facts
Where it sits
Who is behind it
Before choosing this
Conditions
LicenceSAP Developer License Agreement 3.2 CAP§ 1, 2, 3A cds-plugin is used with SAP CAP materials, or an artifact is explicitly identified as a CAP plugin or application.
- § 1 License. Productive customer applications are limited to SAP BTP or another SAP-licensed platform, and mass extraction to a non-SAP product is restricted except where solely required for SAP interoperability.
- § 2 Intellectual Property. The agreement retains SAP's rights in the covered materials and limits making APIs, tools or software available to third parties, while preserving ownership of customer applications subject to SAP's underlying rights.
- § 3 Artificial Intelligence Training. The agreement prohibits use of the covered materials and customer applications for training artificial-intelligence models or systems.
PracticalFunctional boundary
Alerting by mail is not telemetry — if you need traces and metrics correlated across handlers, that is a different layer. It also publishes no repository, so the package is the only thing to read.
What it declares
Every artifact
- Type
- CDS plugin
- Origin
- Independently maintained
- Runtime
- Node.js
As a CDS plugin
- Declared for
- >=7
- Distributed through
- npm
Connections
Turning a failure into a response a client can act on
Download
Install
npm add cds-error-outboxDerived from the package name. No recipe recorded; the README has the configuration.
README
quoted from cds-error-outbox@1.0.4 under MIT, readInstallation · Configuration · Admin UI · Email Providers · How it works · DB Entity · Environment variables · Project structure
Features
- Hooks into all CAP services automatically — zero manual wiring required
- Persists errors to a CDS-managed
error.outbox.Errorsentity - Deduplicates via
SHA-256(message + service + action)— increments a counter instead of flooding the DB - Sends batched HTML email reports on a configurable interval
- Pluggable providers: O365 (Microsoft Graph), SMTP (nodemailer), Mock (dev/test)
- Non-blocking — fire-and-forget capture, the request pipeline is never delayed
- Resilient — all internal failures are caught, logged, and swallowed; the app never crashes
Installation
npm install cds-error-outbox
Because package.json declares "cds": { "plugin": true }, CAP automatically loads index.js at startup.
Note: Due to how CAP resolves symlinked local packages, you may need to explicitly require the plugin so it is loaded at startup.
Option A — with a custom
server.js:require("cds-error-outbox"); // ← add as the very first line // ... rest of your server.jsOption B — without a custom
server.js(most common): Add the require at the top of any service file, e.g.srv/admin-service.js:require("cds-error-outbox"); // ← add at the top // ... rest of your service
Then register the DB model in your project (e.g. in db/schema.cds):
using from 'cds-error-outbox/db/model';
And deploy:
cds deploy --to sqlite # local development
cds build # production (BTP, HANA)
Expose the Admin Service
To enable the OData admin API (required for the Admin UI), expose the built-in service in your project. Add it to any .cds file — for example srv/services.cds or directly in db/schema.cds:
using from 'cds-error-outbox';
This mounts ErrorOutboxAdminService at /odata/v4/error-outbox/ and exposes the Errors entity for querying, filtering, acknowledging, and purging.
Roles: The service requires the
error-outbox-adminrole. In development with CAP mock auth (cds.auth.strategy: 'dummy'), any user (e.g.alice) has access automatically.In production, assign the role to users via your
xs-security.json:{ "scopes": [ { "name": "$XSAPPNAME.error-outbox-admin", "description": "Access Error Outbox admin UI" } ], "role-templates": [ { "name": "error-outbox-admin", "scope-references": ["$XSAPPNAME.error-outbox-admin"] } ] }
Configuration
Add the following to your project's package.json under cds.requires, or to .cdsrc.json:
{
"cds": {
"requires": {
"errorOutbox": {
"enabled": true,
"interval": 300000,
"batchSize": 50,
"dedup": {
"enabled": true,
"windowMinutes": 10
},
"mail": {
"provider": "o365",
"tenantId": "<YOUR_TENANT_ID>",
"clientId": "<YOUR_CLIENT_ID>",
"clientSecret": "<YOUR_CLIENT_SECRET>",
"from": "errors@yourcompany.com",
"to": "devops@yourcompany.com"
}
}
}
}
}
Configuration reference
| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable/disable the entire plugin |
interval | number (ms) | 300000 | Batch email job frequency |
batchSize | number | 50 | Max errors per email batch |
dedup.enabled | boolean | true | Enable hash-based deduplication |
dedup.windowMinutes | number | 10 | Rolling dedup window in minutes |
mail.provider | string | 'mock' | 'o365' | 'smtp' | 'mock' |
mail.from | string | '' | Sender address |
mail.to | string | '' | Recipient(s), comma-separated |
mail.subject | string | '[CAP Error Outbox] {count} occurrence(s) in {errors} error(s) — {timestamp}' | Email subject template. Placeholders: {count}, {errors}, {timestamp} |
mail.importance | string | 'normal' | Email priority: 'low' | 'normal' | 'high' |
mail.tenantId | string | '' | Azure AD tenant ID (O365 only) |
mail.clientId | string | '' | Azure AD app client ID (O365 only) |
mail.clientSecret | string | '' | Azure AD app client secret (O365 only) |
mail.smtp.host | string | '' | SMTP host (SMTP only) |
mail.smtp.port | number | 587 | SMTP port (SMTP only) |
mail.smtp.secure | boolean | false | Use TLS (SMTP only) |
mail.smtp.auth.user | string | '' | SMTP username (SMTP only) |
mail.smtp.auth.pass | string | '' | SMTP password (SMTP only) |
Admin UI
The plugin ships a built-in SAPUI5 admin application served automatically at:
http://localhost:4004/error-outbox-admin/index.html
No additional setup is required — the UI is registered as a static Express route when the plugin loads. To disable it, set adminUi: false in your config:
{
"cds": {
"requires": {
"errorOutbox": {
"adminUi": false
}
}
}
}
Features
| Feature | Description |
|---|---|
| Overview panel | KPI cards (total / pending / acknowledged), sent ratio bar, top services by occurrence count |
| Collapsible rows | Click any row to expand inline details — metadata, hash, stack trace, and action buttons |
| Inline actions | Acknowledge or delete individual errors directly from the list |
| Bulk actions | Select multiple rows via checkboxes → Acknowledge / Delete |
| Purge Sent | One-click deletion of all acknowledged errors |
| Filters | Free-text search (service, action, message) + status filter (All / Pending / Sent) |
| Detail page | Full-page view per error — navigate via "Open Detail" button or direct URL |
Required setup
The Admin UI consumes the ErrorOutboxAdminService OData API. You must expose it in your project as described in Expose the Admin Service above.
Your package.json should also include:
{
"cds": {
"requires": {
"errorOutbox": {
"enabled": true
}
}
}
}
Authentication
In development (CAP mock auth), navigate to:
http://localhost:4004/error-outbox-admin/index.html
You will be prompted for a username — enter alice (or any user defined in your .cdsrc.json / package.json users list). No password is required with dummy auth.
In production, the UI respects the same auth strategy as the rest of your CAP application. The service is protected by the error-outbox-admin role.
Email Providers
mock (default — development/testing)
Logs the email subject and metadata to the console. No external calls. Use this during local development.
o365 — Microsoft Graph API
Uses the Microsoft Graph sendMail API with an OAuth 2.0 client credentials flow. No user login is required. Zero additional npm dependencies.
O365 Setup
- Go to Azure Portal → App Registrations and click New registration
- Note the Application (client) ID and Directory (tenant) ID
- Go to Certificates & Secrets → New client secret — note the secret Value (shown once)
- Go to API Permissions → Add a permission → Microsoft Graph → Application permissions
- Add:
Mail.Send
- Add:
- Click Grant admin consent for your organisation
- The
mail.fromaddress must be a licensed Exchange Online mailbox that the app registration has permission to send from - Configure
cds.requires.errorOutbox.mail:
{
"provider": "o365",
"tenantId": "<Directory (tenant) ID>",
"clientId": "<Application (client) ID>",
"clientSecret": "<Client Secret value>",
"from": "errors@yourcompany.com",
"to": "devops@yourcompany.com"
}
Security: Never commit
clientSecretto source control.The O365 provider reads credentials from env variables as a fallback — values in
package.jsontake priority, but any missing field is automatically picked up from the environment:export CDS_REQUIRES_ERROROUTBOX_MAIL_TENANTID="xxxx" export CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTID="xxxx" export CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTSECRET="xxxx" export CDS_REQUIRES_ERROROUTBOX_MAIL_FROM="errors@company.com" export CDS_REQUIRES_ERROROUTBOX_MAIL_TO="devops@company.com"On BTP / Cloud Foundry use
cf set-env <app> CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTSECRET "<value>"instead of putting the secret inmanifest.yml.
smtp
Requires nodemailer (optional peer dependency):
npm install nodemailer
{
"provider": "smtp",
"from": "errors@yourcompany.com",
"to": "devops@yourcompany.com",
"smtp": {
"host": "smtp.yourcompany.com",
"port": 587,
"secure": false,
"auth": { "user": "smtp-user", "pass": "smtp-password" }
}
}
How it works
CAP service throws an error
│
▼
srv.handle() wrapper — fire-and-forget via setImmediate (non-blocking)
│
▼
SHA-256( message | service | action )
│
┌────┴────┐
│ │
duplicate? new?
│ │
▼ ▼
UPDATE INSERT
count+1 count=1
lastSeen firstSeen/lastSeen
│
▼
setInterval every `interval` ms
│
▼
SELECT sent=false LIMIT batchSize (oldest first)
│
▼
Format HTML (grouped by service)
│
▼
provider.send(...)
│
▼ (only on success)
UPDATE sent=true WHERE ID IN [...]
DB Entity
The plugin automatically adds the following entity to your project's database schema:
namespace error.outbox;
entity Errors {
key ID : UUID;
hash : String(64);
service : String;
action : String;
message : LargeString;
stack : LargeString;
count : Integer;
firstSeen : Timestamp;
lastSeen : Timestamp;
sent : Boolean default false;
}
Environment variables
CAP maps nested config paths to environment variables. You can override any value at runtime without changing package.json:
CDS_REQUIRES_ERROROUTBOX_ENABLED=true
CDS_REQUIRES_ERROROUTBOX_INTERVAL=60000
CDS_REQUIRES_ERROROUTBOX_MAIL_PROVIDER=o365
CDS_REQUIRES_ERROROUTBOX_MAIL_TENANTID=...
CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTID=...
CDS_REQUIRES_ERROROUTBOX_MAIL_CLIENTSECRET=...
CDS_REQUIRES_ERROROUTBOX_MAIL_FROM=errors@yourcompany.com
CDS_REQUIRES_ERROROUTBOX_MAIL_TO=devops@yourcompany.com
Project structure
cds-error-outbox/
├── package.json ← CAP plugin declaration (cds.plugin: true)
├── index.js ← Entry point — calls bootstrap.initialize(), registers admin UI
│
├── config/
│ └── defaults.js ← Default config values
│
├── lib/
│ ├── bootstrap.js ← Init orchestration (cds lifecycle hooks)
│ ├── config.js ← Deep merge + config loader (singleton)
│ ├── interceptor.js ← srv.handle() wrapper (fire-and-forget)
│ ├── dedup.js ← SHA-256 hash + DB upsert logic
│ ├── scheduler.js ← Interval batch job
│ └── formatter.js ← HTML email builder
│
├── providers/
│ ├── index.js ← Provider factory
│ ├── o365.js ← Microsoft Graph API (zero extra deps)
│ ├── smtp.js ← nodemailer wrapper (optional peer dep)
│ └── mock.js ← Console logger (dev/test)
│
├── srv/
│ ├── admin-service.cds ← ErrorOutboxAdminService definition (@path: 'error-outbox')
│ └── admin-service.js ← acknowledge() and purgeSent() action handlers
│
├── app/
│ └── error-outbox-admin/
│ └── webapp/ ← SAPUI5 admin application (served at /error-outbox-admin/)
│ ├── index.html
│ ├── manifest.json
│ ├── Component.js
│ ├── controller/
│ │ ├── List.controller.js ← list page with collapsible rows + stats
│ │ └── Detail.controller.js ← detail page
│ ├── view/
│ │ ├── List.view.xml
│ │ └── Detail.view.xml
│ ├── model/
│ │ └── Formatter.js ← UI formatters (date, status, state)
│ └── css/
│ └── style.css
│
└── db/
└── model.cds ← error.outbox.Errors entity
License
MIT
History
Others providing
Compare all 3 on the Error handling page →- com.sap.cds:cds-services-bomcom.sap.cds:cds-services-bomThe Maven bill of materials for CAP Java, pinning every com.sap.cds module in a release, from the API and core runtime to the Spring Boot starters and the CQN engine, to the same version.sap official, capMaven bill of materials pinning CAP Java module versions
- @sap/cds@sap/cdsThe CAP Node.js runtime, which compiles the CDS model, serves the services declared in it, answers generic requests before any handler runs, and loads every cds-plugin.sap official, capCAP Node.js runtime
Details
Sources
Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.
Compatibility and provenance
History in the atlas
- First seen.
For your agent
- Ref
artifact:cds-error-outbox- JSON
- /artifacts/cds-error-outbox.json
- MCP
- Connecting an agent