Skip to content

Commit 16f01a0

Browse files
plcclaude
andcommitted
Add agent name to outbound emails, update /docs with missing endpoints
- Outbound invite and RSVP reply emails now use agent name as From display name when set (e.g. "My Agent" <cal-xxx@invite.caldave.ai>) - /docs page now documents GET /agents/me, PATCH /agents, agent name/description on POST /agents, GET /changelog, and POST /man - Added IMPORTANT note to CLAUDE.md requiring all five doc surfaces to be updated when adding public API endpoints Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6c0ada7 commit 16f01a0

File tree

6 files changed

+149
-20
lines changed

6 files changed

+149
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1111
- **API changelog endpoint**`GET /changelog` returns a structured list of API changes with dates and docs links. With optional Bearer auth, highlights changes introduced since the agent was created. Designed for agents to poll ~weekly.
1212
- **Agent metadata**`POST /agents` now accepts optional `name` and `description` fields to identify agents. New `GET /agents/me` returns the agent's profile. New `PATCH /agents` updates metadata without changing the API key. Agent name and description are surfaced in `POST /man` context.
1313
- **Outbound calendar invites** — when an event is created or updated with attendees, CalDave sends METHOD:REQUEST iCal invite emails via Postmark. Invites include `.ics` attachments that work with Google Calendar, Outlook, and Apple Calendar. From address is the calendar's email so replies route back through the inbound webhook.
14+
- **Agent name in outbound emails** — when an agent has a name set (via `PATCH /agents`), outbound invite and RSVP reply emails use `"Agent Name" <calendar-email>` as the From address, so recipients see a friendly display name instead of just the calendar email.
1415
- **Outbound RSVP replies** — when an agent responds to an inbound invite via `POST /respond`, CalDave sends a METHOD:REPLY iCal email back to the organiser with the agent's acceptance, decline, or tentative status.
1516
- **Graceful degradation** — if `POSTMARK_SERVER_TOKEN` is not set, outbound emails are silently skipped. All API endpoints continue to work normally.
1617
- **Outbound email tracking** — new `invite_sent`, `reply_sent`, and `ical_sequence` columns on events prevent duplicate sends and support proper iCal update semantics.
@@ -23,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
2324
- **Terms of Service and Privacy Policy** — new `/terms` and `/privacy` pages with footer links on landing, docs, and quickstart pages.
2425

2526
### Fixed
27+
- **API docs updated**`/docs` page now documents `GET /agents/me`, `PATCH /agents`, agent `name`/`description` fields on `POST /agents`, `GET /changelog`, and `POST /man`. Table of contents updated with Agents and Discovery sections.
2628
- **JSON 404 catch-all** — unmatched routes now return `{"error": "Not found. Try POST /man for the API reference."}` instead of Express's default HTML page.
2729
- **Guide mode discoverability**`POST /man?guide` now includes a `discover_more` object pointing to the full API reference, changelog, and agent update endpoint so agents don't have to guess at available endpoints.
2830
- **Welcome event in /man recommendation**`POST /man` recommendation logic now accounts for the auto-created welcome event (same fix as changelog recommendations).

CLAUDE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ fly deploy
7070
| **README.md** | When user-facing details change |
7171
| **GOTCHAS.md** | When you encounter problems |
7272

73+
**IMPORTANT: When adding or changing public API endpoints, you MUST update ALL of these:**
74+
1. **`src/routes/docs.js`** — the HTML docs page at `/docs` (endpoint card + table of contents)
75+
2. **`src/routes/changelog.js`** — the structured `CHANGELOG` array served by `GET /changelog`
76+
3. **`src/routes/man.js`** — the `getEndpoints()` array served by `POST /man`
77+
4. **`CHANGELOG.md`** — the human-readable changelog
78+
5. **`CALDAVE_SPEC.md`** — the full API spec
79+
80+
These are all separate and must be kept in sync. Missing any one means agents or humans won't discover the new endpoint.
81+
7382
## Git Workflow
7483

7584
- **Update CHANGELOG.md before committing** — include it in the same commit as your changes

