Skip to content

Commit 0a089cf

Browse files
committed
feat(mcp): add 6 prompts + 4 resources with live llms.txt docs fetching
Prompts (reusable message templates in Claude's UI): - create-campaign: design a full event-triggered email sequence - create-broadcast: draft + send a broadcast to a segment - build-segment: translate natural language audience into segment conditions - analyze-performance: pull metrics and give actionable recommendations - setup-event-tracking: generate SDK code for any tech stack - group-identify: set up company/team group tracking and segmentation Resources (read-only data, accessible to AI agents): - docs://openmail/index → fetches https://openmail.win/docs/llms.txt LIVE (5-min in-process cache — never needs redeployment when docs change) - docs://openmail/page/{path} → extracts relevant section from live llms.txt - docs://openmail/quickref → compact cheat-sheet (endpoints, SDK methods, segment field types, auth patterns) — fits in context without external fetch - openmail://workspace/context → guides AI to use MCP tools for live data No hardcoded doc content — all docs point to the live llms.txt URL so the MCP server stays current without redeployment when documentation changes.
1 parent 0d85d97 commit 0a089cf

4 files changed

Lines changed: 517 additions & 0 deletions

File tree

docs/public/llms.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Base URL: `https://your-api.railway.app/api/v1/`
2929
MCP URL: `https://mcp.openmail.win/mcp` (POST)
3030
Auth: `Authorization: Bearer om_your_workspace_api_key`
3131
Total tools: 29 (Contacts×5, Broadcasts×7, Campaigns×4, Segments×2, Templates×4, Analytics×2, Assets×5)
32+
Prompts: 6 (create-campaign, create-broadcast, build-segment, analyze-performance, setup-event-tracking, group-identify)
33+
Resources: 4 (docs://openmail/index → live llms.txt, docs://openmail/page/{path}, docs://openmail/quickref, openmail://workspace/context)
3234

3335
- [Overview](https://openmail.win/docs/mcp/overview): What the MCP server is, 29 available tools, and example use cases
3436
- [Quick Connect](https://openmail.win/docs/mcp/quickstart): Connect Claude, Cursor, or any AI agent in 2 minutes

mcp/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { registerTemplateTools } from "./tools/templates.js";
88
import { registerSegmentTools } from "./tools/segments.js";
99
import { registerAnalyticsTools } from "./tools/analytics.js";
1010
import { registerAssetTools } from "./tools/assets.js";
11+
import { registerPrompts } from "./prompts.js";
12+
import { registerResources } from "./resources.js";
1113
import { getApiClient } from "./lib/api-client.js";
1214
import pino from "pino";
1315

@@ -31,6 +33,7 @@ app.post("/mcp", async (c) => {
3133
description: "OpenMail — email marketing automation platform API",
3234
});
3335

36+
// Tools (29 total — contacts, broadcasts, campaigns, templates, segments, analytics, assets)
3437
registerContactTools(server, () => client);
3538
registerBroadcastTools(server, () => client);
3639
registerCampaignTools(server, () => client);
@@ -39,6 +42,12 @@ app.post("/mcp", async (c) => {
3942
registerAnalyticsTools(server, () => client);
4043
registerAssetTools(server, () => client);
4144

45+
// Prompts — reusable message templates for common OpenMail workflows
46+
registerPrompts(server);
47+
48+
// Resources — docs index fetched live from llms.txt + quick-reference card
49+
registerResources(server);
50+
4251
const transport = new WebStandardStreamableHTTPServerTransport({
4352
sessionIdGenerator: undefined, // stateless — no session persistence
4453
});

mcp/src/prompts.ts

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
import { z } from "zod";
2+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3+
4+
/**
5+
* Register reusable prompt templates on the MCP server.
6+
* These appear in Claude's "Prompts" panel and help users
7+
* quickly scaffold common OpenMail workflows.
8+
*/
9+
export function registerPrompts(server: McpServer) {
10+
// ── 1. Create a campaign ─────────────────────────────────────────────────────
11+
server.registerPrompt(
12+
"create-campaign",
13+
{
14+
title: "Create Email Campaign",
15+
description:
16+
"Design a complete event-triggered email campaign: define the trigger event, write the email sequence, set up segments, and activate it.",
17+
argsSchema: {
18+
goal: z.string().describe("What should this campaign achieve? e.g. 'onboard new users', 'recover churned customers'"),
19+
triggerEvent: z.string().optional().describe("The event that starts the campaign, e.g. 'user_signed_up'"),
20+
emailCount: z.number().optional().describe("How many emails in the sequence (default: 3)"),
21+
},
22+
},
23+
({ goal, triggerEvent, emailCount = 3 }) => ({
24+
messages: [
25+
{
26+
role: "user",
27+
content: {
28+
type: "text",
29+
text: `You are an email marketing expert using OpenMail. Create a complete email campaign to achieve this goal:
30+
31+
**Goal:** ${goal}
32+
${triggerEvent ? `**Trigger event:** \`${triggerEvent}\`` : ""}
33+
**Email count:** ${emailCount} emails in the sequence
34+
35+
Use the OpenMail MCP tools to:
36+
1. First, call \`list_templates\` to see available templates
37+
2. Create or reuse email templates for each step
38+
3. Create a campaign with the appropriate trigger (event, segment_enter, or manual)
39+
4. Add campaign steps (email + wait nodes) in the right sequence
40+
5. Activate the campaign
41+
42+
Suggest realistic timing between emails and write compelling subject lines and preview text for each.`,
43+
},
44+
},
45+
],
46+
})
47+
);
48+
49+
// ── 2. Write and send a broadcast ───────────────────────────────────────────
50+
server.registerPrompt(
51+
"create-broadcast",
52+
{
53+
title: "Write & Send Broadcast Email",
54+
description:
55+
"Draft a broadcast email to a segment, write compelling copy, review it, and send or schedule it.",
56+
argsSchema: {
57+
topic: z.string().describe("What is this email about? e.g. 'new feature launch', 'Black Friday sale'"),
58+
audience: z.string().optional().describe("Who is the target audience? e.g. 'all pro users', 'inactive users'"),
59+
tone: z.enum(["professional", "casual", "urgent", "celebratory"]).optional().describe("Email tone"),
60+
scheduledAt: z.string().optional().describe("ISO 8601 datetime to schedule, or omit to send immediately"),
61+
},
62+
},
63+
({ topic, audience = "all active contacts", tone = "professional", scheduledAt }) => ({
64+
messages: [
65+
{
66+
role: "user",
67+
content: {
68+
type: "text",
69+
text: `You are an email copywriter and marketer using OpenMail. Write and send a broadcast email.
70+
71+
**Topic:** ${topic}
72+
**Audience:** ${audience}
73+
**Tone:** ${tone}
74+
${scheduledAt ? `**Schedule for:** ${scheduledAt}` : "**Send:** immediately"}
75+
76+
Use the OpenMail MCP tools to:
77+
1. \`list_segments\` — find the right segment for this audience (create one with \`create_segment\` if needed)
78+
2. Write the full HTML email with a compelling subject line, preview text, and clear CTA
79+
3. \`create_broadcast\` — create the draft with the HTML content
80+
4. \`send_broadcast\` (or schedule with \`scheduledAt\`) when ready
81+
5. After sending, call \`get_broadcast_analytics\` to check performance
82+
83+
Write professional, concise copy that respects the subscriber's time. Include an unsubscribe link in the footer.`,
84+
},
85+
},
86+
],
87+
})
88+
);
89+
90+
// ── 3. Build a segment ───────────────────────────────────────────────────────
91+
server.registerPrompt(
92+
"build-segment",
93+
{
94+
title: "Build Audience Segment",
95+
description:
96+
"Define a dynamic audience segment using contact attributes, event history, or group membership.",
97+
argsSchema: {
98+
description: z.string().describe("Describe the audience in plain English, e.g. 'users on pro plan who upgraded in last 30 days'"),
99+
purpose: z.string().optional().describe("How will this segment be used? e.g. 'upsell campaign', 'churn prevention'"),
100+
},
101+
},
102+
({ description, purpose }) => ({
103+
messages: [
104+
{
105+
role: "user",
106+
content: {
107+
type: "text",
108+
text: `You are a data analyst using OpenMail. Build a precise audience segment.
109+
110+
**Audience:** ${description}
111+
${purpose ? `**Purpose:** ${purpose}` : ""}
112+
113+
Use the OpenMail MCP tools to:
114+
1. \`get_analytics\` — understand the current workspace data
115+
2. Translate the description into segment conditions using these field types:
116+
- \`attributes.<key>\` — contact attributes (eq, ne, contains, gt, lt, gte, lte, is_set, is_not_set)
117+
- \`event.<event_name>\` — whether a contact has triggered an event (is_set / is_not_set)
118+
- \`group.<group_type>\` — group membership (eq with group_key value)
119+
- Standard fields: \`email\`, \`firstName\`, \`lastName\`, \`unsubscribed\`
120+
3. \`create_segment\` — create the segment with conditionLogic "and" or "or"
121+
4. \`list_segments\` with the new segment id to verify it returns the expected contacts
122+
123+
Explain your condition logic and estimated audience size.`,
124+
},
125+
},
126+
],
127+
})
128+
);
129+
130+
// ── 4. Analyze performance ───────────────────────────────────────────────────
131+
server.registerPrompt(
132+
"analyze-performance",
133+
{
134+
title: "Analyze Email Performance",
135+
description: "Pull analytics data and provide actionable recommendations to improve open rates, click rates, and conversions.",
136+
argsSchema: {
137+
scope: z.enum(["workspace", "broadcast"]).optional().describe("Analyze the whole workspace or a specific broadcast"),
138+
broadcastId: z.string().optional().describe("Broadcast ID if scope=broadcast"),
139+
},
140+
},
141+
({ scope = "workspace", broadcastId }) => ({
142+
messages: [
143+
{
144+
role: "user",
145+
content: {
146+
type: "text",
147+
text: `You are an email marketing analyst using OpenMail. Analyze performance and give recommendations.
148+
149+
**Scope:** ${scope}${broadcastId ? ` — broadcast ${broadcastId}` : ""}
150+
151+
Use the OpenMail MCP tools to:
152+
${
153+
scope === "broadcast" && broadcastId
154+
? `1. \`get_broadcast_analytics\` for broadcast ${broadcastId}
155+
2. \`get_analytics\` for workspace baseline comparison`
156+
: `1. \`get_analytics\` for 30-day workspace overview
157+
2. \`list_broadcasts\` — review recent broadcasts`
158+
}
159+
160+
Then provide:
161+
- **Summary**: Key metrics (open rate %, click rate %, unsubscribes)
162+
- **Benchmarks**: Compare to industry averages (SaaS: ~25% open, ~3% click)
163+
- **Top issues**: What's underperforming and why
164+
- **3 concrete actions**: Specific changes to improve results (subject lines, send times, segmentation, etc.)`,
165+
},
166+
},
167+
],
168+
})
169+
);
170+
171+
// ── 5. Track events setup ────────────────────────────────────────────────────
172+
server.registerPrompt(
173+
"setup-event-tracking",
174+
{
175+
title: "Set Up Event Tracking",
176+
description: "Generate the exact code to track user events from your app to trigger OpenMail campaigns.",
177+
argsSchema: {
178+
stack: z.enum(["nextjs", "react", "node", "python", "curl"]).describe("Your tech stack"),
179+
events: z.string().describe("Comma-separated events to track, e.g. 'user_signed_up, plan_upgraded, feature_used'"),
180+
apiUrl: z.string().optional().describe("Your OpenMail API URL (defaults to https://api.openmail.win)"),
181+
},
182+
},
183+
({ stack, events, apiUrl = "https://api.openmail.win" }) => ({
184+
messages: [
185+
{
186+
role: "user",
187+
content: {
188+
type: "text",
189+
text: `Generate complete, production-ready code to track these events from a ${stack} app to OpenMail.
190+
191+
**Stack:** ${stack}
192+
**Events to track:** ${events}
193+
**API URL:** ${apiUrl}
194+
195+
Requirements:
196+
- Use the @openmail/sdk package (npm install @openmail/sdk)
197+
- Show identify() call on user login/signup
198+
- Show track() calls for each event with relevant properties
199+
- Show group() call if tracking workspace/org memberships
200+
- Include proper error handling and flush() before process exit
201+
- Add comments explaining WHAT triggers each event
202+
203+
For Next.js: show both client-side (useTrack hook) and server-side (serverTrack) patterns.
204+
For Node: show the singleton pattern with flush on shutdown.
205+
206+
The API key should come from environment variables (OPENMAIL_API_KEY server-side, NEXT_PUBLIC_OPENMAIL_KEY client-side).`,
207+
},
208+
},
209+
],
210+
})
211+
);
212+
213+
// ── 6. Group identify workflow ───────────────────────────────────────────────
214+
server.registerPrompt(
215+
"group-identify",
216+
{
217+
title: "Group Identify Workflow",
218+
description: "Set up group/organization tracking: link contacts to companies or teams, and use group membership in segments.",
219+
argsSchema: {
220+
groupType: z.string().optional().describe("Type of group: company, team, project (default: company)"),
221+
useCase: z.string().describe("What are you grouping? e.g. 'SaaS customers by company', 'users by team within a company'"),
222+
},
223+
},
224+
({ groupType = "company", useCase }) => ({
225+
messages: [
226+
{
227+
role: "user",
228+
content: {
229+
type: "text",
230+
text: `You are setting up group/organization tracking in OpenMail.
231+
232+
**Group type:** ${groupType}
233+
**Use case:** ${useCase}
234+
235+
Use the OpenMail MCP tools to:
236+
1. Check existing groups: \`list_contacts\` to understand your data model
237+
2. Create a sample group to test the workflow — use the \`create_group\` tool if available or the /api/v1/groups REST endpoint
238+
3. Link contacts to the group
239+
4. Create a segment that filters by group membership:
240+
- Field: \`group.${groupType}\`
241+
- Operator: \`eq\` with the group key as value
242+
5. Verify the segment returns the right contacts
243+
244+
Also explain how to track group identify from code:
245+
\`\`\`ts
246+
// Server-side
247+
await openmail.group("acme-corp", { name: "Acme Corp", plan: "enterprise" }, {
248+
userId: "alice@example.com",
249+
groupType: "${groupType}",
250+
});
251+
252+
// Then use in segments:
253+
// { field: "group.${groupType}", operator: "eq", value: "acme-corp" }
254+
\`\`\``,
255+
},
256+
},
257+
],
258+
})
259+
);
260+
}

0 commit comments

Comments
 (0)