lettr-mcp 1.6.0 → 1.7.0
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.
- package/README.md +4 -3
- package/dist/package.json +1 -1
- package/dist/tools/emails.js +67 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,9 +98,10 @@ Environment variables:
|
|
|
98
98
|
| `list-emails` | List recently sent emails (cursor-paginated, with recipient and date filters) |
|
|
99
99
|
| `list-email-events` | List email events (delivery, bounce, click, open, …) with filters by type, recipient, transmission, and date range |
|
|
100
100
|
| `get-email-detail` | Retrieve the full delivery timeline for a single transmission by request ID |
|
|
101
|
-
| `schedule-email` | Schedule a transactional email for future delivery (5+ minutes ahead, within
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
101
|
+
| `schedule-email` | Schedule a transactional email for future delivery (5+ minutes ahead, within 30 days) |
|
|
102
|
+
| `list-scheduled-emails` | List emails waiting to be sent, with a state filter |
|
|
103
|
+
| `get-scheduled-email` | Get the state and events of a scheduled email |
|
|
104
|
+
| `cancel-scheduled-email` | Cancel a scheduled email before it is sent |
|
|
104
105
|
|
|
105
106
|
### Templates
|
|
106
107
|
|
package/dist/package.json
CHANGED
package/dist/tools/emails.js
CHANGED
|
@@ -19,6 +19,24 @@ const EMAIL_EVENT_TYPES = [
|
|
|
19
19
|
'list_unsubscribe',
|
|
20
20
|
'link_unsubscribe',
|
|
21
21
|
];
|
|
22
|
+
function formatScheduledEmail(d) {
|
|
23
|
+
return [
|
|
24
|
+
`Scheduled email: ${d.request_id}`,
|
|
25
|
+
`State: ${d.state}`,
|
|
26
|
+
d.scheduled_at ? `Scheduled for: ${d.scheduled_at}` : null,
|
|
27
|
+
`From: ${d.from_name ? `${d.from_name} <${d.from}>` : d.from}`,
|
|
28
|
+
d.subject ? `Subject: ${d.subject}` : null,
|
|
29
|
+
`Recipients (${d.num_recipients}): ${d.recipients.join(', ')}`,
|
|
30
|
+
d.tag ? `Tag: ${d.tag}` : null,
|
|
31
|
+
// Only meaningful once sent — this is the id webhook events carry.
|
|
32
|
+
d.transmission_id
|
|
33
|
+
? `Transmission: ${d.transmission_id}`
|
|
34
|
+
: 'Transmission: not sent yet',
|
|
35
|
+
d.failure_reason ? `Failure reason: ${d.failure_reason}` : null,
|
|
36
|
+
]
|
|
37
|
+
.filter((x) => x !== null)
|
|
38
|
+
.join('\n');
|
|
39
|
+
}
|
|
22
40
|
const sendEmailShape = (senderEmailAddress, replierEmailAddress) => ({
|
|
23
41
|
to: z
|
|
24
42
|
.array(z.email().max(255))
|
|
@@ -429,14 +447,14 @@ export function addEmailTools(server, lettr, defaults) {
|
|
|
429
447
|
});
|
|
430
448
|
server.registerTool('schedule-email', {
|
|
431
449
|
title: 'Schedule Email',
|
|
432
|
-
description: `Schedule an email for future delivery. Accepts the same fields as send-email plus a required scheduled_at (ISO 8601, UTC) that is at least 5 minutes in the future and at most
|
|
450
|
+
description: `Schedule an email for future delivery. Accepts the same fields as send-email plus a required scheduled_at (ISO 8601, UTC) that is at least 5 minutes in the future and at most 30 days out.
|
|
433
451
|
|
|
434
|
-
**Returns:**
|
|
452
|
+
**Returns:** The scheduled email, including the \`sch_\` request ID that get-scheduled-email and cancel-scheduled-email take.`,
|
|
435
453
|
inputSchema: {
|
|
436
454
|
...sendEmailShape(senderEmailAddress, replierEmailAddress),
|
|
437
455
|
scheduled_at: z
|
|
438
456
|
.string()
|
|
439
|
-
.describe('ISO 8601 UTC datetime (e.g. 2024-01-16T10:00:00Z). Must be 5+ minutes in the future and within
|
|
457
|
+
.describe('ISO 8601 UTC datetime (e.g. 2024-01-16T10:00:00Z). Must be 5+ minutes in the future and within 30 days.'),
|
|
440
458
|
},
|
|
441
459
|
}, async (input) => {
|
|
442
460
|
const { scheduled_at, ...sendInput } = input;
|
|
@@ -444,64 +462,80 @@ export function addEmailTools(server, lettr, defaults) {
|
|
|
444
462
|
body.scheduled_at = scheduled_at;
|
|
445
463
|
const response = await lettr.post('/emails/scheduled', body);
|
|
446
464
|
return {
|
|
447
|
-
content: [
|
|
448
|
-
{
|
|
449
|
-
type: 'text',
|
|
450
|
-
text: `Email scheduled for ${scheduled_at}. Request ID: ${response.data.request_id}, Accepted: ${response.data.accepted}, Rejected: ${response.data.rejected}`,
|
|
451
|
-
},
|
|
452
|
-
],
|
|
465
|
+
content: [{ type: 'text', text: formatScheduledEmail(response.data) }],
|
|
453
466
|
};
|
|
454
467
|
});
|
|
455
468
|
server.registerTool('get-scheduled-email', {
|
|
456
469
|
title: 'Get Scheduled Email',
|
|
457
|
-
description: 'Retrieve details of a scheduled
|
|
470
|
+
description: 'Retrieve details of a scheduled email, including its state, scheduled_at timestamp, recipients and any events collected so far. Works for every state, not only pending ones — a cancelled or sent email is still readable.',
|
|
458
471
|
inputSchema: {
|
|
459
|
-
|
|
472
|
+
request_id: z
|
|
460
473
|
.string()
|
|
461
474
|
.nonempty()
|
|
462
|
-
.describe('
|
|
475
|
+
.describe('The `sch_` request ID returned by schedule-email'),
|
|
463
476
|
},
|
|
464
|
-
}, async ({
|
|
465
|
-
const response = await lettr.get(`/emails/scheduled/${encodeURIComponent(
|
|
477
|
+
}, async ({ request_id }) => {
|
|
478
|
+
const response = await lettr.get(`/emails/scheduled/${encodeURIComponent(request_id)}`);
|
|
466
479
|
const d = response.data;
|
|
467
480
|
const eventLines = d.events.length === 0
|
|
468
481
|
? '(no events yet)'
|
|
469
482
|
: d.events.map(formatEvent).join('\n');
|
|
470
483
|
return {
|
|
471
484
|
content: [
|
|
472
|
-
{
|
|
473
|
-
type: 'text',
|
|
474
|
-
text: [
|
|
475
|
-
`Transmission: ${d.transmission_id}`,
|
|
476
|
-
`State: ${d.state}`,
|
|
477
|
-
d.scheduled_at ? `Scheduled for: ${d.scheduled_at}` : null,
|
|
478
|
-
`From: ${d.from_name ? `${d.from_name} <${d.from}>` : d.from}`,
|
|
479
|
-
`Subject: ${d.subject}`,
|
|
480
|
-
`Recipients (${d.num_recipients}): ${d.recipients.join(', ')}`,
|
|
481
|
-
]
|
|
482
|
-
.filter((x) => x !== null)
|
|
483
|
-
.join('\n'),
|
|
484
|
-
},
|
|
485
|
+
{ type: 'text', text: formatScheduledEmail(d) },
|
|
485
486
|
{ type: 'text', text: `Events:\n${eventLines}` },
|
|
486
487
|
],
|
|
487
488
|
};
|
|
488
489
|
});
|
|
489
490
|
server.registerTool('cancel-scheduled-email', {
|
|
490
491
|
title: 'Cancel Scheduled Email',
|
|
491
|
-
description: 'Cancel a scheduled email before it is sent. Before using this tool, you MUST confirm with the user that they really want to cancel this
|
|
492
|
+
description: 'Cancel a scheduled email before it is sent. Before using this tool, you MUST confirm with the user that they really want to cancel this email — this action cannot be undone. Only an email still in the `scheduled` state can be cancelled.',
|
|
492
493
|
inputSchema: {
|
|
493
|
-
|
|
494
|
+
request_id: z
|
|
494
495
|
.string()
|
|
495
496
|
.nonempty()
|
|
496
|
-
.describe('
|
|
497
|
+
.describe('The `sch_` request ID to cancel'),
|
|
497
498
|
},
|
|
498
|
-
}, async ({
|
|
499
|
-
|
|
499
|
+
}, async ({ request_id }) => {
|
|
500
|
+
// Cancelling answers with the cancelled email, so report its real state
|
|
501
|
+
// rather than asserting success.
|
|
502
|
+
const response = await lettr.delete(`/emails/scheduled/${encodeURIComponent(request_id)}`);
|
|
503
|
+
return {
|
|
504
|
+
content: [{ type: 'text', text: formatScheduledEmail(response.data) }],
|
|
505
|
+
};
|
|
506
|
+
});
|
|
507
|
+
server.registerTool('list-scheduled-emails', {
|
|
508
|
+
title: 'List Scheduled Emails',
|
|
509
|
+
description: 'List emails waiting to be sent, soonest delivery time first. Use this to find the `sch_` request ID of an email the user describes but cannot name, before getting or cancelling it.',
|
|
510
|
+
inputSchema: {
|
|
511
|
+
status: z
|
|
512
|
+
.enum(['scheduled', 'sending', 'sent', 'cancelled', 'failed'])
|
|
513
|
+
.optional()
|
|
514
|
+
.describe('Only return emails in this state. Omit to see every state.'),
|
|
515
|
+
per_page: z
|
|
516
|
+
.number()
|
|
517
|
+
.int()
|
|
518
|
+
.min(1)
|
|
519
|
+
.max(100)
|
|
520
|
+
.optional()
|
|
521
|
+
.describe('Results per page (1-100, default 25)'),
|
|
522
|
+
page: z.number().int().min(1).optional().describe('Page number'),
|
|
523
|
+
},
|
|
524
|
+
}, async (input) => {
|
|
525
|
+
const response = await lettr.get('/emails/scheduled', input);
|
|
526
|
+
const { scheduled_emails, pagination } = response.data;
|
|
527
|
+
if (scheduled_emails.length === 0) {
|
|
528
|
+
return {
|
|
529
|
+
content: [{ type: 'text', text: 'No scheduled emails found.' }],
|
|
530
|
+
};
|
|
531
|
+
}
|
|
500
532
|
return {
|
|
501
533
|
content: [
|
|
502
534
|
{
|
|
503
535
|
type: 'text',
|
|
504
|
-
text: `Scheduled
|
|
536
|
+
text: `Scheduled emails (total ${pagination.total}, page ${pagination.current_page} of ${pagination.last_page}, page size ${pagination.per_page}):\n\n${scheduled_emails
|
|
537
|
+
.map(formatScheduledEmail)
|
|
538
|
+
.join('\n\n')}`,
|
|
505
539
|
},
|
|
506
540
|
],
|
|
507
541
|
};
|
package/package.json
CHANGED