src/lib/outbound.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,11 @@ function generateReplyIcs(event, calendar, response) {
138138
* @param {Object} event — event row from DB
139139
* @param {Object} calendar — calendar row from DB
140140
* @param {string[]} recipientEmails — email addresses to send to
141+
* @param {Object} [options] — optional settings
142+
* @param {string} [options.agentName] — agent display name for From header
141143
* @returns {Promise<{ sent: boolean, reason?: string, icalUid?: string }>}
142144
*/
143-
async function sendInviteEmail(event, calendar, recipientEmails) {
145+
async function sendInviteEmail(event, calendar, recipientEmails, options = {}) {
144146
const client = getPostmarkClient();
145147
if (!client) {
146148
console.log('[outbound] Skipping invite (no POSTMARK_SERVER_TOKEN): event=%s', event.id);
@@ -155,11 +157,12 @@ async function sendInviteEmail(event, calendar, recipientEmails) {
155157
const { icsString, icalUid } = generateInviteIcs(event, calendar);
156158

157159
const to = recipientEmails.join(',');
158-
console.log('[outbound] Sending invite: event=%s from=%s to=%s title="%s"', event.id, calendar.email, to, event.title);
160+
const from = options.agentName ? '"' + options.agentName + '" <' + calendar.email + '>' : calendar.email;
161+
console.log('[outbound] Sending invite: event=%s from=%s to=%s title="%s"', event.id, from, to, event.title);
159162

160163
try {
161164
const pmResponse = await client.sendEmail({
162-
From: calendar.email,
165+
From: from,
163166
To: to,
164167
Subject: 'Invitation: ' + event.title,
165168
TextBody: [
@@ -193,9 +196,11 @@ async function sendInviteEmail(event, calendar, recipientEmails) {
193196
* @param {Object} event — event row from DB (must have organiser_email and ical_uid)
194197
* @param {Object} calendar — calendar row from DB
195198
* @param {string} response — 'accepted' | 'declined' | 'tentative'
199+
* @param {Object} [options] — optional settings
200+
* @param {string} [options.agentName] — agent display name for From header
196201
* @returns {Promise<{ sent: boolean, reason?: string }>}
197202
*/
198-
async function sendReplyEmail(event, calendar, response) {
203+
async function sendReplyEmail(event, calendar, response, options = {}) {
199204
const client = getPostmarkClient();
200205
if (!client) {
201206
console.log('[outbound] Skipping reply (no POSTMARK_SERVER_TOKEN): event=%s', event.id);
@@ -215,11 +220,12 @@ async function sendReplyEmail(event, calendar, response) {
215220
const icsString = generateReplyIcs(event, calendar, response);
216221
const statusLabel = response.charAt(0).toUpperCase() + response.slice(1);
217222

218-
console.log('[outbound] Sending %s reply: event=%s from=%s to=%s title="%s"', response, event.id, calendar.email, event.organiser_email, event.title);
223+
const from = options.agentName ? '"' + options.agentName + '" <' + calendar.email + '>' : calendar.email;
224+
console.log('[outbound] Sending %s reply: event=%s from=%s to=%s title="%s"', response, event.id, from, event.organiser_email, event.title);
219225

220226
try {
221227
const pmResponse = await client.sendEmail({
222-
From: calendar.email,
228+
From: from,
223229
To: event.organiser_email,
224230
Subject: statusLabel + ': ' + event.title,
225231
TextBody: calendar.name + ' has ' + response + ' the invitation: ' + event.title,

src/routes/changelog.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ const CHANGELOG = [
128128
endpoints: ['POST /agents', 'GET /agents/me', 'PATCH /agents'],
129129
docs: BASE + '/docs#agents',
130130
},
131+
{
132+
type: 'feature',
133+
title: 'Agent name in outbound emails',
134+
description: 'When your agent has a name set, outbound invite and RSVP reply emails show the agent name as the From display name (e.g. "My Agent" <cal-xxx@invite.caldave.ai>).',
135+
endpoints: ['POST /calendars/:id/events', 'PATCH /calendars/:id/events/:event_id', 'POST /calendars/:id/events/:event_id/respond'],
136+
docs: BASE + '/docs#agents',
137+
},
131138
{
132139
type: 'feature',
133140
title: 'Personalized recommendations in changelog',

src/routes/docs.js

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,11 @@ router.get('/', (req, res) => {
7676
7777
<div class="toc">
7878
<h2>Endpoints</h2>
79-
<div class="section">Auth</div>
79+
<div class="section">Agents</div>
8080
<ul>
8181
<li><a href="#post-agents">POST /agents</a> — Create agent</li>
82+
<li><a href="#get-agents-me">GET /agents/me</a> — Get agent profile</li>
83+
<li><a href="#patch-agents">PATCH /agents</a> — Update agent</li>
8284
</ul>
8385
<div class="section">Calendars</div>
8486
<ul>
@@ -104,6 +106,11 @@ router.get('/', (req, res) => {
104106
<li><a href="#get-feed">GET /feeds/:id.ics</a> — iCal feed</li>
105107
<li><a href="#post-inbound">POST /inbound/:token</a> — Inbound email webhook</li>
106108
</ul>
109+
<div class="section">Discovery</div>
110+
<ul>
111+
<li><a href="#get-changelog">GET /changelog</a> — API changelog</li>
112+
<li><a href="#post-man">POST /man</a> — Machine-readable API manual</li>
113+
</ul>
107114
</div>
108115
109116
<!-- ============================================================ -->
@@ -113,7 +120,7 @@ router.get('/', (req, res) => {
113120
<p>Exceptions: <code class="inline-code">POST /agents</code> (no auth), <code class="inline-code">GET /feeds</code> (token in query param), and <code class="inline-code">POST /inbound</code> (token in URL path).</p>
114121
115122
<!-- ============================================================ -->
116-
<h2>Agent Provisioning</h2>
123+
<h2>Agents</h2>
117124
118125
<div class="endpoint" id="post-agents">
119126
<div class="method-path">
@@ -122,16 +129,71 @@ router.get('/', (req, res) => {
122129
<span class="auth-badge">No auth</span>
123130
</div>
124131
<p class="desc">Create a new agent identity. Returns credentials you must save — the API key is shown once.</p>
132+
<div class="label">Body parameters</div>
133+
<div class="params">
134+
<div class="param"><span class="param-name">name <span class="param-opt">optional</span></span><span class="param-desc">Display name for the agent (max 255 chars). Shown in outbound email From headers.</span></div>
135+
<div class="param"><span class="param-name">description <span class="param-opt">optional</span></span><span class="param-desc">What the agent does (max 1000 chars). Surfaced in POST /man context.</span></div>
136+
</div>
125137
<div class="label">Example</div>
126-
<pre><code>curl -s -X POST https://${DOMAIN}/agents</code></pre>
138+
<pre><code>curl -s -X POST https://${DOMAIN}/agents \\
139+
-H "Content-Type: application/json" \\
140+
-d '{"name": "Meeting Scheduler", "description": "Books rooms and sends reminders"}'</code></pre>
127141
<div class="label">Response</div>
128142
<pre><code>{
129143
"agent_id": "agt_x7y8z9AbCd",
130144
"api_key": "sk_live_abc123...",
145+
"name": "Meeting Scheduler",
146+
"description": "Books rooms and sends reminders",
131147
"message": "Store these credentials securely. The API key will not be shown again."
132148
}</code></pre>
133149
</div>
134150
151+
<div class="endpoint" id="get-agents-me">
152+
<div class="method-path">
153+
<span class="method get">GET</span>
154+
<span class="path">/agents/me</span>
155+
<span class="auth-badge required">Bearer token</span>
156+
</div>
157+
<p class="desc">Get the authenticated agent's profile.</p>
158+
<div class="label">Example</div>
159+
<pre><code>curl -s https://${DOMAIN}/agents/me \\
160+
-H "Authorization: Bearer YOUR_API_KEY"</code></pre>
161+
<div class="label">Response</div>
162+
<pre><code>{
163+
"agent_id": "agt_x7y8z9AbCd",
164+
"name": "Meeting Scheduler",
165+
"description": "Books rooms and sends reminders",
166+
"created_at": "2026-02-14T10:30:00.000Z"
167+
}</code></pre>
168+
</div>
169+
170+
<div class="endpoint" id="patch-agents">
171+
<div class="method-path">
172+
<span class="method patch">PATCH</span>
173+
<span class="path">/agents</span>
174+
<span class="auth-badge required">Bearer token</span>
175+
</div>
176+
<p class="desc">Update agent metadata. Does not change the API key. Set a field to <code class="inline-code">null</code> to clear it.</p>
177+
<div class="label">Body parameters</div>
178+
<div class="params">
179+
<div class="param"><span class="param-name">name</span><span class="param-desc">Display name (max 255 chars)</span></div>
180+
<div class="param"><span class="param-name">description</span><span class="param-desc">What the agent does (max 1000 chars)</span></div>
181+
</div>
182+
<div class="label">Example</div>
183+
<pre><code>curl -s -X PATCH https://${DOMAIN}/agents \\
184+
-H "Content-Type: application/json" \\
185+
-H "Authorization: Bearer YOUR_API_KEY" \\
186+
-d '{"name": "Updated Name"}'</code></pre>
187+
<div class="label">Response</div>
188+
<pre><code>{
189+
"agent_id": "agt_x7y8z9AbCd",
190+
"name": "Updated Name",
191+
"description": "Books rooms and sends reminders",
192+
"created_at": "2026-02-14T10:30:00.000Z"
193+
}</code></pre>
194+
<div class="note">When an agent has a name, outbound invite and RSVP reply emails use it as the From display name (e.g. "Meeting Scheduler" &lt;cal-xxx@${EMAIL_DOMAIN}&gt;).</div>
195+
</div>
196+
135197
<!-- ============================================================ -->
136198
<h2>Calendars</h2>
137199
@@ -463,6 +525,49 @@ Daily standup 2026-02-13 16:00:00Z ...
463525
{ "status": "ignored", "reason": "..." }</code></pre>
464526
</div>
465527
528+
<!-- ============================================================ -->
529+
<h2>Discovery</h2>
530+
531+
<div class="endpoint" id="get-changelog">
532+
<div class="method-path">
533+
<span class="method get">GET</span>
534+
<span class="path">/changelog</span>
535+
<span class="auth-badge">No auth (optional Bearer)</span>
536+
</div>
537+
<p class="desc">Structured list of API changes with dates and docs links. With a Bearer token, highlights changes since your agent was created and includes personalized recommendations. Poll ~weekly to discover new features.</p>
538+
<div class="label">Example</div>
539+
<pre><code>curl -s https://${DOMAIN}/changelog \\
540+
-H "Authorization: Bearer YOUR_API_KEY"</code></pre>
541+
<div class="label">Response (authenticated)</div>
542+
<pre><code>{
543+
"description": "CalDave API changelog...",
544+
"your_agent": { "agent_id": "agt_...", "created_at": "..." },
545+
"changes_since_signup": [{ "date": "2026-02-14", "changes": [...] }],
546+
"changes_since_signup_count": 2,
547+
"changelog": [{ "date": "2026-02-08", "changes": [...] }],
548+
"recommendations": [
549+
{ "action": "Name your agent", "why": "...", "how": "PATCH /agents ...", "docs": "..." }
550+
]
551+
}</code></pre>
552+
<div class="note">The <code class="inline-code">recommendations</code> array includes actionable suggestions based on your agent state (e.g. name your agent, create a calendar, create an event). Only present when authenticated and there are suggestions.</div>
553+
</div>
554+
555+
<div class="endpoint" id="post-man">
556+
<div class="method-path">
557+
<span class="method post">POST</span>
558+
<span class="path">/man</span>
559+
<span class="auth-badge">No auth (optional Bearer)</span>
560+
</div>
561+
<p class="desc">Machine-readable API manual. Returns all endpoints with curl examples and parameters. With Bearer auth, includes your real calendar IDs and a recommended next step. Use <code class="inline-code">?guide</code> for a compact onboarding overview.</p>
562+
<div class="label">Example — full reference</div>
563+
<pre><code>curl -s -X POST https://${DOMAIN}/man \\
564+
-H "Authorization: Bearer YOUR_API_KEY"</code></pre>
565+
<div class="label">Example — guided onboarding</div>
566+
<pre><code>curl -s -X POST "https://${DOMAIN}/man?guide" \\
567+
-H "Authorization: Bearer YOUR_API_KEY"</code></pre>
568+
<div class="note">The <code class="inline-code">?guide</code> mode returns only an overview, your context, a recommended next step, and links to discover more. Ideal for first-time agent onboarding.</div>
569+
</div>
570+
466571
<!-- ============================================================ -->
467572
<h2>Quick Start</h2>
468573
<div class="endpoint">

0 commit comments

Comments
 (0)