Skip to content

Commit d226a35

Browse files
plcclaude
andcommitted
Add fire-and-forget Discord notifications for monitoring
New src/lib/notify.js posts to Discord webhooks (via env vars) on agent creation, outbound email, and errors. Fully async — no await, no latency impact on API requests. Silently skipped when env vars are unset. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 479ae84 commit d226a35

File tree

4 files changed

+54
-0
lines changed

4 files changed

+54
-0
lines changed

src/lib/errors.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212

1313
const { pool } = require('../db');
14+
const { notify } = require('./notify');
1415

1516
/**
1617
* Log an error to the error_log table. Never throws — falls back to console.error.
@@ -28,6 +29,7 @@ async function logError(err, ctx = {}) {
2829

2930
// Always log to console too
3031
console.error(ctx.route || 'Error:', message);
32+
notify('error', { route: ctx.route || 'unknown', message, agent_id: ctx.agent_id || 'n/a' });
3133

3234
try {
3335
await pool.query(

src/lib/notify.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Fire-and-forget Discord webhook notifications for internal monitoring.
3+
*
4+
* Usage:
5+
* const { notify } = require('../lib/notify');
6+
* notify('agent_created', { agent_id: 'agt_xxx', name: 'My Agent' });
7+
*
8+
* Env vars (optional — if not set, the notification is silently skipped):
9+
* DISCORD_WEBHOOK_AGENT_CREATED
10+
* DISCORD_WEBHOOK_EMAIL_SENT
11+
* DISCORD_WEBHOOK_ERROR
12+
*/
13+
14+
const CHANNELS = {
15+
agent_created: 'DISCORD_WEBHOOK_AGENT_CREATED',
16+
email_sent: 'DISCORD_WEBHOOK_EMAIL_SENT',
17+
error: 'DISCORD_WEBHOOK_ERROR',
18+
};
19+
20+
/**
21+
* Post a notification to Discord. Never blocks, never throws.
22+
*
23+
* @param {'agent_created'|'email_sent'|'error'} event
24+
* @param {object} data — included in the Discord message
25+
*/
26+
function notify(event, data = {}) {
27+
const envKey = CHANNELS[event];
28+
if (!envKey) return;
29+
30+
const url = process.env[envKey];
31+
if (!url) return;
32+
33+
const lines = Object.entries(data)
34+
.map(([k, v]) => `**${k}:** ${v}`)
35+
.join('\n');
36+
37+
const content = `[${event}] ${new Date().toISOString()}\n${lines}`;
38+
39+
// Fire and forget — no await, no .then()
40+
fetch(url, {
41+
method: 'POST',
42+
headers: { 'Content-Type': 'application/json' },
43+
body: JSON.stringify({ content: content.slice(0, 2000) }),
44+
}).catch(() => {}); // swallow silently
45+
}
46+
47+
module.exports = { notify };

src/lib/outbound.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
const { default: ical } = require('ical-generator');
1818
const { pool } = require('../db');
19+
const { notify } = require('./notify');
1920

2021
let postmarkClient;
2122

@@ -82,6 +83,7 @@ async function sendViaSmtp(smtpConfig, params) {
8283
}
8384

8485
const info = await transporter.sendMail(mailOpts);
86+
notify('email_sent', { to: params.to, subject: params.subject, via: params.smtpConfig ? 'agent_smtp' : 'postmark' });
8587
return { sent: true, messageId: info.messageId };
8688
}
8789

src/routes/agents.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const { pool } = require('../db');
1717
const { agentId, apiKey } = require('../lib/ids');
1818
const { hashKey } = require('../lib/keys');
1919
const { logError } = require('../lib/errors');
20+
const { notify } = require('../lib/notify');
2021
const auth = require('../middleware/auth');
2122

2223
const router = Router();
@@ -78,6 +79,8 @@ router.post('/', async (req, res) => {
7879
if (name) response.name = name;
7980
if (description) response.description = description;
8081

82+
notify('agent_created', { agent_id: id, name: name || '(unnamed)' });
83+
8184
res.status(201).json(response);
8285
} catch (err) {
8386
await logError(err, { route: 'POST /agents', method: 'POST' });

0 commit comments

Comments
 (0)