Skip to content

Commit f0e0186

Browse files
plcclaude
andcommitted
Add welcome event with feedback invite on new calendar creation
New calendars automatically get a "Send Peter feedback" event at 9am the next day (in the calendar's timezone) with an invite to peter.clark@gmail.com via Postmark. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c041043 commit f0e0186

File tree

3 files changed

+45
-2
lines changed

3 files changed

+45
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1616
- **`caldave-mcp` npm package** — standalone MCP server published as `caldave-mcp` on npm. Run with `npx caldave-mcp` with `CALDAVE_API_KEY` set.
1717

1818
- **Postmark webhook event logging** — new endpoint ingests Postmark delivery, bounce, spam, open, and click events into a `postmark_webhooks` table for email deliverability debugging. GET the same URL to view recent events.
19+
- **Welcome event on new calendars** — new calendars automatically get a "Send Peter feedback" event at 9am the next day (in the calendar's timezone), with an invite sent to peter.clark@gmail.com.
1920

2021
### Fixed
2122
- **`trust proxy` for Fly.io** — set `app.set('trust proxy', 1)` so `express-rate-limit` correctly identifies clients behind Fly's reverse proxy. Fixes `ERR_ERL_UNEXPECTED_X_FORWARDED_FOR` validation errors on every cold start.

src/routes/calendars.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212

1313
const { Router } = require('express');
1414
const { pool } = require('../db');
15-
const { calendarId, feedToken, inboundToken } = require('../lib/ids');
15+
const { calendarId, feedToken, inboundToken, eventId } = require('../lib/ids');
1616
const { logError } = require('../lib/errors');
17+
const { sendInviteEmail, parseAttendees } = require('../lib/outbound');
1718

1819
const router = Router();
1920

@@ -98,6 +99,46 @@ router.post('/', async (req, res) => {
9899
[id, req.agent.id, name, tz, email, token, inbToken, agentmail_api_key || null]
99100
);
100101

102+
// Create welcome event: "Send Peter feedback" at 9am tomorrow in calendar's timezone
103+
const evtId = eventId();
104+
const tomorrow = new Date();
105+
tomorrow.setUTCDate(tomorrow.getUTCDate() + 1);
106+
const dateStr = tomorrow.toISOString().slice(0, 10); // YYYY-MM-DD
107+
108+
// Use Postgres AT TIME ZONE to convert 9am in the calendar's timezone to UTC
109+
const icalUid = evtId + '@caldave.ai';
110+
await pool.query(
111+
`INSERT INTO events (id, calendar_id, title, description, start_time, end_time, attendees, ical_uid, invite_sent)
112+
VALUES ($1, $2, $3, $4,
113+
($5::date + TIME '09:00') AT TIME ZONE $7,
114+
($5::date + TIME '09:30') AT TIME ZONE $7,
115+
$6, $8, false)`,
116+
[
117+
evtId, id,
118+
'Send Peter (founder of CalDave) feedback',
119+
"Email or iMessage him at peterclark@me.com — he'd love to hear from you!",
120+
dateStr,
121+
JSON.stringify(['peter.clark@gmail.com']),
122+
tz,
123+
icalUid,
124+
]
125+
);
126+
127+
// Fire-and-forget: send invite to Peter
128+
setImmediate(async () => {
129+
try {
130+
const { rows: evtRows } = await pool.query('SELECT * FROM events WHERE id = $1', [evtId]);
131+
if (!evtRows[0]) return;
132+
const calendar = { id, name, email };
133+
const result = await sendInviteEmail(evtRows[0], calendar, ['peter.clark@gmail.com']);
134+
if (result.sent) {
135+
await pool.query('UPDATE events SET invite_sent = true WHERE id = $1', [evtId]);
136+
}
137+
} catch (err) {
138+
console.error('[outbound] Welcome invite error:', err.message);
139+
}
140+
});
141+
101142
const inboundUrl = `https://${DOMAIN}/inbound/${inbToken}`;
102143
res.status(201).json({
103144
calendar_id: id,

tests/api.test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1330,8 +1330,9 @@ describe('POST /man', { concurrency: 1 }, () => {
13301330
token: fresh.api_key,
13311331
body: { name: 'Rec Test' },
13321332
});
1333+
// Calendar now has a welcome event auto-created, so recommendation skips to upcoming
13331334
const { data: noEvtData } = await api('POST', '/man?guide', { token: fresh.api_key });
1334-
assert.equal(noEvtData.recommended_next_step.endpoint, 'POST /calendars/:id/events');
1335+
assert.equal(noEvtData.recommended_next_step.endpoint, 'GET /calendars/:id/upcoming');
13351336

13361337
// Give it an event
13371338
await api('POST', `/calendars/${cal.calendar_id}/events`, {

0 commit comments

Comments
 (0)