@zyphr-dev/mcp-server 0.1.3 → 0.1.5

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 CHANGED
@@ -86,6 +86,17 @@ The server fails fast with a clear stderr message if `ZYPHR_API_KEY` is missing.
86
86
  | `create_webhook` | Register a new webhook endpoint subscribed to one or more event types. |
87
87
  | `get_webhook_deliveries` | Inspect delivery history for a webhook (filter by status, event type, or date range). |
88
88
 
89
+ ### Domains
90
+
91
+ | Tool | Description |
92
+ | --- | --- |
93
+ | `add_domain` | Register a sending domain and return the DNS records (TXT + CNAME) to publish. Idempotent — calling with an already-registered domain returns the existing record. |
94
+ | `list_domains` | List sending domains with verification status and DNS records. |
95
+ | `get_domain` | Get a sending domain by ID, including per-record `verified` flags and `records_verified` / `records_total` counters. |
96
+ | `verify_domain` | Queue an asynchronous DNS verification check. Poll `get_domain` (or subscribe to the `domain.verified` webhook event) for terminal status. |
97
+
98
+ **Why no `delete_domain`:** This server intentionally does not expose destructive `delete_*` operations through MCP. A bad model call could detach an SES identity, invalidate scheduled sends, and break templates hardcoded to a sender. If your workflow needs scripted domain teardown, call `zyphr.domains.deleteDomain(id)` from the Node SDK directly — that's a deliberate developer action, not an AI tool call. The dashboard works for one-off cleanup.
99
+
89
100
  ## Safety
90
101
 
91
102
  Two environment variables let you scope what the AI is allowed to do:
@@ -109,6 +120,8 @@ Once the server is connected, your AI client can act on prompts like:
109
120
  - "Send an SMS to +14155551234 letting them know their appointment is confirmed."
110
121
  - "Drop an inbox message for subscriber `usr_42` with title 'New report ready' and an action URL to the report page."
111
122
  - "Show me the last 20 webhook deliveries for webhook `whk_abc` that failed in the past hour and tell me why."
123
+ - "Add `sprint.example.com` as a sending domain and show me the DNS records I need to publish."
124
+ - "Has `sprint.example.com` finished verifying? If not, tell me which records are still pending."
112
125
 
113
126
  Errors from the Zyphr API are surfaced verbatim as MCP errors, so the AI can react to validation failures, rate limits, or auth problems without guessing.
114
127
 
package/dist/index.js CHANGED
@@ -251,6 +251,20 @@ var getWebhookDeliveriesShape = {
251
251
  limit: z.number().int().positive().max(200).optional(),
252
252
  offset: z.number().int().nonnegative().optional()
253
253
  };
254
+ var addDomainShape = {
255
+ domain: z.string().min(1).describe(
256
+ 'The sending domain to add (e.g. "mail.example.com"). Will be lowercased and trimmed. Idempotent \u2014 calling with an already-registered domain returns the existing record.'
257
+ )
258
+ };
259
+ var listDomainsShape = {};
260
+ var getDomainShape = {
261
+ id: z.string().min(1).describe("Domain ID (UUID)")
262
+ };
263
+ var verifyDomainShape = {
264
+ id: z.string().min(1).describe(
265
+ "Domain ID (UUID). Queues an asynchronous DNS verification check; poll get_domain (or subscribe to the domain.verified webhook) for terminal status."
266
+ )
267
+ };
254
268
  var sendInboxMessageShape = {
255
269
  subscriberId: z.string().min(1).describe("Subscriber to deliver the in-app message to"),
256
270
  title: z.string().min(1),
@@ -266,6 +280,74 @@ var sendInboxMessageShape = {
266
280
  expiresAt: z.string().datetime().optional()
267
281
  };
268
282
 
283
+ // src/tools/domains.ts
284
+ function registerDomainTools(server, guards) {
285
+ if (isToolEnabled({ name: "add_domain", mutates: true }, guards)) {
286
+ server.registerTool(
287
+ "add_domain",
288
+ {
289
+ title: "Add a sending domain",
290
+ description: "Register a new sending domain on the account. Returns the full set of DNS records (TXT + CNAME) the AI agent should publish to its DNS provider (Route53, Cloudflare, etc.) before email can send from this domain. Idempotent: calling with an already-registered domain returns the existing record.",
291
+ inputSchema: addDomainShape
292
+ },
293
+ async (args) => {
294
+ return runTool(async () => {
295
+ const zyphr = getZyphrClient();
296
+ return await zyphr.domains.addDomain({ domain: args.domain });
297
+ });
298
+ }
299
+ );
300
+ }
301
+ if (isToolEnabled({ name: "list_domains", mutates: false }, guards)) {
302
+ server.registerTool(
303
+ "list_domains",
304
+ {
305
+ title: "List sending domains",
306
+ description: "List sending domains on the current project, with verification status and DNS records.",
307
+ inputSchema: listDomainsShape
308
+ },
309
+ async () => {
310
+ return runTool(async () => {
311
+ const zyphr = getZyphrClient();
312
+ return await zyphr.domains.listDomains();
313
+ });
314
+ }
315
+ );
316
+ }
317
+ if (isToolEnabled({ name: "get_domain", mutates: false }, guards)) {
318
+ server.registerTool(
319
+ "get_domain",
320
+ {
321
+ title: "Get a sending domain",
322
+ description: 'Retrieve a single sending domain by ID, including per-record `verified` flags and `records_verified` / `records_total` counters. Useful for reasoning about partial verification (e.g. "SPF verified, DKIM #2 still pending").',
323
+ inputSchema: getDomainShape
324
+ },
325
+ async (args) => {
326
+ return runTool(async () => {
327
+ const zyphr = getZyphrClient();
328
+ return await zyphr.domains.getDomain(args.id);
329
+ });
330
+ }
331
+ );
332
+ }
333
+ if (isToolEnabled({ name: "verify_domain", mutates: true }, guards)) {
334
+ server.registerTool(
335
+ "verify_domain",
336
+ {
337
+ title: "Trigger domain verification",
338
+ description: "Queue an asynchronous DNS verification check for a sending domain. Returns a job_id immediately; poll `get_domain` (or subscribe to the `domain.verified` webhook event) for terminal status. Returns a 409 error if a verification is already in progress for this domain.",
339
+ inputSchema: verifyDomainShape
340
+ },
341
+ async (args) => {
342
+ return runTool(async () => {
343
+ const zyphr = getZyphrClient();
344
+ return await zyphr.domains.verifyDomain(args.id);
345
+ });
346
+ }
347
+ );
348
+ }
349
+ }
350
+
269
351
  // src/tools/send.ts
270
352
  function normalizeEmailAddress(value) {
271
353
  if (typeof value === "string") return { email: value };
@@ -2289,6 +2371,7 @@ function createServer() {
2289
2371
  registerTemplateTools(server, guards);
2290
2372
  registerSubscriberTools(server, guards);
2291
2373
  registerWebhookTools(server, guards);
2374
+ registerDomainTools(server, guards);
2292
2375
  registerIntegrationTools(server, guards);
2293
2376
  return server;
2294
2377
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/server.ts","../src/config.ts","../src/client.ts","../src/result.ts","../src/schemas.ts","../src/tools/send.ts","../src/tools/subscribers.ts","../src/tools/templates.ts","../src/integration/quickstart/email.ts","../src/integration/quickstart/inbox.ts","../src/integration/quickstart/push.ts","../src/integration/quickstart/sms.ts","../src/integration/quickstart/webhook.ts","../src/integration/quickstart/index.ts","../src/integration/sdk-snippets.ts","../src/tools/integration.ts","../src/tools/webhooks.ts"],"sourcesContent":["import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { createServer } from './server.js';\nimport { getBaseUrl } from './client.js';\n\nasync function main(): Promise<void> {\n // Touch the env early so misconfiguration fails fast with a clear message.\n if (!process.env.ZYPHR_API_KEY) {\n process.stderr.write(\n '[zyphr-mcp] ZYPHR_API_KEY is not set. Provide a zy_live_* or zy_test_* key via the `env` block of your MCP client config.\\n',\n );\n process.exit(1);\n }\n\n const server = createServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n process.stderr.write(`[zyphr-mcp] connected (base=${getBaseUrl()})\\n`);\n}\n\nmain().catch((err) => {\n process.stderr.write(`[zyphr-mcp] fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\\n`);\n process.exit(1);\n});\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { loadToolGuards } from './config.js';\nimport { registerSendTools } from './tools/send.js';\nimport { registerSubscriberTools } from './tools/subscribers.js';\nimport { registerTemplateTools } from './tools/templates.js';\nimport { registerIntegrationTools } from './tools/integration.js';\nimport { registerWebhookTools } from './tools/webhooks.js';\n\nexport const SERVER_NAME = 'zyphr';\nexport const SERVER_VERSION = '0.1.0';\n\nexport function createServer(): McpServer {\n const server = new McpServer(\n { name: SERVER_NAME, version: SERVER_VERSION },\n { capabilities: { tools: {} } },\n );\n\n const guards = loadToolGuards();\n registerSendTools(server, guards);\n registerTemplateTools(server, guards);\n registerSubscriberTools(server, guards);\n registerWebhookTools(server, guards);\n registerIntegrationTools(server, guards);\n\n return server;\n}\n","export interface ToolGuards {\n readOnly: boolean;\n allowedTools: Set<string> | null;\n}\n\nexport function loadToolGuards(): ToolGuards {\n const readOnly = process.env.ZYPHR_READ_ONLY === 'true' || process.env.ZYPHR_READ_ONLY === '1';\n const raw = process.env.ZYPHR_ALLOWED_TOOLS;\n const allowedTools = raw\n ? new Set(\n raw\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean),\n )\n : null;\n return { readOnly, allowedTools };\n}\n\nexport interface ToolDefinition {\n name: string;\n mutates: boolean;\n}\n\nexport function isToolEnabled(tool: ToolDefinition, guards: ToolGuards): boolean {\n if (guards.allowedTools) {\n return guards.allowedTools.has(tool.name);\n }\n if (guards.readOnly && tool.mutates) {\n return false;\n }\n return true;\n}\n","import { Zyphr } from '@zyphr-dev/node-sdk';\n\nconst DEFAULT_BASE_URL = 'https://api.zyphr.dev/v1';\n\nlet cached: Zyphr | undefined;\n\nexport function getZyphrClient(): Zyphr {\n if (cached) return cached;\n\n const apiKey = process.env.ZYPHR_API_KEY;\n if (!apiKey) {\n process.stderr.write(\n '[zyphr-mcp] ZYPHR_API_KEY is not set. Provide a zy_live_* or zy_test_* key via the `env` block of your MCP client config.\\n',\n );\n process.exit(1);\n }\n\n const baseUrl = process.env.ZYPHR_BASE_URL || DEFAULT_BASE_URL;\n cached = new Zyphr({ apiKey, baseUrl });\n return cached;\n}\n\nexport function getBaseUrl(): string {\n return process.env.ZYPHR_BASE_URL || DEFAULT_BASE_URL;\n}\n","import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { ZyphrError, ZyphrRateLimitError } from '@zyphr-dev/node-sdk';\n\nexport function toolResult(data: unknown): CallToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],\n };\n}\n\nexport async function runTool(fn: () => Promise<unknown>): Promise<CallToolResult> {\n try {\n const data = await fn();\n return toolResult(data);\n } catch (err: unknown) {\n return await renderApiError(err);\n }\n}\n\nfunction errorPayload(content: Record<string, unknown>): CallToolResult {\n return {\n isError: true,\n content: [{ type: 'text', text: JSON.stringify(content, null, 2) }],\n };\n}\n\nasync function renderApiError(err: unknown): Promise<CallToolResult> {\n // Typed SDK errors — surface the full envelope so the AI can act on it.\n if (err instanceof ZyphrError) {\n const payload: Record<string, unknown> = {\n name: err.name,\n message: err.message,\n status: err.status,\n };\n if (err.code) payload.code = err.code;\n if (err.requestId) payload.requestId = err.requestId;\n if (err.details) payload.details = err.details;\n if (err instanceof ZyphrRateLimitError && err.retryAfter !== undefined) {\n payload.retryAfter = err.retryAfter;\n }\n return errorPayload(payload);\n }\n\n // Raw Response error (rare — SDK middleware usually parses first).\n if (err && typeof err === 'object' && 'response' in err) {\n const response = (err as { response?: Response }).response;\n if (response && typeof response.text === 'function') {\n try {\n const text = await response.text();\n let parsed: unknown = text;\n try {\n parsed = JSON.parse(text);\n } catch {\n /* keep raw text */\n }\n return errorPayload({ status: response.status, body: parsed });\n } catch {\n /* fall through to generic message */\n }\n }\n }\n\n const message = err instanceof Error ? err.message : String(err);\n const name = err instanceof Error ? err.name : 'Error';\n return errorPayload({ name, message });\n}\n","import { z } from 'zod';\n\nconst emailAddress = z.object({\n email: z.string().email(),\n name: z.string().optional(),\n});\n\nconst recipient = z.union([z.string().email(), emailAddress]);\n\nexport const sendEmailShape = {\n to: z\n .union([recipient, z.array(recipient).min(1)])\n .describe('Recipient email address (string or {email,name}) or an array of them'),\n from: z\n .union([z.string().email(), emailAddress])\n .optional()\n .describe('Sender address. Defaults to the account-level \"from\" address.'),\n replyTo: z.union([z.string().email(), emailAddress]).optional(),\n cc: z.array(z.string().email()).optional(),\n bcc: z.array(z.string().email()).optional(),\n subject: z.string().min(1),\n html: z.string().optional().describe('Rendered HTML body. Mutually exclusive with templateId.'),\n text: z.string().optional().describe('Plain-text body. Mutually exclusive with templateId.'),\n templateId: z.string().optional().describe('Template ID. When set, html/text are ignored.'),\n templateData: z\n .record(z.unknown())\n .optional()\n .describe('Variables to interpolate into the template'),\n tags: z.array(z.string()).optional(),\n metadata: z.record(z.unknown()).optional(),\n subscriberId: z.string().optional(),\n category: z.string().optional(),\n scheduledAt: z\n .string()\n .datetime()\n .optional()\n .describe('ISO 8601 timestamp for scheduled delivery'),\n} as const;\n\nexport const sendPushShape = {\n userId: z.string().optional().describe('Send to all devices for this user/subscriber'),\n deviceId: z.string().optional().describe('Send to a specific device only'),\n title: z.string().optional(),\n body: z.string().optional(),\n data: z.record(z.unknown()).optional().describe('Custom data payload delivered to the device'),\n badge: z.number().int().nonnegative().optional(),\n sound: z.string().optional(),\n imageUrl: z.string().url().optional(),\n contentAvailable: z.boolean().optional().describe('Silent/background push'),\n tags: z.array(z.string()).optional(),\n metadata: z.record(z.unknown()).optional(),\n collapseKey: z.string().optional(),\n subscriberId: z.string().optional(),\n subscriberExternalId: z.string().optional(),\n category: z.string().optional(),\n force: z.boolean().optional().describe('Skip subscriber preference checks'),\n sendAt: z.string().datetime().optional(),\n delay: z.number().int().nonnegative().optional(),\n} as const;\n\nexport const sendSmsShape = {\n to: z.string().min(1).describe('Recipient phone number in E.164 format (e.g. +14155551234)'),\n from: z.string().optional().describe('Sender phone number or sender ID'),\n body: z.string().min(1),\n subscriberId: z.string().optional(),\n scheduledAt: z.string().datetime().optional(),\n metadata: z.record(z.unknown()).optional(),\n} as const;\n\n// Integration\n\nexport const sdkLanguages = ['node', 'python', 'ruby', 'go', 'php', 'csharp'] as const;\nexport type SdkLanguage = (typeof sdkLanguages)[number];\n\nexport const quickstartChannels = ['email', 'push', 'sms', 'inbox', 'webhook'] as const;\nexport type QuickstartChannel = (typeof quickstartChannels)[number];\n\nexport const getQuickstartShape = {\n channel: z\n .enum(quickstartChannels)\n .describe('Which Zyphr channel to wire up'),\n language: z\n .enum(['node', 'python', 'ruby', 'go', 'php', 'csharp'])\n .describe('Target language for the integration'),\n framework: z\n .string()\n .optional()\n .describe(\n 'Optional framework hint (e.g. \"express\", \"nextjs\", \"flask\", \"fastapi\", \"rails\", \"gin\", \"laravel\", \"aspnetcore\"). Falls back to plain SDK code when unrecognized.',\n ),\n} as const;\n\nexport const getSdkInstallShape = {\n language: z\n .enum(sdkLanguages)\n .describe('Target language for the integration'),\n packageManager: z\n .string()\n .optional()\n .describe(\n 'Optional package manager override (e.g. \"yarn\" instead of \"npm\"). When recognized, only that manager is returned; otherwise the full list is returned.',\n ),\n} as const;\n\n// Templates\n\nexport const listTemplatesShape = {\n limit: z.number().int().positive().max(200).optional(),\n offset: z.number().int().nonnegative().optional(),\n} as const;\n\nexport const getTemplateShape = {\n id: z.string().min(1).describe('Template ID'),\n} as const;\n\nexport const renderTemplateShape = {\n id: z.string().min(1).describe('Template ID'),\n variables: z\n .record(z.unknown())\n .describe('Key/value variables to interpolate into the template'),\n} as const;\n\nexport const createTemplateShape = {\n name: z.string().min(1),\n description: z.string().optional(),\n subject: z.string().optional().describe('Default subject (email templates)'),\n html: z.string().optional(),\n text: z.string().optional(),\n} as const;\n\n// Subscribers\n\nexport const findSubscriberShape = {\n externalId: z\n .string()\n .min(1)\n .describe('Subscriber external ID (your application user/customer ID)'),\n} as const;\n\nexport const listSubscribersShape = {\n status: z.enum(['active', 'inactive']).optional(),\n email: z.string().email().optional(),\n limit: z.number().int().positive().max(200).optional(),\n offset: z.number().int().nonnegative().optional(),\n} as const;\n\nexport const createSubscriberShape = {\n externalId: z.string().min(1).describe('Your application user/customer ID'),\n email: z.string().email().optional(),\n phone: z.string().optional(),\n name: z.string().optional(),\n avatarUrl: z.string().url().optional(),\n timezone: z.string().optional(),\n locale: z.string().optional(),\n metadata: z.record(z.unknown()).optional(),\n} as const;\n\nexport const updateSubscriberShape = {\n id: z.string().min(1).describe('Zyphr subscriber ID'),\n email: z.string().email().nullable().optional(),\n phone: z.string().nullable().optional(),\n name: z.string().nullable().optional(),\n avatarUrl: z.string().url().nullable().optional(),\n timezone: z.string().optional(),\n locale: z.string().optional(),\n metadata: z.record(z.unknown()).optional(),\n status: z.enum(['active', 'inactive']).optional(),\n} as const;\n\nexport const setSubscriberPreferencesShape = {\n id: z.string().min(1).describe('Zyphr subscriber ID'),\n preferences: z\n .array(\n z.object({\n categoryId: z.string().optional(),\n channel: z.string().optional().describe('email | push | sms | in_app'),\n enabled: z.boolean().optional(),\n }),\n )\n .min(1),\n} as const;\n\n// Webhooks\n\nexport const listWebhooksShape = {\n limit: z.number().int().positive().max(200).optional(),\n offset: z.number().int().nonnegative().optional(),\n} as const;\n\nexport const createWebhookShape = {\n url: z.string().url().describe('Receiver URL that Zyphr will POST events to'),\n events: z\n .array(z.string().min(1))\n .min(1)\n .describe('Event types to subscribe to (e.g. [\"email.*\", \"subscriber.created\"])'),\n description: z.string().optional(),\n secret: z\n .string()\n .optional()\n .describe('Optional secret used to sign payloads. If omitted, Zyphr generates one.'),\n metadata: z.record(z.unknown()).optional(),\n headers: z.record(z.string()).optional().describe('Custom headers to send with every delivery'),\n version: z.string().optional(),\n rateLimit: z.number().int().positive().optional(),\n} as const;\n\nexport const getWebhookDeliveriesShape = {\n webhookId: z.string().min(1).describe('Webhook endpoint ID'),\n status: z.enum(['pending', 'delivering', 'delivered', 'failed', 'exhausted']).optional(),\n eventType: z.string().optional(),\n search: z.string().optional(),\n startDate: z.string().datetime().optional(),\n endDate: z.string().datetime().optional(),\n limit: z.number().int().positive().max(200).optional(),\n offset: z.number().int().nonnegative().optional(),\n} as const;\n\nexport const sendInboxMessageShape = {\n subscriberId: z.string().min(1).describe('Subscriber to deliver the in-app message to'),\n title: z.string().min(1),\n body: z.string().optional(),\n actionUrl: z.string().url().optional(),\n actionLabel: z.string().optional(),\n imageUrl: z.string().url().optional(),\n icon: z.string().optional(),\n category: z.string().optional(),\n priority: z.enum(['low', 'normal', 'high', 'urgent']).optional(),\n data: z.record(z.unknown()).optional(),\n tags: z.array(z.string()).optional(),\n expiresAt: z.string().datetime().optional(),\n} as const;\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { getZyphrClient } from '../client.js';\nimport { isToolEnabled, type ToolGuards } from '../config.js';\nimport { runTool } from '../result.js';\nimport {\n sendEmailShape,\n sendInboxMessageShape,\n sendPushShape,\n sendSmsShape,\n} from '../schemas.js';\n\nfunction normalizeEmailAddress(value: unknown): { email: string; name?: string } | undefined {\n if (typeof value === 'string') return { email: value };\n if (value && typeof value === 'object' && 'email' in value) {\n return value as { email: string; name?: string };\n }\n return undefined;\n}\n\nfunction normalizeRecipients(to: unknown): { email: string; name?: string }[] {\n if (Array.isArray(to)) {\n return to.map(normalizeEmailAddress).filter((v): v is { email: string } => Boolean(v));\n }\n const one = normalizeEmailAddress(to);\n return one ? [one] : [];\n}\n\nexport function registerSendTools(server: McpServer, guards: ToolGuards): void {\n if (isToolEnabled({ name: 'send_email', mutates: true }, guards)) {\n server.registerTool(\n 'send_email',\n {\n title: 'Send email',\n description:\n 'Send a transactional email via Zyphr. Use either html/text OR templateId+templateData, not both.',\n inputSchema: sendEmailShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.emails.sendEmail({\n to: normalizeRecipients(args.to),\n from: normalizeEmailAddress(args.from),\n replyTo: normalizeEmailAddress(args.replyTo),\n cc: args.cc,\n bcc: args.bcc,\n subject: args.subject,\n html: args.html,\n text: args.text,\n templateId: args.templateId,\n templateData: args.templateData,\n tags: args.tags,\n metadata: args.metadata,\n subscriberId: args.subscriberId,\n category: args.category,\n scheduledAt: args.scheduledAt ? new Date(args.scheduledAt) : undefined,\n });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'send_push', mutates: true }, guards)) {\n server.registerTool(\n 'send_push',\n {\n title: 'Send push notification',\n description:\n 'Send a push notification. Target one of: userId (all devices for a user), deviceId (specific device), subscriberId, or subscriberExternalId.',\n inputSchema: sendPushShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.push.sendPush({\n userId: args.userId,\n deviceId: args.deviceId,\n title: args.title,\n body: args.body,\n data: args.data,\n badge: args.badge,\n sound: args.sound,\n imageUrl: args.imageUrl,\n contentAvailable: args.contentAvailable,\n tags: args.tags,\n metadata: args.metadata,\n collapseKey: args.collapseKey,\n subscriberId: args.subscriberId,\n subscriberExternalId: args.subscriberExternalId,\n category: args.category,\n force: args.force,\n sendAt: args.sendAt ? new Date(args.sendAt) : undefined,\n delay: args.delay,\n });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'send_sms', mutates: true }, guards)) {\n server.registerTool(\n 'send_sms',\n {\n title: 'Send SMS',\n description: 'Send an SMS message via Zyphr. The recipient must be in E.164 format.',\n inputSchema: sendSmsShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.sms.sendSms({\n to: args.to,\n from: args.from,\n body: args.body,\n subscriberId: args.subscriberId,\n scheduledAt: args.scheduledAt ? new Date(args.scheduledAt) : undefined,\n metadata: args.metadata,\n });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'send_inbox_message', mutates: true }, guards)) {\n server.registerTool(\n 'send_inbox_message',\n {\n title: 'Send in-app inbox message',\n description: 'Deliver an in-app inbox notification to a Zyphr subscriber.',\n inputSchema: sendInboxMessageShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.inbox.sendInApp({\n subscriberId: args.subscriberId,\n title: args.title,\n body: args.body,\n actionUrl: args.actionUrl,\n actionLabel: args.actionLabel,\n imageUrl: args.imageUrl,\n icon: args.icon,\n category: args.category,\n priority: args.priority,\n data: args.data,\n tags: args.tags,\n expiresAt: args.expiresAt ? new Date(args.expiresAt) : undefined,\n });\n });\n },\n );\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { getZyphrClient } from '../client.js';\nimport { isToolEnabled, type ToolGuards } from '../config.js';\nimport { runTool } from '../result.js';\nimport {\n createSubscriberShape,\n findSubscriberShape,\n listSubscribersShape,\n setSubscriberPreferencesShape,\n updateSubscriberShape,\n} from '../schemas.js';\n\nexport function registerSubscriberTools(server: McpServer, guards: ToolGuards): void {\n if (isToolEnabled({ name: 'find_subscriber', mutates: false }, guards)) {\n server.registerTool(\n 'find_subscriber',\n {\n title: 'Find subscriber by external ID',\n description:\n 'Look up a subscriber by the external ID you assigned (typically your application user/customer ID).',\n inputSchema: findSubscriberShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.subscribers.getSubscriberByExternalId(args.externalId);\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'list_subscribers', mutates: false }, guards)) {\n server.registerTool(\n 'list_subscribers',\n {\n title: 'List subscribers',\n description: 'List subscribers, optionally filtered by status or email.',\n inputSchema: listSubscribersShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.subscribers.listSubscribers(\n args.status,\n args.email,\n args.limit,\n args.offset,\n );\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'create_subscriber', mutates: true }, guards)) {\n server.registerTool(\n 'create_subscriber',\n {\n title: 'Create subscriber',\n description: 'Create a new subscriber. `externalId` must be unique within your account.',\n inputSchema: createSubscriberShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.subscribers.createSubscriber({\n externalId: args.externalId,\n email: args.email,\n phone: args.phone,\n name: args.name,\n avatarUrl: args.avatarUrl,\n timezone: args.timezone,\n locale: args.locale,\n metadata: args.metadata,\n });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'update_subscriber', mutates: true }, guards)) {\n server.registerTool(\n 'update_subscriber',\n {\n title: 'Update subscriber',\n description:\n 'Update a subscriber by Zyphr ID. Pass null for email/phone/name/avatarUrl to clear those fields.',\n inputSchema: updateSubscriberShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.subscribers.updateSubscriber(args.id, {\n email: args.email ?? undefined,\n phone: args.phone ?? undefined,\n name: args.name ?? undefined,\n avatarUrl: args.avatarUrl ?? undefined,\n timezone: args.timezone,\n locale: args.locale,\n metadata: args.metadata,\n status: args.status,\n });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'set_subscriber_preferences', mutates: true }, guards)) {\n server.registerTool(\n 'set_subscriber_preferences',\n {\n title: 'Set subscriber preferences',\n description:\n 'Set notification preferences for a subscriber. Each preference targets a category and/or channel and toggles `enabled`.',\n inputSchema: setSubscriberPreferencesShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.subscribers.setSubscriberPreferences(args.id, {\n preferences: args.preferences,\n });\n });\n },\n );\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { getZyphrClient } from '../client.js';\nimport { isToolEnabled, type ToolGuards } from '../config.js';\nimport { runTool } from '../result.js';\nimport {\n createTemplateShape,\n getTemplateShape,\n listTemplatesShape,\n renderTemplateShape,\n} from '../schemas.js';\n\nexport function registerTemplateTools(server: McpServer, guards: ToolGuards): void {\n if (isToolEnabled({ name: 'list_templates', mutates: false }, guards)) {\n server.registerTool(\n 'list_templates',\n {\n title: 'List templates',\n description: 'List notification templates in the account.',\n inputSchema: listTemplatesShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.templates.listTemplates(args.limit, args.offset);\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'get_template', mutates: false }, guards)) {\n server.registerTool(\n 'get_template',\n {\n title: 'Get template',\n description: 'Fetch a single template by ID.',\n inputSchema: getTemplateShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.templates.getTemplate(args.id);\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'render_template', mutates: false }, guards)) {\n server.registerTool(\n 'render_template',\n {\n title: 'Render template',\n description:\n 'Preview a template with the given variables WITHOUT sending. Returns the rendered subject/html/text so the AI can show the user what would be sent.',\n inputSchema: renderTemplateShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.templates.renderTemplate(args.id, { variables: args.variables });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'create_template', mutates: true }, guards)) {\n server.registerTool(\n 'create_template',\n {\n title: 'Create template',\n description: 'Create a new notification template.',\n inputSchema: createTemplateShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.templates.createTemplate({\n name: args.name,\n description: args.description,\n subject: args.subject,\n html: args.html,\n text: args.text,\n });\n });\n },\n );\n }\n}\n","import type { QuickstartChannelMap } from '../quickstart-types.js';\n\nconst DOCS = 'https://docs.zyphr.dev/channels/email';\n\nconst ENV: string[] = ['ZYPHR_API_KEY'];\n\nconst NEXT_STEPS = [\n 'Add ZYPHR_API_KEY to your .env file (run get_sdk_install_for_language to confirm the install).',\n 'Wire the new service into your app entrypoint.',\n 'Verify your sender domain in the Zyphr dashboard before sending to real recipients.',\n];\n\nexport const emailChannel: QuickstartChannelMap = {\n node: {\n sdk: {\n channel: 'email',\n language: 'node',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'src/lib/zyphr.ts',\n purpose: 'Zyphr SDK client singleton',\n contents:\n \"import { Zyphr } from '@zyphr-dev/node-sdk';\\n\\n\" +\n 'export const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\\n',\n overwrite: false,\n },\n {\n path: 'src/services/notify.ts',\n purpose: 'Sends a transactional email through Zyphr',\n contents:\n \"import { zyphr } from '../lib/zyphr.js';\\n\\n\" +\n 'export async function sendWelcomeEmail(to: string, name: string) {\\n' +\n ' return await zyphr.emails.sendEmail({\\n' +\n ' to: [{ email: to, name }],\\n' +\n ' subject: `Welcome, ${name}!`,\\n' +\n \" html: `<h1>Welcome aboard, ${name}!</h1><p>Glad you're here.</p>`,\\n\" +\n ' });\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n express: {\n channel: 'email',\n language: 'node',\n framework: 'express',\n variant: 'sdk',\n files: [\n {\n path: 'src/lib/zyphr.ts',\n purpose: 'Zyphr SDK client singleton',\n contents:\n \"import { Zyphr } from '@zyphr-dev/node-sdk';\\n\\n\" +\n 'export const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\\n',\n overwrite: false,\n },\n {\n path: 'src/routes/notify.ts',\n purpose: 'Express route that sends a welcome email',\n contents:\n \"import { Router } from 'express';\\n\" +\n \"import { zyphr } from '../lib/zyphr.js';\\n\\n\" +\n 'export const notifyRouter = Router();\\n\\n' +\n \"notifyRouter.post('/notify', async (req, res, next) => {\\n\" +\n ' try {\\n' +\n ' const { to, name } = req.body as { to: string; name: string };\\n' +\n ' const result = await zyphr.emails.sendEmail({\\n' +\n ' to: [{ email: to, name }],\\n' +\n ' subject: `Welcome, ${name}!`,\\n' +\n ' html: `<h1>Welcome, ${name}!</h1>`,\\n' +\n ' });\\n' +\n ' res.json(result);\\n' +\n ' } catch (err) {\\n' +\n ' next(err);\\n' +\n ' }\\n' +\n '});\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n \"Mount the router in app.ts: app.use('/api', notifyRouter)\",\n 'Test: curl -X POST http://localhost:3000/api/notify -d \\'{\"to\":\"you@example.com\",\"name\":\"You\"}\\' -H \"Content-Type: application/json\"',\n ],\n docsUrl: DOCS,\n },\n nextjs: {\n channel: 'email',\n language: 'node',\n framework: 'nextjs',\n variant: 'sdk',\n files: [\n {\n path: 'src/lib/zyphr.ts',\n purpose: 'Zyphr SDK client singleton (server-only)',\n contents:\n \"import 'server-only';\\n\" +\n \"import { Zyphr } from '@zyphr-dev/node-sdk';\\n\\n\" +\n 'export const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\\n',\n overwrite: false,\n },\n {\n path: 'src/app/api/notify/route.ts',\n purpose: 'Next.js App Router route handler that sends a welcome email',\n contents:\n \"import { NextResponse } from 'next/server';\\n\" +\n \"import { zyphr } from '@/lib/zyphr';\\n\\n\" +\n 'export async function POST(req: Request) {\\n' +\n ' const { to, name } = (await req.json()) as { to: string; name: string };\\n' +\n ' const result = await zyphr.emails.sendEmail({\\n' +\n ' to: [{ email: to, name }],\\n' +\n ' subject: `Welcome, ${name}!`,\\n' +\n ' html: `<h1>Welcome, ${name}!</h1>`,\\n' +\n ' });\\n' +\n ' return NextResponse.json(result);\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n 'Test: curl -X POST http://localhost:3000/api/notify -d \\'{\"to\":\"you@example.com\",\"name\":\"You\"}\\' -H \"Content-Type: application/json\"',\n ],\n docsUrl: DOCS,\n },\n },\n },\n python: {\n sdk: {\n channel: 'email',\n language: 'python',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/zyphr_client.py',\n purpose: 'REST helper for the Zyphr API',\n contents:\n 'import os\\n' +\n 'import requests\\n\\n' +\n 'ZYPHR_API_KEY = os.environ[\"ZYPHR_API_KEY\"]\\n' +\n 'BASE_URL = \"https://api.zyphr.dev/v1\"\\n\\n' +\n 'headers = {\\n' +\n ' \"X-API-Key\": ZYPHR_API_KEY,\\n' +\n ' \"Content-Type\": \"application/json\",\\n' +\n '}\\n\\n' +\n 'def zyphr_request(method, path, json=None, params=None):\\n' +\n ' response = requests.request(\\n' +\n ' method, f\"{BASE_URL}{path}\", headers=headers, json=json, params=params,\\n' +\n ' )\\n' +\n ' response.raise_for_status()\\n' +\n ' return response.json()\\n',\n overwrite: false,\n },\n {\n path: 'app/notify.py',\n purpose: 'Send a welcome email through Zyphr',\n contents:\n 'from .zyphr_client import zyphr_request\\n\\n' +\n 'def send_welcome_email(to: str, name: str) -> dict:\\n' +\n ' return zyphr_request(\"POST\", \"/emails\", json={\\n' +\n ' \"to\": [{\"email\": to, \"name\": name}],\\n' +\n ' \"subject\": f\"Welcome, {name}!\",\\n' +\n ' \"html\": f\"<h1>Welcome, {name}!</h1>\",\\n' +\n ' })\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n flask: {\n channel: 'email',\n language: 'python',\n framework: 'flask',\n variant: 'sdk',\n files: [\n {\n path: 'app/zyphr_client.py',\n purpose: 'REST helper for the Zyphr API',\n contents:\n 'import os\\n' +\n 'import requests\\n\\n' +\n 'BASE_URL = \"https://api.zyphr.dev/v1\"\\n\\n' +\n 'def zyphr_request(method, path, json=None, params=None):\\n' +\n ' response = requests.request(\\n' +\n ' method, f\"{BASE_URL}{path}\",\\n' +\n ' headers={\"X-API-Key\": os.environ[\"ZYPHR_API_KEY\"], \"Content-Type\": \"application/json\"},\\n' +\n ' json=json, params=params,\\n' +\n ' )\\n' +\n ' response.raise_for_status()\\n' +\n ' return response.json()\\n',\n overwrite: false,\n },\n {\n path: 'app/routes/notify.py',\n purpose: 'Flask blueprint that sends a welcome email',\n contents:\n 'from flask import Blueprint, request, jsonify\\n' +\n 'from ..zyphr_client import zyphr_request\\n\\n' +\n 'notify_bp = Blueprint(\"notify\", __name__)\\n\\n' +\n '@notify_bp.route(\"/notify\", methods=[\"POST\"])\\n' +\n 'def notify():\\n' +\n ' body = request.get_json() or {}\\n' +\n ' result = zyphr_request(\"POST\", \"/emails\", json={\\n' +\n ' \"to\": [{\"email\": body[\"to\"], \"name\": body.get(\"name\", \"\")}],\\n' +\n ' \"subject\": f\"Welcome, {body.get(\\'name\\', \\'friend\\')}!\",\\n' +\n ' \"html\": f\"<h1>Welcome!</h1>\",\\n' +\n ' })\\n' +\n ' return jsonify(result)\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n 'Register the blueprint: app.register_blueprint(notify_bp, url_prefix=\"/api\")',\n ],\n docsUrl: DOCS,\n },\n fastapi: {\n channel: 'email',\n language: 'python',\n framework: 'fastapi',\n variant: 'sdk',\n files: [\n {\n path: 'app/zyphr_client.py',\n purpose: 'REST helper for the Zyphr API',\n contents:\n 'import os\\n' +\n 'import httpx\\n\\n' +\n 'BASE_URL = \"https://api.zyphr.dev/v1\"\\n\\n' +\n 'async def zyphr_request(method: str, path: str, json: dict | None = None) -> dict:\\n' +\n ' async with httpx.AsyncClient() as client:\\n' +\n ' resp = await client.request(\\n' +\n ' method, f\"{BASE_URL}{path}\",\\n' +\n ' headers={\"X-API-Key\": os.environ[\"ZYPHR_API_KEY\"], \"Content-Type\": \"application/json\"},\\n' +\n ' json=json,\\n' +\n ' )\\n' +\n ' resp.raise_for_status()\\n' +\n ' return resp.json()\\n',\n overwrite: false,\n },\n {\n path: 'app/routers/notify.py',\n purpose: 'FastAPI router that sends a welcome email',\n contents:\n 'from fastapi import APIRouter\\n' +\n 'from pydantic import BaseModel, EmailStr\\n' +\n 'from ..zyphr_client import zyphr_request\\n\\n' +\n 'router = APIRouter()\\n\\n' +\n 'class NotifyIn(BaseModel):\\n' +\n ' to: EmailStr\\n' +\n ' name: str\\n\\n' +\n '@router.post(\"/notify\")\\n' +\n 'async def notify(body: NotifyIn):\\n' +\n ' return await zyphr_request(\"POST\", \"/emails\", json={\\n' +\n ' \"to\": [{\"email\": body.to, \"name\": body.name}],\\n' +\n ' \"subject\": f\"Welcome, {body.name}!\",\\n' +\n ' \"html\": f\"<h1>Welcome, {body.name}!</h1>\",\\n' +\n ' })\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n 'Install httpx: `pip install httpx` (or `poetry add httpx`)',\n 'Mount the router: app.include_router(router, prefix=\"/api\")',\n ],\n docsUrl: DOCS,\n },\n },\n },\n ruby: {\n sdk: {\n channel: 'email',\n language: 'ruby',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'config/initializers/zyphr.rb',\n purpose: 'Zyphr SDK configuration',\n contents:\n \"require 'zyphr'\\n\\n\" +\n 'Zyphr.configure do |config|\\n' +\n \" config.api_key['X-API-Key'] = ENV.fetch('ZYPHR_API_KEY')\\n\" +\n 'end\\n',\n overwrite: false,\n },\n {\n path: 'app/services/notify_service.rb',\n purpose: 'Service object that sends a welcome email',\n contents:\n 'class NotifyService\\n' +\n ' def self.send_welcome_email(to:, name:)\\n' +\n ' Zyphr::EmailsApi.new.send_email(\\n' +\n ' Zyphr::SendEmailRequest.new(\\n' +\n ' to: [{ email: to, name: name }],\\n' +\n \" subject: \\\"Welcome, #{name}!\\\",\\n\" +\n \" html: \\\"<h1>Welcome, #{name}!</h1>\\\"\\n\" +\n ' )\\n' +\n ' )\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n rails: {\n channel: 'email',\n language: 'ruby',\n framework: 'rails',\n variant: 'sdk',\n files: [\n {\n path: 'config/initializers/zyphr.rb',\n purpose: 'Zyphr SDK configuration',\n contents:\n \"require 'zyphr'\\n\\n\" +\n 'Zyphr.configure do |config|\\n' +\n \" config.api_key['X-API-Key'] = ENV.fetch('ZYPHR_API_KEY')\\n\" +\n 'end\\n',\n overwrite: false,\n },\n {\n path: 'app/controllers/notify_controller.rb',\n purpose: 'Rails controller that sends a welcome email',\n contents:\n 'class NotifyController < ApplicationController\\n' +\n ' def create\\n' +\n ' result = Zyphr::EmailsApi.new.send_email(\\n' +\n ' Zyphr::SendEmailRequest.new(\\n' +\n ' to: [{ email: params.require(:to), name: params[:name] }],\\n' +\n \" subject: \\\"Welcome, #{params[:name]}!\\\",\\n\" +\n \" html: \\\"<h1>Welcome, #{params[:name]}!</h1>\\\"\\n\" +\n ' )\\n' +\n ' )\\n' +\n ' render json: result\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n \"Add a route in config/routes.rb: post '/notify', to: 'notify#create'\",\n ],\n docsUrl: DOCS,\n },\n },\n },\n go: {\n sdk: {\n channel: 'email',\n language: 'go',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'internal/zyphr/client.go',\n purpose: 'Thin REST client for the Zyphr API',\n contents:\n 'package zyphr\\n\\n' +\n 'import (\\n' +\n '\\t\"bytes\"\\n' +\n '\\t\"encoding/json\"\\n' +\n '\\t\"fmt\"\\n' +\n '\\t\"io\"\\n' +\n '\\t\"net/http\"\\n' +\n '\\t\"os\"\\n' +\n ')\\n\\n' +\n 'const baseURL = \"https://api.zyphr.dev/v1\"\\n\\n' +\n 'type Client struct {\\n' +\n '\\tAPIKey string\\n' +\n '\\tHTTPClient *http.Client\\n' +\n '}\\n\\n' +\n 'func NewClient() *Client {\\n' +\n '\\treturn &Client{APIKey: os.Getenv(\"ZYPHR_API_KEY\"), HTTPClient: &http.Client{}}\\n' +\n '}\\n\\n' +\n 'func (c *Client) Do(method, path string, body any) ([]byte, error) {\\n' +\n '\\tvar buf io.Reader\\n' +\n '\\tif body != nil {\\n' +\n '\\t\\tb, err := json.Marshal(body)\\n' +\n '\\t\\tif err != nil { return nil, fmt.Errorf(\"marshal: %w\", err) }\\n' +\n '\\t\\tbuf = bytes.NewReader(b)\\n' +\n '\\t}\\n' +\n '\\treq, _ := http.NewRequest(method, baseURL+path, buf)\\n' +\n '\\treq.Header.Set(\"X-API-Key\", c.APIKey)\\n' +\n '\\treq.Header.Set(\"Content-Type\", \"application/json\")\\n' +\n '\\tresp, err := c.HTTPClient.Do(req)\\n' +\n '\\tif err != nil { return nil, err }\\n' +\n '\\tdefer resp.Body.Close()\\n' +\n '\\tdata, _ := io.ReadAll(resp.Body)\\n' +\n '\\tif resp.StatusCode >= 400 { return nil, fmt.Errorf(\"zyphr %d: %s\", resp.StatusCode, data) }\\n' +\n '\\treturn data, nil\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'internal/notify/notify.go',\n purpose: 'Send a welcome email through Zyphr',\n contents:\n 'package notify\\n\\n' +\n 'import \"yourapp/internal/zyphr\"\\n\\n' +\n 'func SendWelcomeEmail(client *zyphr.Client, to, name string) ([]byte, error) {\\n' +\n '\\treturn client.Do(\"POST\", \"/emails\", map[string]any{\\n' +\n '\\t\\t\"to\": []map[string]string{{\"email\": to, \"name\": name}},\\n' +\n '\\t\\t\"subject\": \"Welcome, \" + name + \"!\",\\n' +\n '\\t\\t\"html\": \"<h1>Welcome, \" + name + \"!</h1>\",\\n' +\n '\\t})\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n php: {\n sdk: {\n channel: 'email',\n language: 'php',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/Services/ZyphrClient.php',\n purpose: 'Guzzle-backed Zyphr client',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Services;\\n\\n' +\n 'use GuzzleHttp\\\\Client;\\n\\n' +\n 'class ZyphrClient\\n' +\n '{\\n' +\n ' private Client $http;\\n\\n' +\n ' public function __construct()\\n' +\n ' {\\n' +\n ' $this->http = new Client([\\n' +\n \" 'base_uri' => 'https://api.zyphr.dev/v1/',\\n\" +\n \" 'headers' => [\\n\" +\n \" 'X-API-Key' => getenv('ZYPHR_API_KEY'),\\n\" +\n \" 'Content-Type' => 'application/json',\\n\" +\n ' ],\\n' +\n ' ]);\\n' +\n ' }\\n\\n' +\n ' public function sendWelcomeEmail(string $to, string $name): array\\n' +\n ' {\\n' +\n \" $response = $this->http->post('emails', [\\n\" +\n \" 'json' => [\\n\" +\n \" 'to' => [['email' => $to, 'name' => $name]],\\n\" +\n \" 'subject' => \\\"Welcome, {$name}!\\\",\\n\" +\n \" 'html' => \\\"<h1>Welcome, {$name}!</h1>\\\",\\n\" +\n ' ],\\n' +\n ' ]);\\n' +\n \" return json_decode((string) $response->getBody(), true);\\n\" +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n laravel: {\n channel: 'email',\n language: 'php',\n framework: 'laravel',\n variant: 'sdk',\n files: [\n {\n path: 'app/Services/ZyphrClient.php',\n purpose: 'Laravel-friendly Zyphr client',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Services;\\n\\n' +\n 'use Illuminate\\\\Support\\\\Facades\\\\Http;\\n\\n' +\n 'class ZyphrClient\\n' +\n '{\\n' +\n ' public function sendWelcomeEmail(string $to, string $name): array\\n' +\n ' {\\n' +\n ' $response = Http::withHeaders([\\n' +\n \" 'X-API-Key' => config('services.zyphr.api_key'),\\n\" +\n \" 'Content-Type' => 'application/json',\\n\" +\n \" ])->post('https://api.zyphr.dev/v1/emails', [\\n\" +\n \" 'to' => [['email' => $to, 'name' => $name]],\\n\" +\n \" 'subject' => \\\"Welcome, {$name}!\\\",\\n\" +\n \" 'html' => \\\"<h1>Welcome, {$name}!</h1>\\\",\\n\" +\n ' ]);\\n' +\n ' $response->throw();\\n' +\n \" return $response->json();\\n\" +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'config/services.php (snippet)',\n purpose: 'Register the Zyphr API key under services config',\n contents:\n \"'zyphr' => [\\n\" +\n \" 'api_key' => env('ZYPHR_API_KEY'),\\n\" +\n '],\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n },\n csharp: {\n sdk: {\n channel: 'email',\n language: 'csharp',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'Services/ZyphrClient.cs',\n purpose: 'Singleton-style Zyphr client wrapper',\n contents:\n 'using ZyphrDev.SDK.Api;\\n' +\n 'using ZyphrDev.SDK.Client;\\n' +\n 'using ZyphrDev.SDK.Model;\\n\\n' +\n 'namespace YourApp.Services;\\n\\n' +\n 'public class ZyphrClient\\n' +\n '{\\n' +\n ' private readonly EmailsApi _emails;\\n\\n' +\n ' public ZyphrClient()\\n' +\n ' {\\n' +\n ' var config = new Configuration\\n' +\n ' {\\n' +\n ' ApiKey = new Dictionary<string, string>\\n' +\n ' {\\n' +\n ' { \"X-API-Key\", Environment.GetEnvironmentVariable(\"ZYPHR_API_KEY\")! }\\n' +\n ' }\\n' +\n ' };\\n' +\n ' _emails = new EmailsApi(config);\\n' +\n ' }\\n\\n' +\n ' public async Task<SendEmailResponse> SendWelcomeEmailAsync(string to, string name)\\n' +\n ' {\\n' +\n ' return await _emails.SendEmailAsync(new SendEmailRequest(\\n' +\n ' to: new List<EmailAddress> { new() { Email = to, Name = name } },\\n' +\n ' subject: $\"Welcome, {name}!\",\\n' +\n ' html: $\"<h1>Welcome, {name}!</h1>\"\\n' +\n ' ));\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n aspnetcore: {\n channel: 'email',\n language: 'csharp',\n framework: 'aspnetcore',\n variant: 'sdk',\n files: [\n {\n path: 'Services/ZyphrClient.cs',\n purpose: 'DI-friendly Zyphr client',\n contents:\n 'using ZyphrDev.SDK.Api;\\n' +\n 'using ZyphrDev.SDK.Client;\\n' +\n 'using ZyphrDev.SDK.Model;\\n\\n' +\n 'namespace YourApp.Services;\\n\\n' +\n 'public class ZyphrClient\\n' +\n '{\\n' +\n ' public EmailsApi Emails { get; }\\n\\n' +\n ' public ZyphrClient(IConfiguration cfg)\\n' +\n ' {\\n' +\n ' var config = new Configuration\\n' +\n ' {\\n' +\n ' ApiKey = new Dictionary<string, string>\\n' +\n ' {\\n' +\n ' { \"X-API-Key\", cfg[\"Zyphr:ApiKey\"]! }\\n' +\n ' }\\n' +\n ' };\\n' +\n ' Emails = new EmailsApi(config);\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'Controllers/NotifyController.cs',\n purpose: 'ASP.NET Core controller that sends a welcome email',\n contents:\n 'using Microsoft.AspNetCore.Mvc;\\n' +\n 'using YourApp.Services;\\n' +\n 'using ZyphrDev.SDK.Model;\\n\\n' +\n '[ApiController]\\n' +\n '[Route(\"api/[controller]\")]\\n' +\n 'public class NotifyController : ControllerBase\\n' +\n '{\\n' +\n ' private readonly ZyphrClient _zyphr;\\n' +\n ' public NotifyController(ZyphrClient zyphr) => _zyphr = zyphr;\\n\\n' +\n ' public record NotifyIn(string To, string Name);\\n\\n' +\n ' [HttpPost]\\n' +\n ' public async Task<IActionResult> Post([FromBody] NotifyIn body)\\n' +\n ' {\\n' +\n ' var result = await _zyphr.Emails.SendEmailAsync(new SendEmailRequest(\\n' +\n ' to: new List<EmailAddress> { new() { Email = body.To, Name = body.Name } },\\n' +\n ' subject: $\"Welcome, {body.Name}!\",\\n' +\n ' html: $\"<h1>Welcome, {body.Name}!</h1>\"\\n' +\n ' ));\\n' +\n ' return Ok(result);\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n 'Register the client in Program.cs: builder.Services.AddSingleton<ZyphrClient>();',\n ],\n docsUrl: DOCS,\n },\n },\n },\n};\n","import type { QuickstartChannelMap } from '../quickstart-types.js';\n\nconst DOCS = 'https://docs.zyphr.dev/channels/in-app-messaging';\nconst ENV: string[] = ['ZYPHR_API_KEY'];\nconst NEXT_STEPS = [\n 'Add ZYPHR_API_KEY to your .env file.',\n 'Use the matching subscriberId on the client (e.g. @zyphr-dev/inbox-react) to display the message.',\n];\n\nexport const inboxChannel: QuickstartChannelMap = {\n node: {\n sdk: {\n channel: 'inbox',\n language: 'node',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'src/services/inbox.ts',\n purpose: 'Send an in-app inbox message through Zyphr',\n contents:\n \"import { Zyphr } from '@zyphr-dev/node-sdk';\\n\\n\" +\n 'const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\\n\\n' +\n 'export async function notifyReportReady(subscriberId: string, reportId: string) {\\n' +\n ' return await zyphr.inbox.sendInApp({\\n' +\n ' subscriberId,\\n' +\n \" title: 'New report ready',\\n\" +\n \" body: 'Your report finished processing — click to view.',\\n\" +\n ' actionUrl: `/reports/${reportId}`,\\n' +\n \" actionLabel: 'View report',\\n\" +\n ' });\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n python: {\n sdk: {\n channel: 'inbox',\n language: 'python',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/inbox.py',\n purpose: 'Send an in-app inbox message through Zyphr',\n contents:\n 'from .zyphr_client import zyphr_request\\n\\n' +\n 'def notify_report_ready(subscriber_id: str, report_id: str) -> dict:\\n' +\n ' return zyphr_request(\"POST\", \"/inbox\", json={\\n' +\n ' \"subscriberId\": subscriber_id,\\n' +\n ' \"title\": \"New report ready\",\\n' +\n ' \"body\": \"Your report finished processing — click to view.\",\\n' +\n ' \"actionUrl\": f\"/reports/{report_id}\",\\n' +\n ' \"actionLabel\": \"View report\",\\n' +\n ' })\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n ruby: {\n sdk: {\n channel: 'inbox',\n language: 'ruby',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/services/inbox_service.rb',\n purpose: 'Send an in-app inbox message through Zyphr',\n contents:\n 'class InboxService\\n' +\n ' def self.notify_report_ready(subscriber_id:, report_id:)\\n' +\n ' Zyphr::InboxApi.new.send_in_app(\\n' +\n ' Zyphr::SendInAppRequest.new(\\n' +\n ' subscriber_id: subscriber_id,\\n' +\n \" title: 'New report ready',\\n\" +\n \" body: 'Your report finished processing — click to view.',\\n\" +\n \" action_url: \\\"/reports/#{report_id}\\\",\\n\" +\n \" action_label: 'View report'\\n\" +\n ' )\\n' +\n ' )\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n go: {\n sdk: {\n channel: 'inbox',\n language: 'go',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'internal/notify/inbox.go',\n purpose: 'Send an in-app inbox message through Zyphr',\n contents:\n 'package notify\\n\\n' +\n 'import \"yourapp/internal/zyphr\"\\n\\n' +\n 'func NotifyReportReady(client *zyphr.Client, subscriberID, reportID string) ([]byte, error) {\\n' +\n '\\treturn client.Do(\"POST\", \"/inbox\", map[string]any{\\n' +\n '\\t\\t\"subscriberId\": subscriberID,\\n' +\n '\\t\\t\"title\": \"New report ready\",\\n' +\n '\\t\\t\"body\": \"Your report finished processing — click to view.\",\\n' +\n '\\t\\t\"actionUrl\": \"/reports/\" + reportID,\\n' +\n '\\t\\t\"actionLabel\": \"View report\",\\n' +\n '\\t})\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n php: {\n sdk: {\n channel: 'inbox',\n language: 'php',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/Services/InboxService.php',\n purpose: 'Send an in-app inbox message through Zyphr',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Services;\\n\\n' +\n 'use GuzzleHttp\\\\Client;\\n\\n' +\n 'class InboxService\\n' +\n '{\\n' +\n ' public function notifyReportReady(string $subscriberId, string $reportId): array\\n' +\n ' {\\n' +\n \" $http = new Client([\\n\" +\n \" 'base_uri' => 'https://api.zyphr.dev/v1/',\\n\" +\n \" 'headers' => [\\n\" +\n \" 'X-API-Key' => getenv('ZYPHR_API_KEY'),\\n\" +\n \" 'Content-Type' => 'application/json',\\n\" +\n ' ],\\n' +\n \" ]);\\n\" +\n \" $r = $http->post('inbox', ['json' => [\\n\" +\n \" 'subscriberId' => $subscriberId,\\n\" +\n \" 'title' => 'New report ready',\\n\" +\n \" 'body' => 'Your report finished processing — click to view.',\\n\" +\n \" 'actionUrl' => \\\"/reports/{$reportId}\\\",\\n\" +\n \" 'actionLabel' => 'View report',\\n\" +\n ' ]]);\\n' +\n \" return json_decode((string) $r->getBody(), true);\\n\" +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n csharp: {\n sdk: {\n channel: 'inbox',\n language: 'csharp',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'Services/InboxService.cs',\n purpose: 'Send an in-app inbox message through Zyphr',\n contents:\n 'using ZyphrDev.SDK.Api;\\n' +\n 'using ZyphrDev.SDK.Client;\\n' +\n 'using ZyphrDev.SDK.Model;\\n\\n' +\n 'namespace YourApp.Services;\\n\\n' +\n 'public class InboxService\\n' +\n '{\\n' +\n ' private readonly InboxApi _inbox;\\n' +\n ' public InboxService()\\n' +\n ' {\\n' +\n ' var config = new Configuration\\n' +\n ' {\\n' +\n ' ApiKey = new Dictionary<string, string>\\n' +\n ' {\\n' +\n ' { \"X-API-Key\", Environment.GetEnvironmentVariable(\"ZYPHR_API_KEY\")! }\\n' +\n ' }\\n' +\n ' };\\n' +\n ' _inbox = new InboxApi(config);\\n' +\n ' }\\n\\n' +\n ' public Task<SendInAppResponse> NotifyReportReadyAsync(string subscriberId, string reportId)\\n' +\n ' {\\n' +\n ' return _inbox.SendInAppAsync(new SendInAppRequest(\\n' +\n ' subscriberId: subscriberId,\\n' +\n ' title: \"New report ready\",\\n' +\n ' body: \"Your report finished processing — click to view.\",\\n' +\n ' actionUrl: $\"/reports/{reportId}\",\\n' +\n ' actionLabel: \"View report\"\\n' +\n ' ));\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n};\n","import type { QuickstartChannelMap } from '../quickstart-types.js';\n\nconst DOCS = 'https://docs.zyphr.dev/channels/push-notifications';\nconst ENV: string[] = ['ZYPHR_API_KEY'];\nconst NEXT_STEPS = [\n 'Add ZYPHR_API_KEY to your .env file.',\n 'Register at least one device for the target subscriber (via the SDK or dashboard) before sending.',\n 'Verify your push provider credentials (APNs/FCM) are configured in the Zyphr dashboard.',\n];\n\nexport const pushChannel: QuickstartChannelMap = {\n node: {\n sdk: {\n channel: 'push',\n language: 'node',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'src/services/push.ts',\n purpose: 'Send a push notification through Zyphr',\n contents:\n \"import { Zyphr } from '@zyphr-dev/node-sdk';\\n\\n\" +\n 'const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\\n\\n' +\n 'export async function pushOrderShipped(subscriberId: string, orderId: string) {\\n' +\n ' return await zyphr.push.sendPush({\\n' +\n ' subscriberId,\\n' +\n \" title: 'Order shipped',\\n\" +\n ' body: `Order ${orderId} is on its way.`,\\n' +\n ' data: { orderId },\\n' +\n ' });\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n python: {\n sdk: {\n channel: 'push',\n language: 'python',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/push.py',\n purpose: 'Send a push notification through Zyphr',\n contents:\n 'from .zyphr_client import zyphr_request\\n\\n' +\n 'def push_order_shipped(subscriber_id: str, order_id: str) -> dict:\\n' +\n ' return zyphr_request(\"POST\", \"/push\", json={\\n' +\n ' \"subscriberId\": subscriber_id,\\n' +\n ' \"title\": \"Order shipped\",\\n' +\n ' \"body\": f\"Order {order_id} is on its way.\",\\n' +\n ' \"data\": {\"orderId\": order_id},\\n' +\n ' })\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n ruby: {\n sdk: {\n channel: 'push',\n language: 'ruby',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/services/push_service.rb',\n purpose: 'Send a push notification through Zyphr',\n contents:\n 'class PushService\\n' +\n ' def self.order_shipped(subscriber_id:, order_id:)\\n' +\n ' Zyphr::PushApi.new.send_push(\\n' +\n ' Zyphr::SendPushRequest.new(\\n' +\n ' subscriber_id: subscriber_id,\\n' +\n \" title: 'Order shipped',\\n\" +\n \" body: \\\"Order #{order_id} is on its way.\\\",\\n\" +\n ' data: { orderId: order_id }\\n' +\n ' )\\n' +\n ' )\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n go: {\n sdk: {\n channel: 'push',\n language: 'go',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'internal/notify/push.go',\n purpose: 'Send a push notification through Zyphr',\n contents:\n 'package notify\\n\\n' +\n 'import \"yourapp/internal/zyphr\"\\n\\n' +\n 'func PushOrderShipped(client *zyphr.Client, subscriberID, orderID string) ([]byte, error) {\\n' +\n '\\treturn client.Do(\"POST\", \"/push\", map[string]any{\\n' +\n '\\t\\t\"subscriberId\": subscriberID,\\n' +\n '\\t\\t\"title\": \"Order shipped\",\\n' +\n '\\t\\t\"body\": \"Order \" + orderID + \" is on its way.\",\\n' +\n '\\t\\t\"data\": map[string]string{\"orderId\": orderID},\\n' +\n '\\t})\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n php: {\n sdk: {\n channel: 'push',\n language: 'php',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/Services/PushService.php',\n purpose: 'Send a push notification through Zyphr',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Services;\\n\\n' +\n 'use GuzzleHttp\\\\Client;\\n\\n' +\n 'class PushService\\n' +\n '{\\n' +\n ' public function orderShipped(string $subscriberId, string $orderId): array\\n' +\n ' {\\n' +\n \" $http = new Client([\\n\" +\n \" 'base_uri' => 'https://api.zyphr.dev/v1/',\\n\" +\n \" 'headers' => [\\n\" +\n \" 'X-API-Key' => getenv('ZYPHR_API_KEY'),\\n\" +\n \" 'Content-Type' => 'application/json',\\n\" +\n ' ],\\n' +\n \" ]);\\n\" +\n \" $r = $http->post('push', ['json' => [\\n\" +\n \" 'subscriberId' => $subscriberId,\\n\" +\n \" 'title' => 'Order shipped',\\n\" +\n \" 'body' => \\\"Order {$orderId} is on its way.\\\",\\n\" +\n \" 'data' => ['orderId' => $orderId],\\n\" +\n ' ]]);\\n' +\n \" return json_decode((string) $r->getBody(), true);\\n\" +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n csharp: {\n sdk: {\n channel: 'push',\n language: 'csharp',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'Services/PushService.cs',\n purpose: 'Send a push notification through Zyphr',\n contents:\n 'using ZyphrDev.SDK.Api;\\n' +\n 'using ZyphrDev.SDK.Client;\\n' +\n 'using ZyphrDev.SDK.Model;\\n\\n' +\n 'namespace YourApp.Services;\\n\\n' +\n 'public class PushService\\n' +\n '{\\n' +\n ' private readonly PushApi _push;\\n' +\n ' public PushService()\\n' +\n ' {\\n' +\n ' var config = new Configuration\\n' +\n ' {\\n' +\n ' ApiKey = new Dictionary<string, string>\\n' +\n ' {\\n' +\n ' { \"X-API-Key\", Environment.GetEnvironmentVariable(\"ZYPHR_API_KEY\")! }\\n' +\n ' }\\n' +\n ' };\\n' +\n ' _push = new PushApi(config);\\n' +\n ' }\\n\\n' +\n ' public Task<SendPushResponse> OrderShippedAsync(string subscriberId, string orderId)\\n' +\n ' {\\n' +\n ' return _push.SendPushAsync(new SendPushRequest(\\n' +\n ' subscriberId: subscriberId,\\n' +\n ' title: \"Order shipped\",\\n' +\n ' body: $\"Order {orderId} is on its way.\"\\n' +\n ' ));\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n};\n","import type { QuickstartChannelMap } from '../quickstart-types.js';\n\nconst DOCS = 'https://docs.zyphr.dev/channels/sms';\nconst ENV: string[] = ['ZYPHR_API_KEY'];\nconst NEXT_STEPS = [\n 'Add ZYPHR_API_KEY to your .env file.',\n 'Recipients MUST be in E.164 format (e.g. +14155551234).',\n 'Provision your SMS sender (phone number or alphanumeric sender ID) in the Zyphr dashboard.',\n];\n\nexport const smsChannel: QuickstartChannelMap = {\n node: {\n sdk: {\n channel: 'sms',\n language: 'node',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'src/services/sms.ts',\n purpose: 'Send an SMS through Zyphr',\n contents:\n \"import { Zyphr } from '@zyphr-dev/node-sdk';\\n\\n\" +\n 'const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\\n\\n' +\n 'export async function sendOtp(to: string, code: string) {\\n' +\n ' return await zyphr.sms.sendSms({\\n' +\n ' to,\\n' +\n ' body: `Your verification code is ${code}. Expires in 5 minutes.`,\\n' +\n ' });\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n python: {\n sdk: {\n channel: 'sms',\n language: 'python',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/sms.py',\n purpose: 'Send an SMS through Zyphr',\n contents:\n 'from .zyphr_client import zyphr_request\\n\\n' +\n 'def send_otp(to: str, code: str) -> dict:\\n' +\n ' return zyphr_request(\"POST\", \"/sms\", json={\\n' +\n ' \"to\": to,\\n' +\n ' \"body\": f\"Your verification code is {code}. Expires in 5 minutes.\",\\n' +\n ' })\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n ruby: {\n sdk: {\n channel: 'sms',\n language: 'ruby',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/services/sms_service.rb',\n purpose: 'Send an SMS through Zyphr',\n contents:\n 'class SmsService\\n' +\n ' def self.send_otp(to:, code:)\\n' +\n ' Zyphr::SMSApi.new.send_sms(\\n' +\n ' Zyphr::SendSmsRequest.new(\\n' +\n ' to: to,\\n' +\n \" body: \\\"Your verification code is #{code}. Expires in 5 minutes.\\\"\\n\" +\n ' )\\n' +\n ' )\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n go: {\n sdk: {\n channel: 'sms',\n language: 'go',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'internal/notify/sms.go',\n purpose: 'Send an SMS through Zyphr',\n contents:\n 'package notify\\n\\n' +\n 'import \"yourapp/internal/zyphr\"\\n\\n' +\n 'func SendOtp(client *zyphr.Client, to, code string) ([]byte, error) {\\n' +\n '\\treturn client.Do(\"POST\", \"/sms\", map[string]any{\\n' +\n '\\t\\t\"to\": to,\\n' +\n '\\t\\t\"body\": \"Your verification code is \" + code + \". Expires in 5 minutes.\",\\n' +\n '\\t})\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n php: {\n sdk: {\n channel: 'sms',\n language: 'php',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/Services/SmsService.php',\n purpose: 'Send an SMS through Zyphr',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Services;\\n\\n' +\n 'use GuzzleHttp\\\\Client;\\n\\n' +\n 'class SmsService\\n' +\n '{\\n' +\n ' public function sendOtp(string $to, string $code): array\\n' +\n ' {\\n' +\n \" $http = new Client([\\n\" +\n \" 'base_uri' => 'https://api.zyphr.dev/v1/',\\n\" +\n \" 'headers' => [\\n\" +\n \" 'X-API-Key' => getenv('ZYPHR_API_KEY'),\\n\" +\n \" 'Content-Type' => 'application/json',\\n\" +\n ' ],\\n' +\n \" ]);\\n\" +\n \" $r = $http->post('sms', ['json' => [\\n\" +\n \" 'to' => $to,\\n\" +\n \" 'body' => \\\"Your verification code is {$code}. Expires in 5 minutes.\\\",\\n\" +\n ' ]]);\\n' +\n \" return json_decode((string) $r->getBody(), true);\\n\" +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n csharp: {\n sdk: {\n channel: 'sms',\n language: 'csharp',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'Services/SmsService.cs',\n purpose: 'Send an SMS through Zyphr',\n contents:\n 'using ZyphrDev.SDK.Api;\\n' +\n 'using ZyphrDev.SDK.Client;\\n' +\n 'using ZyphrDev.SDK.Model;\\n\\n' +\n 'namespace YourApp.Services;\\n\\n' +\n 'public class SmsService\\n' +\n '{\\n' +\n ' private readonly SMSApi _sms;\\n' +\n ' public SmsService()\\n' +\n ' {\\n' +\n ' var config = new Configuration\\n' +\n ' {\\n' +\n ' ApiKey = new Dictionary<string, string>\\n' +\n ' {\\n' +\n ' { \"X-API-Key\", Environment.GetEnvironmentVariable(\"ZYPHR_API_KEY\")! }\\n' +\n ' }\\n' +\n ' };\\n' +\n ' _sms = new SMSApi(config);\\n' +\n ' }\\n\\n' +\n ' public Task<SendSmsResponse> SendOtpAsync(string to, string code)\\n' +\n ' {\\n' +\n ' return _sms.SendSmsAsync(new SendSmsRequest(\\n' +\n ' to: to,\\n' +\n ' body: $\"Your verification code is {code}. Expires in 5 minutes.\"\\n' +\n ' ));\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n};\n","import type { QuickstartChannelMap } from '../quickstart-types.js';\n\nconst DOCS = 'https://docs.zyphr.dev/features/webhooks-security';\n\nconst ENV: string[] = ['ZYPHR_WEBHOOK_SECRET'];\n\nconst NEXT_STEPS = [\n 'Add ZYPHR_WEBHOOK_SECRET to your .env file — get it from `zyphr.webhooks.rotateWebhookSecret(id)` or the Zyphr dashboard.',\n 'Configure the webhook endpoint URL in the Zyphr dashboard or via `create_webhook`.',\n 'ALWAYS verify signatures before processing payloads — never trust an unverified webhook.',\n 'Reject deliveries whose timestamp is more than 5 minutes from now to prevent replay attacks.',\n];\n\nexport const webhookChannel: QuickstartChannelMap = {\n node: {\n sdk: {\n channel: 'webhook',\n language: 'node',\n framework: null,\n variant: 'webhook-handler',\n files: [\n {\n path: 'src/lib/verifyZyphrWebhook.ts',\n purpose:\n 'Standard Webhooks (HMAC-SHA256) signature + timestamp verification. Mirrors the canonical snippet in apps/docs/docs/features/webhooks-security.md.',\n contents:\n \"import crypto from 'crypto';\\n\\n\" +\n 'export function verifyZyphrWebhook(\\n' +\n ' payload: string,\\n' +\n \" headers: { 'webhook-id': string; 'webhook-timestamp': string; 'webhook-signature': string },\\n\" +\n ' secret: string,\\n' +\n '): boolean {\\n' +\n \" const msgId = headers['webhook-id'];\\n\" +\n \" const timestamp = parseInt(headers['webhook-timestamp'], 10);\\n\" +\n \" const signatures = headers['webhook-signature'];\\n\\n\" +\n ' const now = Math.floor(Date.now() / 1000);\\n' +\n ' if (Math.abs(now - timestamp) > 300) return false;\\n\\n' +\n ' const signedContent = `${msgId}.${timestamp}.${payload}`;\\n' +\n ' const secretBytes = Buffer.from(\\n' +\n \" secret.startsWith('whsec_') ? secret.slice(6) : secret,\\n\" +\n \" 'hex',\\n\" +\n ' );\\n' +\n ' const expected = crypto\\n' +\n \" .createHmac('sha256', secretBytes)\\n\" +\n ' .update(signedContent)\\n' +\n \" .digest('base64');\\n\\n\" +\n \" for (const sig of signatures.split(' ')) {\\n\" +\n ' const sigValue = sig.slice(3);\\n' +\n ' if (\\n' +\n ' sigValue.length === expected.length &&\\n' +\n ' crypto.timingSafeEqual(Buffer.from(sigValue), Buffer.from(expected))\\n' +\n ' ) {\\n' +\n ' return true;\\n' +\n ' }\\n' +\n ' }\\n' +\n ' return false;\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n express: {\n channel: 'webhook',\n language: 'node',\n framework: 'express',\n variant: 'webhook-handler',\n files: [\n {\n path: 'src/lib/verifyZyphrWebhook.ts',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n \"import crypto from 'crypto';\\n\\n\" +\n 'export function verifyZyphrWebhook(payload: string, headers: Record<string,string>, secret: string): boolean {\\n' +\n \" const msgId = headers['webhook-id'];\\n\" +\n \" const timestamp = parseInt(headers['webhook-timestamp'], 10);\\n\" +\n \" const signatures = headers['webhook-signature'] || '';\\n\" +\n ' const now = Math.floor(Date.now() / 1000);\\n' +\n ' if (Math.abs(now - timestamp) > 300) return false;\\n' +\n ' const signedContent = `${msgId}.${timestamp}.${payload}`;\\n' +\n \" const secretBytes = Buffer.from(secret.startsWith('whsec_') ? secret.slice(6) : secret, 'hex');\\n\" +\n \" const expected = crypto.createHmac('sha256', secretBytes).update(signedContent).digest('base64');\\n\" +\n \" return signatures.split(' ').some((sig) => {\\n\" +\n ' const v = sig.slice(3);\\n' +\n ' return v.length === expected.length && crypto.timingSafeEqual(Buffer.from(v), Buffer.from(expected));\\n' +\n ' });\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'src/routes/zyphrWebhook.ts',\n purpose:\n 'Express route that VERIFIES the signature before processing the webhook. Uses express.raw() so we can hash the exact bytes.',\n contents:\n \"import { Router, raw } from 'express';\\n\" +\n \"import { verifyZyphrWebhook } from '../lib/verifyZyphrWebhook.js';\\n\\n\" +\n 'export const zyphrWebhookRouter = Router();\\n\\n' +\n \"zyphrWebhookRouter.post('/zyphr', raw({ type: 'application/json' }), (req, res) => {\\n\" +\n ' const payload = (req.body as Buffer).toString();\\n' +\n ' const headers = req.headers as Record<string, string>;\\n' +\n ' if (!verifyZyphrWebhook(payload, headers, process.env.ZYPHR_WEBHOOK_SECRET!)) {\\n' +\n \" return res.status(401).send('invalid signature');\\n\" +\n ' }\\n' +\n ' const event = JSON.parse(payload) as { type: string; data: unknown };\\n' +\n \" console.log('zyphr event', event.type);\\n\" +\n ' res.sendStatus(204);\\n' +\n '});\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n 'Mount the router BEFORE express.json(): app.use(zyphrWebhookRouter)',\n ],\n docsUrl: DOCS,\n },\n nextjs: {\n channel: 'webhook',\n language: 'node',\n framework: 'nextjs',\n variant: 'webhook-handler',\n files: [\n {\n path: 'src/lib/verifyZyphrWebhook.ts',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n \"import crypto from 'crypto';\\n\\n\" +\n 'export function verifyZyphrWebhook(payload: string, headers: Headers, secret: string): boolean {\\n' +\n \" const msgId = headers.get('webhook-id') || '';\\n\" +\n \" const timestamp = parseInt(headers.get('webhook-timestamp') || '0', 10);\\n\" +\n \" const signatures = headers.get('webhook-signature') || '';\\n\" +\n ' const now = Math.floor(Date.now() / 1000);\\n' +\n ' if (Math.abs(now - timestamp) > 300) return false;\\n' +\n ' const signedContent = `${msgId}.${timestamp}.${payload}`;\\n' +\n \" const secretBytes = Buffer.from(secret.startsWith('whsec_') ? secret.slice(6) : secret, 'hex');\\n\" +\n \" const expected = crypto.createHmac('sha256', secretBytes).update(signedContent).digest('base64');\\n\" +\n \" return signatures.split(' ').some((sig) => {\\n\" +\n ' const v = sig.slice(3);\\n' +\n ' return v.length === expected.length && crypto.timingSafeEqual(Buffer.from(v), Buffer.from(expected));\\n' +\n ' });\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'src/app/api/zyphr/webhook/route.ts',\n purpose: 'Next.js App Router webhook handler with signature verification',\n contents:\n \"import { NextResponse } from 'next/server';\\n\" +\n \"import { verifyZyphrWebhook } from '@/lib/verifyZyphrWebhook';\\n\\n\" +\n 'export async function POST(req: Request) {\\n' +\n ' const payload = await req.text();\\n' +\n ' if (!verifyZyphrWebhook(payload, req.headers, process.env.ZYPHR_WEBHOOK_SECRET!)) {\\n' +\n \" return new NextResponse('invalid signature', { status: 401 });\\n\" +\n ' }\\n' +\n ' const event = JSON.parse(payload) as { type: string; data: unknown };\\n' +\n \" console.log('zyphr event', event.type);\\n\" +\n ' return new NextResponse(null, { status: 204 });\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n },\n python: {\n sdk: {\n channel: 'webhook',\n language: 'python',\n framework: null,\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/zyphr_webhook.py',\n purpose: 'Standard Webhooks signature verification helper (verbatim from docs)',\n contents:\n 'import hashlib\\n' +\n 'import hmac\\n' +\n 'import base64\\n' +\n 'import time\\n\\n' +\n 'def verify_zyphr_webhook(payload: str, headers: dict, secret: str) -> bool:\\n' +\n ' msg_id = headers.get(\"webhook-id\", \"\")\\n' +\n ' timestamp = headers.get(\"webhook-timestamp\", \"\")\\n' +\n ' signature = headers.get(\"webhook-signature\", \"\")\\n\\n' +\n ' now = int(time.time())\\n' +\n ' if abs(now - int(timestamp)) > 300:\\n' +\n ' return False\\n\\n' +\n ' signed_content = f\"{msg_id}.{timestamp}.{payload}\"\\n' +\n ' secret_hex = secret.removeprefix(\"whsec_\")\\n' +\n ' secret_bytes = bytes.fromhex(secret_hex)\\n' +\n ' expected = base64.b64encode(\\n' +\n ' hmac.new(secret_bytes, signed_content.encode(), hashlib.sha256).digest()\\n' +\n ' ).decode()\\n\\n' +\n ' for sig in signature.split(\" \"):\\n' +\n ' sig_value = sig.removeprefix(\"v1,\")\\n' +\n ' if hmac.compare_digest(sig_value, expected):\\n' +\n ' return True\\n' +\n ' return False\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n flask: {\n channel: 'webhook',\n language: 'python',\n framework: 'flask',\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/zyphr_webhook.py',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n 'import hashlib, hmac, base64, time\\n\\n' +\n 'def verify_zyphr_webhook(payload: str, headers, secret: str) -> bool:\\n' +\n ' msg_id = headers.get(\"webhook-id\", \"\")\\n' +\n ' timestamp = headers.get(\"webhook-timestamp\", \"\")\\n' +\n ' signature = headers.get(\"webhook-signature\", \"\")\\n' +\n ' if abs(int(time.time()) - int(timestamp)) > 300:\\n' +\n ' return False\\n' +\n ' signed = f\"{msg_id}.{timestamp}.{payload}\"\\n' +\n ' secret_bytes = bytes.fromhex(secret.removeprefix(\"whsec_\"))\\n' +\n ' expected = base64.b64encode(hmac.new(secret_bytes, signed.encode(), hashlib.sha256).digest()).decode()\\n' +\n ' return any(hmac.compare_digest(sig.removeprefix(\"v1,\"), expected) for sig in signature.split(\" \"))\\n',\n overwrite: false,\n },\n {\n path: 'app/routes/zyphr_webhook.py',\n purpose: 'Flask blueprint that verifies the webhook signature before processing',\n contents:\n 'import os, json\\n' +\n 'from flask import Blueprint, request, abort\\n' +\n 'from ..zyphr_webhook import verify_zyphr_webhook\\n\\n' +\n 'zyphr_webhook_bp = Blueprint(\"zyphr_webhook\", __name__)\\n\\n' +\n '@zyphr_webhook_bp.route(\"/webhooks/zyphr\", methods=[\"POST\"])\\n' +\n 'def handle():\\n' +\n ' payload = request.get_data(as_text=True)\\n' +\n ' if not verify_zyphr_webhook(payload, request.headers, os.environ[\"ZYPHR_WEBHOOK_SECRET\"]):\\n' +\n ' abort(401, \"invalid signature\")\\n' +\n ' event = json.loads(payload)\\n' +\n ' print(\"zyphr event\", event.get(\"type\"))\\n' +\n ' return \"\", 204\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n fastapi: {\n channel: 'webhook',\n language: 'python',\n framework: 'fastapi',\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/routers/zyphr_webhook.py',\n purpose: 'FastAPI router that verifies the webhook signature before processing',\n contents:\n 'import os, json, hashlib, hmac, base64, time\\n' +\n 'from fastapi import APIRouter, Request, HTTPException\\n\\n' +\n 'router = APIRouter()\\n\\n' +\n 'def verify_zyphr_webhook(payload: str, headers, secret: str) -> bool:\\n' +\n ' msg_id = headers.get(\"webhook-id\", \"\")\\n' +\n ' timestamp = headers.get(\"webhook-timestamp\", \"\")\\n' +\n ' signature = headers.get(\"webhook-signature\", \"\")\\n' +\n ' if abs(int(time.time()) - int(timestamp)) > 300:\\n' +\n ' return False\\n' +\n ' signed = f\"{msg_id}.{timestamp}.{payload}\"\\n' +\n ' secret_bytes = bytes.fromhex(secret.removeprefix(\"whsec_\"))\\n' +\n ' expected = base64.b64encode(hmac.new(secret_bytes, signed.encode(), hashlib.sha256).digest()).decode()\\n' +\n ' return any(hmac.compare_digest(sig.removeprefix(\"v1,\"), expected) for sig in signature.split(\" \"))\\n\\n' +\n '@router.post(\"/webhooks/zyphr\")\\n' +\n 'async def handle(request: Request):\\n' +\n ' body = (await request.body()).decode()\\n' +\n ' if not verify_zyphr_webhook(body, request.headers, os.environ[\"ZYPHR_WEBHOOK_SECRET\"]):\\n' +\n ' raise HTTPException(status_code=401, detail=\"invalid signature\")\\n' +\n ' event = json.loads(body)\\n' +\n ' print(\"zyphr event\", event.get(\"type\"))\\n' +\n ' return {\"ok\": True}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n },\n ruby: {\n sdk: {\n channel: 'webhook',\n language: 'ruby',\n framework: null,\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/services/zyphr_webhook.rb',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n \"require 'openssl'\\n\" +\n \"require 'base64'\\n\\n\" +\n 'class ZyphrWebhook\\n' +\n ' def self.verify(payload:, headers:, secret:)\\n' +\n \" msg_id = headers['webhook-id'].to_s\\n\" +\n \" timestamp = headers['webhook-timestamp'].to_i\\n\" +\n \" signatures = headers['webhook-signature'].to_s\\n\\n\" +\n ' return false if (Time.now.to_i - timestamp).abs > 300\\n\\n' +\n ' signed = \"#{msg_id}.#{timestamp}.#{payload}\"\\n' +\n \" hex = secret.start_with?('whsec_') ? secret[6..] : secret\\n\" +\n ' secret_bytes = [hex].pack(\\'H*\\')\\n' +\n \" expected = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', secret_bytes, signed))\\n\\n\" +\n \" signatures.split(' ').any? do |sig|\\n\" +\n ' v = sig[3..]\\n' +\n ' v && Rack::Utils.secure_compare(v, expected)\\n' +\n ' end\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n rails: {\n channel: 'webhook',\n language: 'ruby',\n framework: 'rails',\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/services/zyphr_webhook.rb',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n \"require 'openssl'\\n\" +\n \"require 'base64'\\n\\n\" +\n 'class ZyphrWebhook\\n' +\n ' def self.verify(payload:, headers:, secret:)\\n' +\n \" msg_id = headers['webhook-id'].to_s\\n\" +\n \" timestamp = headers['webhook-timestamp'].to_i\\n\" +\n \" signatures = headers['webhook-signature'].to_s\\n\" +\n ' return false if (Time.now.to_i - timestamp).abs > 300\\n' +\n ' signed = \"#{msg_id}.#{timestamp}.#{payload}\"\\n' +\n \" hex = secret.start_with?('whsec_') ? secret[6..] : secret\\n\" +\n ' secret_bytes = [hex].pack(\\'H*\\')\\n' +\n \" expected = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', secret_bytes, signed))\\n\" +\n \" signatures.split(' ').any? { |sig| sig[3..] && ActiveSupport::SecurityUtils.secure_compare(sig[3..], expected) }\\n\" +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n {\n path: 'app/controllers/zyphr_webhooks_controller.rb',\n purpose: 'Rails controller that verifies the webhook signature before processing',\n contents:\n 'class ZyphrWebhooksController < ApplicationController\\n' +\n ' skip_before_action :verify_authenticity_token\\n\\n' +\n ' def create\\n' +\n \" payload = request.raw_post\\n\" +\n \" secret = ENV.fetch('ZYPHR_WEBHOOK_SECRET')\\n\" +\n ' unless ZyphrWebhook.verify(payload: payload, headers: request.headers, secret: secret)\\n' +\n ' head :unauthorized and return\\n' +\n ' end\\n' +\n ' event = JSON.parse(payload)\\n' +\n \" Rails.logger.info(\\\"zyphr event #{event['type']}\\\")\\n\" +\n ' head :no_content\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n \"Add route: post '/webhooks/zyphr', to: 'zyphr_webhooks#create'\",\n ],\n docsUrl: DOCS,\n },\n },\n },\n go: {\n sdk: {\n channel: 'webhook',\n language: 'go',\n framework: null,\n variant: 'webhook-handler',\n files: [\n {\n path: 'internal/zyphr/verify.go',\n purpose:\n 'Standard Webhooks signature verification helper (verbatim from docs).',\n contents:\n 'package zyphr\\n\\n' +\n 'import (\\n' +\n '\\t\"crypto/hmac\"\\n' +\n '\\t\"crypto/sha256\"\\n' +\n '\\t\"encoding/base64\"\\n' +\n '\\t\"encoding/hex\"\\n' +\n '\\t\"math\"\\n' +\n '\\t\"strconv\"\\n' +\n '\\t\"strings\"\\n' +\n '\\t\"time\"\\n' +\n ')\\n\\n' +\n 'func VerifyWebhook(payload, msgID, timestamp, signature, secret string) bool {\\n' +\n '\\tts, err := strconv.ParseInt(timestamp, 10, 64)\\n' +\n '\\tif err != nil { return false }\\n' +\n '\\tif math.Abs(float64(time.Now().Unix()-ts)) > 300 { return false }\\n\\n' +\n '\\tsigned := msgID + \".\" + timestamp + \".\" + payload\\n' +\n '\\tsecretHex := strings.TrimPrefix(secret, \"whsec_\")\\n' +\n '\\tsecretBytes, err := hex.DecodeString(secretHex)\\n' +\n '\\tif err != nil { return false }\\n' +\n '\\tmac := hmac.New(sha256.New, secretBytes)\\n' +\n '\\tmac.Write([]byte(signed))\\n' +\n '\\texpected := base64.StdEncoding.EncodeToString(mac.Sum(nil))\\n\\n' +\n '\\tfor _, sig := range strings.Split(signature, \" \") {\\n' +\n '\\t\\tv := strings.TrimPrefix(sig, \"v1,\")\\n' +\n '\\t\\tif hmac.Equal([]byte(v), []byte(expected)) { return true }\\n' +\n '\\t}\\n' +\n '\\treturn false\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n php: {\n sdk: {\n channel: 'webhook',\n language: 'php',\n framework: null,\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/Webhooks/ZyphrWebhook.php',\n purpose: 'Standard Webhooks signature verification helper (verbatim from docs)',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Webhooks;\\n\\n' +\n 'class ZyphrWebhook\\n' +\n '{\\n' +\n ' public static function verify(string $payload, array $headers, string $secret): bool\\n' +\n ' {\\n' +\n \" $msgId = $headers['webhook-id'] ?? '';\\n\" +\n \" $timestamp = $headers['webhook-timestamp'] ?? '';\\n\" +\n \" $signature = $headers['webhook-signature'] ?? '';\\n\" +\n ' if (abs(time() - intval($timestamp)) > 300) {\\n' +\n ' return false;\\n' +\n ' }\\n' +\n ' $signed = \"{$msgId}.{$timestamp}.{$payload}\";\\n' +\n \" $hex = str_starts_with($secret, 'whsec_') ? substr($secret, 6) : $secret;\\n\" +\n \" $expected = base64_encode(hash_hmac('sha256', $signed, hex2bin($hex), true));\\n\" +\n \" foreach (explode(' ', $signature) as $sig) {\\n\" +\n ' if (hash_equals(substr($sig, 3), $expected)) {\\n' +\n ' return true;\\n' +\n ' }\\n' +\n ' }\\n' +\n ' return false;\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n laravel: {\n channel: 'webhook',\n language: 'php',\n framework: 'laravel',\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/Webhooks/ZyphrWebhook.php',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Webhooks;\\n\\n' +\n 'class ZyphrWebhook\\n' +\n '{\\n' +\n ' public static function verify(string $payload, array $headers, string $secret): bool\\n' +\n ' {\\n' +\n \" $msgId = $headers['webhook-id'][0] ?? '';\\n\" +\n \" $timestamp = $headers['webhook-timestamp'][0] ?? '';\\n\" +\n \" $signature = $headers['webhook-signature'][0] ?? '';\\n\" +\n ' if (abs(time() - intval($timestamp)) > 300) {\\n' +\n ' return false;\\n' +\n ' }\\n' +\n ' $signed = \"{$msgId}.{$timestamp}.{$payload}\";\\n' +\n \" $hex = str_starts_with($secret, 'whsec_') ? substr($secret, 6) : $secret;\\n\" +\n \" $expected = base64_encode(hash_hmac('sha256', $signed, hex2bin($hex), true));\\n\" +\n \" foreach (explode(' ', $signature) as $sig) {\\n\" +\n ' if (hash_equals(substr($sig, 3), $expected)) {\\n' +\n ' return true;\\n' +\n ' }\\n' +\n ' }\\n' +\n ' return false;\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'app/Http/Controllers/ZyphrWebhookController.php',\n purpose: 'Laravel controller that verifies the webhook signature before processing',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Http\\\\Controllers;\\n\\n' +\n 'use App\\\\Webhooks\\\\ZyphrWebhook;\\n' +\n 'use Illuminate\\\\Http\\\\Request;\\n\\n' +\n 'class ZyphrWebhookController extends Controller\\n' +\n '{\\n' +\n ' public function handle(Request $request)\\n' +\n ' {\\n' +\n \" $payload = $request->getContent();\\n\" +\n \" $secret = config('services.zyphr.webhook_secret');\\n\" +\n \" if (! ZyphrWebhook::verify($payload, $request->headers->all(), $secret)) {\\n\" +\n \" return response('invalid signature', 401);\\n\" +\n ' }\\n' +\n \" $event = json_decode($payload, true);\\n\" +\n \" \\\\Log::info('zyphr event ' . ($event['type'] ?? 'unknown'));\\n\" +\n \" return response()->noContent();\\n\" +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n \"Register route in routes/api.php: Route::post('/webhooks/zyphr', [ZyphrWebhookController::class, 'handle']);\",\n \"Exclude this route from CSRF (VerifyCsrfToken::$except).\",\n ],\n docsUrl: DOCS,\n },\n },\n },\n csharp: {\n sdk: {\n channel: 'webhook',\n language: 'csharp',\n framework: null,\n variant: 'webhook-handler',\n files: [\n {\n path: 'Webhooks/ZyphrWebhookVerifier.cs',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n 'using System.Security.Cryptography;\\n' +\n 'using System.Text;\\n\\n' +\n 'namespace YourApp.Webhooks;\\n\\n' +\n 'public static class ZyphrWebhookVerifier\\n' +\n '{\\n' +\n ' public static bool Verify(string payload, IDictionary<string,string> headers, string secret)\\n' +\n ' {\\n' +\n ' var msgId = headers.TryGetValue(\"webhook-id\", out var id) ? id : \"\";\\n' +\n ' var timestamp = headers.TryGetValue(\"webhook-timestamp\", out var ts) ? long.Parse(ts) : 0;\\n' +\n ' var signatures = headers.TryGetValue(\"webhook-signature\", out var sig) ? sig : \"\";\\n\\n' +\n ' var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();\\n' +\n ' if (Math.Abs(now - timestamp) > 300) return false;\\n\\n' +\n ' var signed = $\"{msgId}.{timestamp}.{payload}\";\\n' +\n ' var hex = secret.StartsWith(\"whsec_\") ? secret[6..] : secret;\\n' +\n ' var secretBytes = Convert.FromHexString(hex);\\n' +\n ' using var hmac = new HMACSHA256(secretBytes);\\n' +\n ' var expected = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(signed)));\\n\\n' +\n ' foreach (var s in signatures.Split(\\' \\'))\\n' +\n ' {\\n' +\n ' var v = s.Length > 3 ? s[3..] : \"\";\\n' +\n ' if (CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(v), Encoding.UTF8.GetBytes(expected)))\\n' +\n ' return true;\\n' +\n ' }\\n' +\n ' return false;\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n aspnetcore: {\n channel: 'webhook',\n language: 'csharp',\n framework: 'aspnetcore',\n variant: 'webhook-handler',\n files: [\n {\n path: 'Webhooks/ZyphrWebhookVerifier.cs',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n 'using System.Security.Cryptography;\\n' +\n 'using System.Text;\\n\\n' +\n 'namespace YourApp.Webhooks;\\n\\n' +\n 'public static class ZyphrWebhookVerifier\\n' +\n '{\\n' +\n ' public static bool Verify(string payload, IHeaderDictionary headers, string secret)\\n' +\n ' {\\n' +\n ' string msgId = headers[\"webhook-id\"].ToString();\\n' +\n ' long timestamp = long.TryParse(headers[\"webhook-timestamp\"], out var t) ? t : 0;\\n' +\n ' string signatures = headers[\"webhook-signature\"].ToString();\\n' +\n ' if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - timestamp) > 300) return false;\\n' +\n ' var signed = $\"{msgId}.{timestamp}.{payload}\";\\n' +\n ' var hex = secret.StartsWith(\"whsec_\") ? secret[6..] : secret;\\n' +\n ' using var hmac = new HMACSHA256(Convert.FromHexString(hex));\\n' +\n ' var expected = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(signed)));\\n' +\n ' foreach (var s in signatures.Split(\\' \\'))\\n' +\n ' {\\n' +\n ' var v = s.Length > 3 ? s[3..] : \"\";\\n' +\n ' if (CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(v), Encoding.UTF8.GetBytes(expected)))\\n' +\n ' return true;\\n' +\n ' }\\n' +\n ' return false;\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'Controllers/ZyphrWebhookController.cs',\n purpose: 'ASP.NET Core controller that verifies the webhook signature before processing',\n contents:\n 'using Microsoft.AspNetCore.Mvc;\\n' +\n 'using YourApp.Webhooks;\\n\\n' +\n '[ApiController]\\n' +\n '[Route(\"webhooks/zyphr\")]\\n' +\n 'public class ZyphrWebhookController : ControllerBase\\n' +\n '{\\n' +\n ' [HttpPost]\\n' +\n ' public async Task<IActionResult> Handle()\\n' +\n ' {\\n' +\n ' using var reader = new StreamReader(Request.Body);\\n' +\n ' var payload = await reader.ReadToEndAsync();\\n' +\n ' var secret = Environment.GetEnvironmentVariable(\"ZYPHR_WEBHOOK_SECRET\")!;\\n' +\n ' if (!ZyphrWebhookVerifier.Verify(payload, Request.Headers, secret))\\n' +\n ' return Unauthorized(\"invalid signature\");\\n' +\n ' Console.WriteLine($\"zyphr event {payload[..Math.Min(80, payload.Length)]}\");\\n' +\n ' return NoContent();\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n },\n};\n","import type { SdkLanguage, QuickstartChannel } from '../../schemas.js';\nimport type { QuickstartChannelMap, QuickstartResult } from '../quickstart-types.js';\nimport { emailChannel } from './email.js';\nimport { inboxChannel } from './inbox.js';\nimport { pushChannel } from './push.js';\nimport { smsChannel } from './sms.js';\nimport { webhookChannel } from './webhook.js';\n\nconst REGISTRY: Record<QuickstartChannel, QuickstartChannelMap> = {\n email: emailChannel,\n push: pushChannel,\n sms: smsChannel,\n inbox: inboxChannel,\n webhook: webhookChannel,\n};\n\nexport interface ResolveQuickstartArgs {\n channel: QuickstartChannel;\n language: SdkLanguage;\n framework?: string;\n}\n\nexport interface ResolveQuickstartResult {\n result: QuickstartResult;\n frameworkRecognized: boolean;\n}\n\nexport function resolveQuickstart(\n args: ResolveQuickstartArgs,\n): ResolveQuickstartResult | null {\n const langMap = REGISTRY[args.channel]?.[args.language];\n if (!langMap) return null;\n\n if (args.framework) {\n const key = args.framework.trim().toLowerCase();\n const fw = langMap.frameworks?.[key];\n if (fw) return { result: fw, frameworkRecognized: true };\n // unknown framework → fall back to plain SDK variant, signal recognition status\n return { result: langMap.sdk, frameworkRecognized: false };\n }\n\n return { result: langMap.sdk, frameworkRecognized: true };\n}\n\nexport const QUICKSTART_REGISTRY = REGISTRY;\n","// TODO(v0.3): auto-generate these snippets from packages/sdk/<lang>/examples/\n// so docs and MCP output never drift. For v0.2 they are hand-maintained and\n// must mirror apps/docs/docs/sdks/<lang>.md exactly.\nimport type { SdkLanguage } from '../schemas.js';\n\nexport interface InstallCommand {\n manager: string;\n command: string;\n}\n\nexport interface InitSnippet {\n imports: string;\n init: string;\n fileExample: string;\n}\n\nexport type SdkKind = 'sdk' | 'rest-client';\n\nexport interface SdkInstallEntry {\n language: SdkLanguage;\n kind: SdkKind;\n packageName: string;\n registry: string;\n registryUrl: string;\n installCommands: InstallCommand[];\n initSnippet: InitSnippet;\n envVarsNeeded: string[];\n docsUrl: string;\n notes?: string;\n}\n\nconst DOCS = 'https://docs.zyphr.dev/sdks';\n\nexport const SDK_INSTALL_TABLE: Record<SdkLanguage, SdkInstallEntry> = {\n node: {\n language: 'node',\n kind: 'sdk',\n packageName: '@zyphr-dev/node-sdk',\n registry: 'npm',\n registryUrl: 'https://www.npmjs.com/package/@zyphr-dev/node-sdk',\n installCommands: [\n { manager: 'npm', command: 'npm install @zyphr-dev/node-sdk' },\n { manager: 'yarn', command: 'yarn add @zyphr-dev/node-sdk' },\n { manager: 'pnpm', command: 'pnpm add @zyphr-dev/node-sdk' },\n ],\n initSnippet: {\n imports: \"import { Zyphr } from '@zyphr-dev/node-sdk';\",\n init: \"export const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\",\n fileExample: 'src/lib/zyphr.ts',\n },\n envVarsNeeded: ['ZYPHR_API_KEY'],\n docsUrl: `${DOCS}/node`,\n },\n csharp: {\n language: 'csharp',\n kind: 'sdk',\n packageName: 'ZyphrDev.SDK',\n registry: 'NuGet',\n registryUrl: 'https://www.nuget.org/packages/ZyphrDev.SDK',\n installCommands: [\n { manager: 'dotnet', command: 'dotnet add package ZyphrDev.SDK' },\n { manager: 'nuget', command: 'Install-Package ZyphrDev.SDK' },\n ],\n initSnippet: {\n imports: [\n 'using ZyphrDev.SDK.Api;',\n 'using ZyphrDev.SDK.Client;',\n 'using ZyphrDev.SDK.Model;',\n ].join('\\n'),\n init: [\n 'var config = new Configuration',\n '{',\n ' ApiKey = new Dictionary<string, string>',\n ' {',\n ' { \"X-API-Key\", Environment.GetEnvironmentVariable(\"ZYPHR_API_KEY\")! }',\n ' }',\n '};',\n 'var emails = new EmailsApi(config);',\n ].join('\\n'),\n fileExample: 'Services/ZyphrClient.cs',\n },\n envVarsNeeded: ['ZYPHR_API_KEY'],\n docsUrl: `${DOCS}/csharp`,\n },\n ruby: {\n language: 'ruby',\n kind: 'sdk',\n packageName: 'zyphr',\n registry: 'RubyGems',\n registryUrl: 'https://rubygems.org/gems/zyphr',\n installCommands: [\n { manager: 'gem', command: 'gem install zyphr' },\n { manager: 'bundler', command: \"bundle add zyphr\" },\n ],\n initSnippet: {\n imports: \"require 'zyphr'\",\n init: [\n 'Zyphr.configure do |config|',\n \" config.api_key['X-API-Key'] = ENV.fetch('ZYPHR_API_KEY')\",\n 'end',\n ].join('\\n'),\n fileExample: 'config/initializers/zyphr.rb',\n },\n envVarsNeeded: ['ZYPHR_API_KEY'],\n docsUrl: `${DOCS}/ruby`,\n },\n python: {\n language: 'python',\n kind: 'rest-client',\n packageName: 'requests',\n registry: 'PyPI',\n registryUrl: 'https://pypi.org/project/requests/',\n installCommands: [\n { manager: 'pip', command: 'pip install requests' },\n { manager: 'poetry', command: 'poetry add requests' },\n { manager: 'uv', command: 'uv add requests' },\n ],\n initSnippet: {\n imports: 'import os\\nimport requests',\n init: [\n 'ZYPHR_API_KEY = os.environ[\"ZYPHR_API_KEY\"]',\n 'BASE_URL = \"https://api.zyphr.dev/v1\"',\n '',\n 'headers = {',\n ' \"X-API-Key\": ZYPHR_API_KEY,',\n ' \"Content-Type\": \"application/json\",',\n '}',\n '',\n 'def zyphr_request(method, path, json=None, params=None):',\n ' response = requests.request(',\n ' method, f\"{BASE_URL}{path}\", headers=headers, json=json, params=params,',\n ' )',\n ' response.raise_for_status()',\n ' return response.json()',\n ].join('\\n'),\n fileExample: 'app/zyphr_client.py',\n },\n envVarsNeeded: ['ZYPHR_API_KEY'],\n docsUrl: `${DOCS}/python`,\n notes:\n 'There is no official Zyphr Python SDK yet — the canonical integration is a thin REST wrapper around the requests library.',\n },\n go: {\n language: 'go',\n kind: 'rest-client',\n packageName: 'net/http (stdlib)',\n registry: 'stdlib',\n registryUrl: 'https://pkg.go.dev/net/http',\n installCommands: [\n { manager: 'go', command: '# No install needed — net/http ships with Go.' },\n ],\n initSnippet: {\n imports: [\n 'package zyphr',\n '',\n 'import (',\n '\\t\"bytes\"',\n '\\t\"encoding/json\"',\n '\\t\"fmt\"',\n '\\t\"io\"',\n '\\t\"net/http\"',\n '\\t\"os\"',\n ')',\n ].join('\\n'),\n init: [\n 'const baseURL = \"https://api.zyphr.dev/v1\"',\n '',\n 'type Client struct {',\n '\\tAPIKey string',\n '\\tHTTPClient *http.Client',\n '}',\n '',\n 'func NewClient() *Client {',\n '\\treturn &Client{APIKey: os.Getenv(\"ZYPHR_API_KEY\"), HTTPClient: &http.Client{}}',\n '}',\n '',\n 'func (c *Client) Do(method, path string, body any) ([]byte, error) {',\n '\\tvar buf io.Reader',\n '\\tif body != nil {',\n '\\t\\tb, err := json.Marshal(body)',\n '\\t\\tif err != nil { return nil, fmt.Errorf(\"marshal: %w\", err) }',\n '\\t\\tbuf = bytes.NewReader(b)',\n '\\t}',\n '\\treq, err := http.NewRequest(method, baseURL+path, buf)',\n '\\tif err != nil { return nil, err }',\n '\\treq.Header.Set(\"X-API-Key\", c.APIKey)',\n '\\treq.Header.Set(\"Content-Type\", \"application/json\")',\n '\\tresp, err := c.HTTPClient.Do(req)',\n '\\tif err != nil { return nil, err }',\n '\\tdefer resp.Body.Close()',\n '\\tdata, _ := io.ReadAll(resp.Body)',\n '\\tif resp.StatusCode >= 400 { return nil, fmt.Errorf(\"zyphr %d: %s\", resp.StatusCode, data) }',\n '\\treturn data, nil',\n '}',\n ].join('\\n'),\n fileExample: 'internal/zyphr/client.go',\n },\n envVarsNeeded: ['ZYPHR_API_KEY'],\n docsUrl: `${DOCS}/go`,\n notes:\n 'There is no official Zyphr Go SDK yet — the canonical integration is a thin REST wrapper around net/http.',\n },\n php: {\n language: 'php',\n kind: 'rest-client',\n packageName: 'guzzlehttp/guzzle',\n registry: 'Packagist',\n registryUrl: 'https://packagist.org/packages/guzzlehttp/guzzle',\n installCommands: [\n { manager: 'composer', command: 'composer require guzzlehttp/guzzle' },\n ],\n initSnippet: {\n imports: [\n '<?php',\n '',\n 'use GuzzleHttp\\\\Client;',\n ].join('\\n'),\n init: [\n '$client = new Client([',\n \" 'base_uri' => 'https://api.zyphr.dev/v1/',\",\n \" 'headers' => [\",\n \" 'X-API-Key' => getenv('ZYPHR_API_KEY'),\",\n \" 'Content-Type' => 'application/json',\",\n ' ],',\n ']);',\n ].join('\\n'),\n fileExample: 'app/Services/ZyphrClient.php',\n },\n envVarsNeeded: ['ZYPHR_API_KEY'],\n docsUrl: `${DOCS}/php`,\n notes:\n 'There is no official Zyphr PHP SDK yet — the canonical integration uses Guzzle (or raw cURL).',\n },\n};\n\nexport function resolveInstallEntry(\n language: SdkLanguage,\n packageManager?: string,\n): SdkInstallEntry {\n const entry = SDK_INSTALL_TABLE[language];\n if (!packageManager) return entry;\n\n const normalized = packageManager.trim().toLowerCase();\n const match = entry.installCommands.find((c) => c.manager.toLowerCase() === normalized);\n if (!match) return entry;\n\n return { ...entry, installCommands: [match] };\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { isToolEnabled, type ToolGuards } from '../config.js';\nimport { resolveQuickstart } from '../integration/quickstart/index.js';\nimport { resolveInstallEntry } from '../integration/sdk-snippets.js';\nimport { toolResult } from '../result.js';\nimport { getQuickstartShape, getSdkInstallShape } from '../schemas.js';\n\nexport function registerIntegrationTools(server: McpServer, guards: ToolGuards): void {\n if (isToolEnabled({ name: 'get_sdk_install_for_language', mutates: false }, guards)) {\n server.registerTool(\n 'get_sdk_install_for_language',\n {\n title: 'Get SDK install instructions for a language',\n description:\n 'Returns install commands, init snippet, env vars, and docs URL for the chosen language. Use this when wiring Zyphr into a new project so the AI can drop the correct package + client init code.',\n inputSchema: getSdkInstallShape,\n },\n async (args) => {\n const entry = resolveInstallEntry(args.language, args.packageManager);\n return toolResult(entry);\n },\n );\n }\n\n if (isToolEnabled({ name: 'get_quickstart_for_channel', mutates: false }, guards)) {\n server.registerTool(\n 'get_quickstart_for_channel',\n {\n title: 'Get quickstart code for a channel + language',\n description:\n 'Returns drop-in service file(s) for the chosen Zyphr channel (email/push/sms/inbox/webhook), language, and optional framework (express, nextjs, flask, fastapi, rails, laravel, aspnetcore). Webhook handlers ALWAYS verify HMAC signatures. Unknown frameworks fall back to plain SDK code.',\n inputSchema: getQuickstartShape,\n },\n async (args) => {\n const resolved = resolveQuickstart({\n channel: args.channel,\n language: args.language,\n framework: args.framework,\n });\n if (!resolved) {\n return toolResult({\n error: `No quickstart available for channel=${args.channel} language=${args.language}`,\n });\n }\n const { result, frameworkRecognized } = resolved;\n return toolResult({\n ...result,\n frameworkRecognized,\n requestedFramework: args.framework ?? null,\n });\n },\n );\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { getZyphrClient } from '../client.js';\nimport { isToolEnabled, type ToolGuards } from '../config.js';\nimport { runTool } from '../result.js';\nimport {\n createWebhookShape,\n getWebhookDeliveriesShape,\n listWebhooksShape,\n} from '../schemas.js';\n\nexport function registerWebhookTools(server: McpServer, guards: ToolGuards): void {\n if (isToolEnabled({ name: 'list_webhooks', mutates: false }, guards)) {\n server.registerTool(\n 'list_webhooks',\n {\n title: 'List webhooks',\n description: 'List webhook endpoints configured for the account.',\n inputSchema: listWebhooksShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.webhooks.listWebhooks(args.limit, args.offset);\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'create_webhook', mutates: true }, guards)) {\n server.registerTool(\n 'create_webhook',\n {\n title: 'Create webhook',\n description:\n 'Register a new webhook endpoint. Subscribe to event types like \"email.*\", \"subscriber.created\", or \"*\".',\n inputSchema: createWebhookShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.webhooks.createWebhook({\n url: args.url,\n events: args.events,\n description: args.description,\n secret: args.secret,\n metadata: args.metadata,\n headers: args.headers,\n version: args.version,\n rateLimit: args.rateLimit,\n });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'get_webhook_deliveries', mutates: false }, guards)) {\n server.registerTool(\n 'get_webhook_deliveries',\n {\n title: 'List webhook deliveries',\n description:\n 'Inspect delivery history for a webhook endpoint. Filter by status (pending/delivering/delivered/failed/exhausted), event type, or date range. Useful for debugging why a webhook is not firing.',\n inputSchema: getWebhookDeliveriesShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.webhooks.listWebhookDeliveries(\n args.webhookId,\n args.status,\n args.eventType,\n args.search,\n args.startDate ? new Date(args.startDate) : undefined,\n args.endDate ? new Date(args.endDate) : undefined,\n args.limit,\n args.offset,\n );\n });\n },\n );\n }\n}\n"],"mappings":"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","names":["DOCS","ENV","NEXT_STEPS","DOCS","ENV","NEXT_STEPS","DOCS","ENV","NEXT_STEPS","DOCS","ENV","NEXT_STEPS","DOCS"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/server.ts","../src/config.ts","../src/client.ts","../src/result.ts","../src/schemas.ts","../src/tools/domains.ts","../src/tools/send.ts","../src/tools/subscribers.ts","../src/tools/templates.ts","../src/integration/quickstart/email.ts","../src/integration/quickstart/inbox.ts","../src/integration/quickstart/push.ts","../src/integration/quickstart/sms.ts","../src/integration/quickstart/webhook.ts","../src/integration/quickstart/index.ts","../src/integration/sdk-snippets.ts","../src/tools/integration.ts","../src/tools/webhooks.ts"],"sourcesContent":["import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { createServer } from './server.js';\nimport { getBaseUrl } from './client.js';\n\nasync function main(): Promise<void> {\n // Touch the env early so misconfiguration fails fast with a clear message.\n if (!process.env.ZYPHR_API_KEY) {\n process.stderr.write(\n '[zyphr-mcp] ZYPHR_API_KEY is not set. Provide a zy_live_* or zy_test_* key via the `env` block of your MCP client config.\\n',\n );\n process.exit(1);\n }\n\n const server = createServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n process.stderr.write(`[zyphr-mcp] connected (base=${getBaseUrl()})\\n`);\n}\n\nmain().catch((err) => {\n process.stderr.write(`[zyphr-mcp] fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\\n`);\n process.exit(1);\n});\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { loadToolGuards } from './config.js';\nimport { registerDomainTools } from './tools/domains.js';\nimport { registerSendTools } from './tools/send.js';\nimport { registerSubscriberTools } from './tools/subscribers.js';\nimport { registerTemplateTools } from './tools/templates.js';\nimport { registerIntegrationTools } from './tools/integration.js';\nimport { registerWebhookTools } from './tools/webhooks.js';\n\nexport const SERVER_NAME = 'zyphr';\nexport const SERVER_VERSION = '0.1.0';\n\nexport function createServer(): McpServer {\n const server = new McpServer(\n { name: SERVER_NAME, version: SERVER_VERSION },\n { capabilities: { tools: {} } },\n );\n\n const guards = loadToolGuards();\n registerSendTools(server, guards);\n registerTemplateTools(server, guards);\n registerSubscriberTools(server, guards);\n registerWebhookTools(server, guards);\n registerDomainTools(server, guards);\n registerIntegrationTools(server, guards);\n\n return server;\n}\n","export interface ToolGuards {\n readOnly: boolean;\n allowedTools: Set<string> | null;\n}\n\nexport function loadToolGuards(): ToolGuards {\n const readOnly = process.env.ZYPHR_READ_ONLY === 'true' || process.env.ZYPHR_READ_ONLY === '1';\n const raw = process.env.ZYPHR_ALLOWED_TOOLS;\n const allowedTools = raw\n ? new Set(\n raw\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean),\n )\n : null;\n return { readOnly, allowedTools };\n}\n\nexport interface ToolDefinition {\n name: string;\n mutates: boolean;\n}\n\nexport function isToolEnabled(tool: ToolDefinition, guards: ToolGuards): boolean {\n if (guards.allowedTools) {\n return guards.allowedTools.has(tool.name);\n }\n if (guards.readOnly && tool.mutates) {\n return false;\n }\n return true;\n}\n","import { Zyphr } from '@zyphr-dev/node-sdk';\n\nconst DEFAULT_BASE_URL = 'https://api.zyphr.dev/v1';\n\nlet cached: Zyphr | undefined;\n\nexport function getZyphrClient(): Zyphr {\n if (cached) return cached;\n\n const apiKey = process.env.ZYPHR_API_KEY;\n if (!apiKey) {\n process.stderr.write(\n '[zyphr-mcp] ZYPHR_API_KEY is not set. Provide a zy_live_* or zy_test_* key via the `env` block of your MCP client config.\\n',\n );\n process.exit(1);\n }\n\n const baseUrl = process.env.ZYPHR_BASE_URL || DEFAULT_BASE_URL;\n cached = new Zyphr({ apiKey, baseUrl });\n return cached;\n}\n\nexport function getBaseUrl(): string {\n return process.env.ZYPHR_BASE_URL || DEFAULT_BASE_URL;\n}\n","import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { ZyphrError, ZyphrRateLimitError } from '@zyphr-dev/node-sdk';\n\nexport function toolResult(data: unknown): CallToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],\n };\n}\n\nexport async function runTool(fn: () => Promise<unknown>): Promise<CallToolResult> {\n try {\n const data = await fn();\n return toolResult(data);\n } catch (err: unknown) {\n return await renderApiError(err);\n }\n}\n\nfunction errorPayload(content: Record<string, unknown>): CallToolResult {\n return {\n isError: true,\n content: [{ type: 'text', text: JSON.stringify(content, null, 2) }],\n };\n}\n\nasync function renderApiError(err: unknown): Promise<CallToolResult> {\n // Typed SDK errors — surface the full envelope so the AI can act on it.\n if (err instanceof ZyphrError) {\n const payload: Record<string, unknown> = {\n name: err.name,\n message: err.message,\n status: err.status,\n };\n if (err.code) payload.code = err.code;\n if (err.requestId) payload.requestId = err.requestId;\n if (err.details) payload.details = err.details;\n if (err instanceof ZyphrRateLimitError && err.retryAfter !== undefined) {\n payload.retryAfter = err.retryAfter;\n }\n return errorPayload(payload);\n }\n\n // Raw Response error (rare — SDK middleware usually parses first).\n if (err && typeof err === 'object' && 'response' in err) {\n const response = (err as { response?: Response }).response;\n if (response && typeof response.text === 'function') {\n try {\n const text = await response.text();\n let parsed: unknown = text;\n try {\n parsed = JSON.parse(text);\n } catch {\n /* keep raw text */\n }\n return errorPayload({ status: response.status, body: parsed });\n } catch {\n /* fall through to generic message */\n }\n }\n }\n\n const message = err instanceof Error ? err.message : String(err);\n const name = err instanceof Error ? err.name : 'Error';\n return errorPayload({ name, message });\n}\n","import { z } from 'zod';\n\nconst emailAddress = z.object({\n email: z.string().email(),\n name: z.string().optional(),\n});\n\nconst recipient = z.union([z.string().email(), emailAddress]);\n\nexport const sendEmailShape = {\n to: z\n .union([recipient, z.array(recipient).min(1)])\n .describe('Recipient email address (string or {email,name}) or an array of them'),\n from: z\n .union([z.string().email(), emailAddress])\n .optional()\n .describe('Sender address. Defaults to the account-level \"from\" address.'),\n replyTo: z.union([z.string().email(), emailAddress]).optional(),\n cc: z.array(z.string().email()).optional(),\n bcc: z.array(z.string().email()).optional(),\n subject: z.string().min(1),\n html: z.string().optional().describe('Rendered HTML body. Mutually exclusive with templateId.'),\n text: z.string().optional().describe('Plain-text body. Mutually exclusive with templateId.'),\n templateId: z.string().optional().describe('Template ID. When set, html/text are ignored.'),\n templateData: z\n .record(z.unknown())\n .optional()\n .describe('Variables to interpolate into the template'),\n tags: z.array(z.string()).optional(),\n metadata: z.record(z.unknown()).optional(),\n subscriberId: z.string().optional(),\n category: z.string().optional(),\n scheduledAt: z\n .string()\n .datetime()\n .optional()\n .describe('ISO 8601 timestamp for scheduled delivery'),\n} as const;\n\nexport const sendPushShape = {\n userId: z.string().optional().describe('Send to all devices for this user/subscriber'),\n deviceId: z.string().optional().describe('Send to a specific device only'),\n title: z.string().optional(),\n body: z.string().optional(),\n data: z.record(z.unknown()).optional().describe('Custom data payload delivered to the device'),\n badge: z.number().int().nonnegative().optional(),\n sound: z.string().optional(),\n imageUrl: z.string().url().optional(),\n contentAvailable: z.boolean().optional().describe('Silent/background push'),\n tags: z.array(z.string()).optional(),\n metadata: z.record(z.unknown()).optional(),\n collapseKey: z.string().optional(),\n subscriberId: z.string().optional(),\n subscriberExternalId: z.string().optional(),\n category: z.string().optional(),\n force: z.boolean().optional().describe('Skip subscriber preference checks'),\n sendAt: z.string().datetime().optional(),\n delay: z.number().int().nonnegative().optional(),\n} as const;\n\nexport const sendSmsShape = {\n to: z.string().min(1).describe('Recipient phone number in E.164 format (e.g. +14155551234)'),\n from: z.string().optional().describe('Sender phone number or sender ID'),\n body: z.string().min(1),\n subscriberId: z.string().optional(),\n scheduledAt: z.string().datetime().optional(),\n metadata: z.record(z.unknown()).optional(),\n} as const;\n\n// Integration\n\nexport const sdkLanguages = ['node', 'python', 'ruby', 'go', 'php', 'csharp'] as const;\nexport type SdkLanguage = (typeof sdkLanguages)[number];\n\nexport const quickstartChannels = ['email', 'push', 'sms', 'inbox', 'webhook'] as const;\nexport type QuickstartChannel = (typeof quickstartChannels)[number];\n\nexport const getQuickstartShape = {\n channel: z\n .enum(quickstartChannels)\n .describe('Which Zyphr channel to wire up'),\n language: z\n .enum(['node', 'python', 'ruby', 'go', 'php', 'csharp'])\n .describe('Target language for the integration'),\n framework: z\n .string()\n .optional()\n .describe(\n 'Optional framework hint (e.g. \"express\", \"nextjs\", \"flask\", \"fastapi\", \"rails\", \"gin\", \"laravel\", \"aspnetcore\"). Falls back to plain SDK code when unrecognized.',\n ),\n} as const;\n\nexport const getSdkInstallShape = {\n language: z\n .enum(sdkLanguages)\n .describe('Target language for the integration'),\n packageManager: z\n .string()\n .optional()\n .describe(\n 'Optional package manager override (e.g. \"yarn\" instead of \"npm\"). When recognized, only that manager is returned; otherwise the full list is returned.',\n ),\n} as const;\n\n// Templates\n\nexport const listTemplatesShape = {\n limit: z.number().int().positive().max(200).optional(),\n offset: z.number().int().nonnegative().optional(),\n} as const;\n\nexport const getTemplateShape = {\n id: z.string().min(1).describe('Template ID'),\n} as const;\n\nexport const renderTemplateShape = {\n id: z.string().min(1).describe('Template ID'),\n variables: z\n .record(z.unknown())\n .describe('Key/value variables to interpolate into the template'),\n} as const;\n\nexport const createTemplateShape = {\n name: z.string().min(1),\n description: z.string().optional(),\n subject: z.string().optional().describe('Default subject (email templates)'),\n html: z.string().optional(),\n text: z.string().optional(),\n} as const;\n\n// Subscribers\n\nexport const findSubscriberShape = {\n externalId: z\n .string()\n .min(1)\n .describe('Subscriber external ID (your application user/customer ID)'),\n} as const;\n\nexport const listSubscribersShape = {\n status: z.enum(['active', 'inactive']).optional(),\n email: z.string().email().optional(),\n limit: z.number().int().positive().max(200).optional(),\n offset: z.number().int().nonnegative().optional(),\n} as const;\n\nexport const createSubscriberShape = {\n externalId: z.string().min(1).describe('Your application user/customer ID'),\n email: z.string().email().optional(),\n phone: z.string().optional(),\n name: z.string().optional(),\n avatarUrl: z.string().url().optional(),\n timezone: z.string().optional(),\n locale: z.string().optional(),\n metadata: z.record(z.unknown()).optional(),\n} as const;\n\nexport const updateSubscriberShape = {\n id: z.string().min(1).describe('Zyphr subscriber ID'),\n email: z.string().email().nullable().optional(),\n phone: z.string().nullable().optional(),\n name: z.string().nullable().optional(),\n avatarUrl: z.string().url().nullable().optional(),\n timezone: z.string().optional(),\n locale: z.string().optional(),\n metadata: z.record(z.unknown()).optional(),\n status: z.enum(['active', 'inactive']).optional(),\n} as const;\n\nexport const setSubscriberPreferencesShape = {\n id: z.string().min(1).describe('Zyphr subscriber ID'),\n preferences: z\n .array(\n z.object({\n categoryId: z.string().optional(),\n channel: z.string().optional().describe('email | push | sms | in_app'),\n enabled: z.boolean().optional(),\n }),\n )\n .min(1),\n} as const;\n\n// Webhooks\n\nexport const listWebhooksShape = {\n limit: z.number().int().positive().max(200).optional(),\n offset: z.number().int().nonnegative().optional(),\n} as const;\n\nexport const createWebhookShape = {\n url: z.string().url().describe('Receiver URL that Zyphr will POST events to'),\n events: z\n .array(z.string().min(1))\n .min(1)\n .describe('Event types to subscribe to (e.g. [\"email.*\", \"subscriber.created\"])'),\n description: z.string().optional(),\n secret: z\n .string()\n .optional()\n .describe('Optional secret used to sign payloads. If omitted, Zyphr generates one.'),\n metadata: z.record(z.unknown()).optional(),\n headers: z.record(z.string()).optional().describe('Custom headers to send with every delivery'),\n version: z.string().optional(),\n rateLimit: z.number().int().positive().optional(),\n} as const;\n\nexport const getWebhookDeliveriesShape = {\n webhookId: z.string().min(1).describe('Webhook endpoint ID'),\n status: z.enum(['pending', 'delivering', 'delivered', 'failed', 'exhausted']).optional(),\n eventType: z.string().optional(),\n search: z.string().optional(),\n startDate: z.string().datetime().optional(),\n endDate: z.string().datetime().optional(),\n limit: z.number().int().positive().max(200).optional(),\n offset: z.number().int().nonnegative().optional(),\n} as const;\n\n// Domains (sc-5443)\n//\n// NOTE on what's intentionally absent: there is NO `deleteDomainShape` here\n// and there is no `delete_domain` tool registered anywhere in the MCP server.\n// `@zyphr-dev/mcp-server` does not expose destructive `delete_*` operations\n// by policy (epic 5306). Customers who need scripted domain teardown should\n// call `zyphr.domains.deleteDomain(id)` from the Node SDK directly.\n\nexport const addDomainShape = {\n domain: z\n .string()\n .min(1)\n .describe(\n 'The sending domain to add (e.g. \"mail.example.com\"). Will be lowercased and trimmed. Idempotent — calling with an already-registered domain returns the existing record.',\n ),\n} as const;\n\nexport const listDomainsShape = {} as const;\n\nexport const getDomainShape = {\n id: z.string().min(1).describe('Domain ID (UUID)'),\n} as const;\n\nexport const verifyDomainShape = {\n id: z\n .string()\n .min(1)\n .describe(\n 'Domain ID (UUID). Queues an asynchronous DNS verification check; poll get_domain (or subscribe to the domain.verified webhook) for terminal status.',\n ),\n} as const;\n\nexport const sendInboxMessageShape = {\n subscriberId: z.string().min(1).describe('Subscriber to deliver the in-app message to'),\n title: z.string().min(1),\n body: z.string().optional(),\n actionUrl: z.string().url().optional(),\n actionLabel: z.string().optional(),\n imageUrl: z.string().url().optional(),\n icon: z.string().optional(),\n category: z.string().optional(),\n priority: z.enum(['low', 'normal', 'high', 'urgent']).optional(),\n data: z.record(z.unknown()).optional(),\n tags: z.array(z.string()).optional(),\n expiresAt: z.string().datetime().optional(),\n} as const;\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { getZyphrClient } from '../client.js';\nimport { isToolEnabled, type ToolGuards } from '../config.js';\nimport { runTool } from '../result.js';\nimport {\n addDomainShape,\n getDomainShape,\n listDomainsShape,\n verifyDomainShape,\n} from '../schemas.js';\n\n/**\n * Domain-management MCP tools (sc-5443).\n *\n * Surface lets an AI agent complete the full domain-onboarding loop without\n * touching the dashboard: add a sending domain, get back the DNS records to\n * publish (already tagged with `purpose` per record), poll verification, then\n * `send_email` from that domain once `status === 'verified'`.\n *\n * NOT registered (by policy): `delete_domain`. Destructive operations are\n * intentionally absent from `@zyphr-dev/mcp-server` — one bad model call could\n * detach an SES identity, invalidate scheduled sends, and break templates\n * hardcoded to a sender. Customers who need scripted teardown call\n * `zyphr.domains.deleteDomain(id)` from the Node SDK directly.\n */\nexport function registerDomainTools(server: McpServer, guards: ToolGuards): void {\n if (isToolEnabled({ name: 'add_domain', mutates: true }, guards)) {\n server.registerTool(\n 'add_domain',\n {\n title: 'Add a sending domain',\n description:\n 'Register a new sending domain on the account. Returns the full set of DNS records (TXT + CNAME) the AI agent should publish to its DNS provider (Route53, Cloudflare, etc.) before email can send from this domain. Idempotent: calling with an already-registered domain returns the existing record.',\n inputSchema: addDomainShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.domains.addDomain({ domain: args.domain });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'list_domains', mutates: false }, guards)) {\n server.registerTool(\n 'list_domains',\n {\n title: 'List sending domains',\n description:\n 'List sending domains on the current project, with verification status and DNS records.',\n inputSchema: listDomainsShape,\n },\n async () => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.domains.listDomains();\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'get_domain', mutates: false }, guards)) {\n server.registerTool(\n 'get_domain',\n {\n title: 'Get a sending domain',\n description:\n 'Retrieve a single sending domain by ID, including per-record `verified` flags and `records_verified` / `records_total` counters. Useful for reasoning about partial verification (e.g. \"SPF verified, DKIM #2 still pending\").',\n inputSchema: getDomainShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.domains.getDomain(args.id);\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'verify_domain', mutates: true }, guards)) {\n server.registerTool(\n 'verify_domain',\n {\n title: 'Trigger domain verification',\n description:\n 'Queue an asynchronous DNS verification check for a sending domain. Returns a job_id immediately; poll `get_domain` (or subscribe to the `domain.verified` webhook event) for terminal status. Returns a 409 error if a verification is already in progress for this domain.',\n inputSchema: verifyDomainShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.domains.verifyDomain(args.id);\n });\n },\n );\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { getZyphrClient } from '../client.js';\nimport { isToolEnabled, type ToolGuards } from '../config.js';\nimport { runTool } from '../result.js';\nimport {\n sendEmailShape,\n sendInboxMessageShape,\n sendPushShape,\n sendSmsShape,\n} from '../schemas.js';\n\nfunction normalizeEmailAddress(value: unknown): { email: string; name?: string } | undefined {\n if (typeof value === 'string') return { email: value };\n if (value && typeof value === 'object' && 'email' in value) {\n return value as { email: string; name?: string };\n }\n return undefined;\n}\n\nfunction normalizeRecipients(to: unknown): { email: string; name?: string }[] {\n if (Array.isArray(to)) {\n return to.map(normalizeEmailAddress).filter((v): v is { email: string } => Boolean(v));\n }\n const one = normalizeEmailAddress(to);\n return one ? [one] : [];\n}\n\nexport function registerSendTools(server: McpServer, guards: ToolGuards): void {\n if (isToolEnabled({ name: 'send_email', mutates: true }, guards)) {\n server.registerTool(\n 'send_email',\n {\n title: 'Send email',\n description:\n 'Send a transactional email via Zyphr. Use either html/text OR templateId+templateData, not both.',\n inputSchema: sendEmailShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.emails.sendEmail({\n to: normalizeRecipients(args.to),\n from: normalizeEmailAddress(args.from),\n replyTo: normalizeEmailAddress(args.replyTo),\n cc: args.cc,\n bcc: args.bcc,\n subject: args.subject,\n html: args.html,\n text: args.text,\n templateId: args.templateId,\n templateData: args.templateData,\n tags: args.tags,\n metadata: args.metadata,\n subscriberId: args.subscriberId,\n category: args.category,\n scheduledAt: args.scheduledAt ? new Date(args.scheduledAt) : undefined,\n });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'send_push', mutates: true }, guards)) {\n server.registerTool(\n 'send_push',\n {\n title: 'Send push notification',\n description:\n 'Send a push notification. Target one of: userId (all devices for a user), deviceId (specific device), subscriberId, or subscriberExternalId.',\n inputSchema: sendPushShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.push.sendPush({\n userId: args.userId,\n deviceId: args.deviceId,\n title: args.title,\n body: args.body,\n data: args.data,\n badge: args.badge,\n sound: args.sound,\n imageUrl: args.imageUrl,\n contentAvailable: args.contentAvailable,\n tags: args.tags,\n metadata: args.metadata,\n collapseKey: args.collapseKey,\n subscriberId: args.subscriberId,\n subscriberExternalId: args.subscriberExternalId,\n category: args.category,\n force: args.force,\n sendAt: args.sendAt ? new Date(args.sendAt) : undefined,\n delay: args.delay,\n });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'send_sms', mutates: true }, guards)) {\n server.registerTool(\n 'send_sms',\n {\n title: 'Send SMS',\n description: 'Send an SMS message via Zyphr. The recipient must be in E.164 format.',\n inputSchema: sendSmsShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.sms.sendSms({\n to: args.to,\n from: args.from,\n body: args.body,\n subscriberId: args.subscriberId,\n scheduledAt: args.scheduledAt ? new Date(args.scheduledAt) : undefined,\n metadata: args.metadata,\n });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'send_inbox_message', mutates: true }, guards)) {\n server.registerTool(\n 'send_inbox_message',\n {\n title: 'Send in-app inbox message',\n description: 'Deliver an in-app inbox notification to a Zyphr subscriber.',\n inputSchema: sendInboxMessageShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.inbox.sendInApp({\n subscriberId: args.subscriberId,\n title: args.title,\n body: args.body,\n actionUrl: args.actionUrl,\n actionLabel: args.actionLabel,\n imageUrl: args.imageUrl,\n icon: args.icon,\n category: args.category,\n priority: args.priority,\n data: args.data,\n tags: args.tags,\n expiresAt: args.expiresAt ? new Date(args.expiresAt) : undefined,\n });\n });\n },\n );\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { getZyphrClient } from '../client.js';\nimport { isToolEnabled, type ToolGuards } from '../config.js';\nimport { runTool } from '../result.js';\nimport {\n createSubscriberShape,\n findSubscriberShape,\n listSubscribersShape,\n setSubscriberPreferencesShape,\n updateSubscriberShape,\n} from '../schemas.js';\n\nexport function registerSubscriberTools(server: McpServer, guards: ToolGuards): void {\n if (isToolEnabled({ name: 'find_subscriber', mutates: false }, guards)) {\n server.registerTool(\n 'find_subscriber',\n {\n title: 'Find subscriber by external ID',\n description:\n 'Look up a subscriber by the external ID you assigned (typically your application user/customer ID).',\n inputSchema: findSubscriberShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.subscribers.getSubscriberByExternalId(args.externalId);\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'list_subscribers', mutates: false }, guards)) {\n server.registerTool(\n 'list_subscribers',\n {\n title: 'List subscribers',\n description: 'List subscribers, optionally filtered by status or email.',\n inputSchema: listSubscribersShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.subscribers.listSubscribers(\n args.status,\n args.email,\n args.limit,\n args.offset,\n );\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'create_subscriber', mutates: true }, guards)) {\n server.registerTool(\n 'create_subscriber',\n {\n title: 'Create subscriber',\n description: 'Create a new subscriber. `externalId` must be unique within your account.',\n inputSchema: createSubscriberShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.subscribers.createSubscriber({\n externalId: args.externalId,\n email: args.email,\n phone: args.phone,\n name: args.name,\n avatarUrl: args.avatarUrl,\n timezone: args.timezone,\n locale: args.locale,\n metadata: args.metadata,\n });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'update_subscriber', mutates: true }, guards)) {\n server.registerTool(\n 'update_subscriber',\n {\n title: 'Update subscriber',\n description:\n 'Update a subscriber by Zyphr ID. Pass null for email/phone/name/avatarUrl to clear those fields.',\n inputSchema: updateSubscriberShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.subscribers.updateSubscriber(args.id, {\n email: args.email ?? undefined,\n phone: args.phone ?? undefined,\n name: args.name ?? undefined,\n avatarUrl: args.avatarUrl ?? undefined,\n timezone: args.timezone,\n locale: args.locale,\n metadata: args.metadata,\n status: args.status,\n });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'set_subscriber_preferences', mutates: true }, guards)) {\n server.registerTool(\n 'set_subscriber_preferences',\n {\n title: 'Set subscriber preferences',\n description:\n 'Set notification preferences for a subscriber. Each preference targets a category and/or channel and toggles `enabled`.',\n inputSchema: setSubscriberPreferencesShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.subscribers.setSubscriberPreferences(args.id, {\n preferences: args.preferences,\n });\n });\n },\n );\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { getZyphrClient } from '../client.js';\nimport { isToolEnabled, type ToolGuards } from '../config.js';\nimport { runTool } from '../result.js';\nimport {\n createTemplateShape,\n getTemplateShape,\n listTemplatesShape,\n renderTemplateShape,\n} from '../schemas.js';\n\nexport function registerTemplateTools(server: McpServer, guards: ToolGuards): void {\n if (isToolEnabled({ name: 'list_templates', mutates: false }, guards)) {\n server.registerTool(\n 'list_templates',\n {\n title: 'List templates',\n description: 'List notification templates in the account.',\n inputSchema: listTemplatesShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.templates.listTemplates(args.limit, args.offset);\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'get_template', mutates: false }, guards)) {\n server.registerTool(\n 'get_template',\n {\n title: 'Get template',\n description: 'Fetch a single template by ID.',\n inputSchema: getTemplateShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.templates.getTemplate(args.id);\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'render_template', mutates: false }, guards)) {\n server.registerTool(\n 'render_template',\n {\n title: 'Render template',\n description:\n 'Preview a template with the given variables WITHOUT sending. Returns the rendered subject/html/text so the AI can show the user what would be sent.',\n inputSchema: renderTemplateShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.templates.renderTemplate(args.id, { variables: args.variables });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'create_template', mutates: true }, guards)) {\n server.registerTool(\n 'create_template',\n {\n title: 'Create template',\n description: 'Create a new notification template.',\n inputSchema: createTemplateShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.templates.createTemplate({\n name: args.name,\n description: args.description,\n subject: args.subject,\n html: args.html,\n text: args.text,\n });\n });\n },\n );\n }\n}\n","import type { QuickstartChannelMap } from '../quickstart-types.js';\n\nconst DOCS = 'https://docs.zyphr.dev/channels/email';\n\nconst ENV: string[] = ['ZYPHR_API_KEY'];\n\nconst NEXT_STEPS = [\n 'Add ZYPHR_API_KEY to your .env file (run get_sdk_install_for_language to confirm the install).',\n 'Wire the new service into your app entrypoint.',\n 'Verify your sender domain in the Zyphr dashboard before sending to real recipients.',\n];\n\nexport const emailChannel: QuickstartChannelMap = {\n node: {\n sdk: {\n channel: 'email',\n language: 'node',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'src/lib/zyphr.ts',\n purpose: 'Zyphr SDK client singleton',\n contents:\n \"import { Zyphr } from '@zyphr-dev/node-sdk';\\n\\n\" +\n 'export const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\\n',\n overwrite: false,\n },\n {\n path: 'src/services/notify.ts',\n purpose: 'Sends a transactional email through Zyphr',\n contents:\n \"import { zyphr } from '../lib/zyphr.js';\\n\\n\" +\n 'export async function sendWelcomeEmail(to: string, name: string) {\\n' +\n ' return await zyphr.emails.sendEmail({\\n' +\n ' to: [{ email: to, name }],\\n' +\n ' subject: `Welcome, ${name}!`,\\n' +\n \" html: `<h1>Welcome aboard, ${name}!</h1><p>Glad you're here.</p>`,\\n\" +\n ' });\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n express: {\n channel: 'email',\n language: 'node',\n framework: 'express',\n variant: 'sdk',\n files: [\n {\n path: 'src/lib/zyphr.ts',\n purpose: 'Zyphr SDK client singleton',\n contents:\n \"import { Zyphr } from '@zyphr-dev/node-sdk';\\n\\n\" +\n 'export const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\\n',\n overwrite: false,\n },\n {\n path: 'src/routes/notify.ts',\n purpose: 'Express route that sends a welcome email',\n contents:\n \"import { Router } from 'express';\\n\" +\n \"import { zyphr } from '../lib/zyphr.js';\\n\\n\" +\n 'export const notifyRouter = Router();\\n\\n' +\n \"notifyRouter.post('/notify', async (req, res, next) => {\\n\" +\n ' try {\\n' +\n ' const { to, name } = req.body as { to: string; name: string };\\n' +\n ' const result = await zyphr.emails.sendEmail({\\n' +\n ' to: [{ email: to, name }],\\n' +\n ' subject: `Welcome, ${name}!`,\\n' +\n ' html: `<h1>Welcome, ${name}!</h1>`,\\n' +\n ' });\\n' +\n ' res.json(result);\\n' +\n ' } catch (err) {\\n' +\n ' next(err);\\n' +\n ' }\\n' +\n '});\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n \"Mount the router in app.ts: app.use('/api', notifyRouter)\",\n 'Test: curl -X POST http://localhost:3000/api/notify -d \\'{\"to\":\"you@example.com\",\"name\":\"You\"}\\' -H \"Content-Type: application/json\"',\n ],\n docsUrl: DOCS,\n },\n nextjs: {\n channel: 'email',\n language: 'node',\n framework: 'nextjs',\n variant: 'sdk',\n files: [\n {\n path: 'src/lib/zyphr.ts',\n purpose: 'Zyphr SDK client singleton (server-only)',\n contents:\n \"import 'server-only';\\n\" +\n \"import { Zyphr } from '@zyphr-dev/node-sdk';\\n\\n\" +\n 'export const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\\n',\n overwrite: false,\n },\n {\n path: 'src/app/api/notify/route.ts',\n purpose: 'Next.js App Router route handler that sends a welcome email',\n contents:\n \"import { NextResponse } from 'next/server';\\n\" +\n \"import { zyphr } from '@/lib/zyphr';\\n\\n\" +\n 'export async function POST(req: Request) {\\n' +\n ' const { to, name } = (await req.json()) as { to: string; name: string };\\n' +\n ' const result = await zyphr.emails.sendEmail({\\n' +\n ' to: [{ email: to, name }],\\n' +\n ' subject: `Welcome, ${name}!`,\\n' +\n ' html: `<h1>Welcome, ${name}!</h1>`,\\n' +\n ' });\\n' +\n ' return NextResponse.json(result);\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n 'Test: curl -X POST http://localhost:3000/api/notify -d \\'{\"to\":\"you@example.com\",\"name\":\"You\"}\\' -H \"Content-Type: application/json\"',\n ],\n docsUrl: DOCS,\n },\n },\n },\n python: {\n sdk: {\n channel: 'email',\n language: 'python',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/zyphr_client.py',\n purpose: 'REST helper for the Zyphr API',\n contents:\n 'import os\\n' +\n 'import requests\\n\\n' +\n 'ZYPHR_API_KEY = os.environ[\"ZYPHR_API_KEY\"]\\n' +\n 'BASE_URL = \"https://api.zyphr.dev/v1\"\\n\\n' +\n 'headers = {\\n' +\n ' \"X-API-Key\": ZYPHR_API_KEY,\\n' +\n ' \"Content-Type\": \"application/json\",\\n' +\n '}\\n\\n' +\n 'def zyphr_request(method, path, json=None, params=None):\\n' +\n ' response = requests.request(\\n' +\n ' method, f\"{BASE_URL}{path}\", headers=headers, json=json, params=params,\\n' +\n ' )\\n' +\n ' response.raise_for_status()\\n' +\n ' return response.json()\\n',\n overwrite: false,\n },\n {\n path: 'app/notify.py',\n purpose: 'Send a welcome email through Zyphr',\n contents:\n 'from .zyphr_client import zyphr_request\\n\\n' +\n 'def send_welcome_email(to: str, name: str) -> dict:\\n' +\n ' return zyphr_request(\"POST\", \"/emails\", json={\\n' +\n ' \"to\": [{\"email\": to, \"name\": name}],\\n' +\n ' \"subject\": f\"Welcome, {name}!\",\\n' +\n ' \"html\": f\"<h1>Welcome, {name}!</h1>\",\\n' +\n ' })\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n flask: {\n channel: 'email',\n language: 'python',\n framework: 'flask',\n variant: 'sdk',\n files: [\n {\n path: 'app/zyphr_client.py',\n purpose: 'REST helper for the Zyphr API',\n contents:\n 'import os\\n' +\n 'import requests\\n\\n' +\n 'BASE_URL = \"https://api.zyphr.dev/v1\"\\n\\n' +\n 'def zyphr_request(method, path, json=None, params=None):\\n' +\n ' response = requests.request(\\n' +\n ' method, f\"{BASE_URL}{path}\",\\n' +\n ' headers={\"X-API-Key\": os.environ[\"ZYPHR_API_KEY\"], \"Content-Type\": \"application/json\"},\\n' +\n ' json=json, params=params,\\n' +\n ' )\\n' +\n ' response.raise_for_status()\\n' +\n ' return response.json()\\n',\n overwrite: false,\n },\n {\n path: 'app/routes/notify.py',\n purpose: 'Flask blueprint that sends a welcome email',\n contents:\n 'from flask import Blueprint, request, jsonify\\n' +\n 'from ..zyphr_client import zyphr_request\\n\\n' +\n 'notify_bp = Blueprint(\"notify\", __name__)\\n\\n' +\n '@notify_bp.route(\"/notify\", methods=[\"POST\"])\\n' +\n 'def notify():\\n' +\n ' body = request.get_json() or {}\\n' +\n ' result = zyphr_request(\"POST\", \"/emails\", json={\\n' +\n ' \"to\": [{\"email\": body[\"to\"], \"name\": body.get(\"name\", \"\")}],\\n' +\n ' \"subject\": f\"Welcome, {body.get(\\'name\\', \\'friend\\')}!\",\\n' +\n ' \"html\": f\"<h1>Welcome!</h1>\",\\n' +\n ' })\\n' +\n ' return jsonify(result)\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n 'Register the blueprint: app.register_blueprint(notify_bp, url_prefix=\"/api\")',\n ],\n docsUrl: DOCS,\n },\n fastapi: {\n channel: 'email',\n language: 'python',\n framework: 'fastapi',\n variant: 'sdk',\n files: [\n {\n path: 'app/zyphr_client.py',\n purpose: 'REST helper for the Zyphr API',\n contents:\n 'import os\\n' +\n 'import httpx\\n\\n' +\n 'BASE_URL = \"https://api.zyphr.dev/v1\"\\n\\n' +\n 'async def zyphr_request(method: str, path: str, json: dict | None = None) -> dict:\\n' +\n ' async with httpx.AsyncClient() as client:\\n' +\n ' resp = await client.request(\\n' +\n ' method, f\"{BASE_URL}{path}\",\\n' +\n ' headers={\"X-API-Key\": os.environ[\"ZYPHR_API_KEY\"], \"Content-Type\": \"application/json\"},\\n' +\n ' json=json,\\n' +\n ' )\\n' +\n ' resp.raise_for_status()\\n' +\n ' return resp.json()\\n',\n overwrite: false,\n },\n {\n path: 'app/routers/notify.py',\n purpose: 'FastAPI router that sends a welcome email',\n contents:\n 'from fastapi import APIRouter\\n' +\n 'from pydantic import BaseModel, EmailStr\\n' +\n 'from ..zyphr_client import zyphr_request\\n\\n' +\n 'router = APIRouter()\\n\\n' +\n 'class NotifyIn(BaseModel):\\n' +\n ' to: EmailStr\\n' +\n ' name: str\\n\\n' +\n '@router.post(\"/notify\")\\n' +\n 'async def notify(body: NotifyIn):\\n' +\n ' return await zyphr_request(\"POST\", \"/emails\", json={\\n' +\n ' \"to\": [{\"email\": body.to, \"name\": body.name}],\\n' +\n ' \"subject\": f\"Welcome, {body.name}!\",\\n' +\n ' \"html\": f\"<h1>Welcome, {body.name}!</h1>\",\\n' +\n ' })\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n 'Install httpx: `pip install httpx` (or `poetry add httpx`)',\n 'Mount the router: app.include_router(router, prefix=\"/api\")',\n ],\n docsUrl: DOCS,\n },\n },\n },\n ruby: {\n sdk: {\n channel: 'email',\n language: 'ruby',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'config/initializers/zyphr.rb',\n purpose: 'Zyphr SDK configuration',\n contents:\n \"require 'zyphr'\\n\\n\" +\n 'Zyphr.configure do |config|\\n' +\n \" config.api_key['X-API-Key'] = ENV.fetch('ZYPHR_API_KEY')\\n\" +\n 'end\\n',\n overwrite: false,\n },\n {\n path: 'app/services/notify_service.rb',\n purpose: 'Service object that sends a welcome email',\n contents:\n 'class NotifyService\\n' +\n ' def self.send_welcome_email(to:, name:)\\n' +\n ' Zyphr::EmailsApi.new.send_email(\\n' +\n ' Zyphr::SendEmailRequest.new(\\n' +\n ' to: [{ email: to, name: name }],\\n' +\n \" subject: \\\"Welcome, #{name}!\\\",\\n\" +\n \" html: \\\"<h1>Welcome, #{name}!</h1>\\\"\\n\" +\n ' )\\n' +\n ' )\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n rails: {\n channel: 'email',\n language: 'ruby',\n framework: 'rails',\n variant: 'sdk',\n files: [\n {\n path: 'config/initializers/zyphr.rb',\n purpose: 'Zyphr SDK configuration',\n contents:\n \"require 'zyphr'\\n\\n\" +\n 'Zyphr.configure do |config|\\n' +\n \" config.api_key['X-API-Key'] = ENV.fetch('ZYPHR_API_KEY')\\n\" +\n 'end\\n',\n overwrite: false,\n },\n {\n path: 'app/controllers/notify_controller.rb',\n purpose: 'Rails controller that sends a welcome email',\n contents:\n 'class NotifyController < ApplicationController\\n' +\n ' def create\\n' +\n ' result = Zyphr::EmailsApi.new.send_email(\\n' +\n ' Zyphr::SendEmailRequest.new(\\n' +\n ' to: [{ email: params.require(:to), name: params[:name] }],\\n' +\n \" subject: \\\"Welcome, #{params[:name]}!\\\",\\n\" +\n \" html: \\\"<h1>Welcome, #{params[:name]}!</h1>\\\"\\n\" +\n ' )\\n' +\n ' )\\n' +\n ' render json: result\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n \"Add a route in config/routes.rb: post '/notify', to: 'notify#create'\",\n ],\n docsUrl: DOCS,\n },\n },\n },\n go: {\n sdk: {\n channel: 'email',\n language: 'go',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'internal/zyphr/client.go',\n purpose: 'Thin REST client for the Zyphr API',\n contents:\n 'package zyphr\\n\\n' +\n 'import (\\n' +\n '\\t\"bytes\"\\n' +\n '\\t\"encoding/json\"\\n' +\n '\\t\"fmt\"\\n' +\n '\\t\"io\"\\n' +\n '\\t\"net/http\"\\n' +\n '\\t\"os\"\\n' +\n ')\\n\\n' +\n 'const baseURL = \"https://api.zyphr.dev/v1\"\\n\\n' +\n 'type Client struct {\\n' +\n '\\tAPIKey string\\n' +\n '\\tHTTPClient *http.Client\\n' +\n '}\\n\\n' +\n 'func NewClient() *Client {\\n' +\n '\\treturn &Client{APIKey: os.Getenv(\"ZYPHR_API_KEY\"), HTTPClient: &http.Client{}}\\n' +\n '}\\n\\n' +\n 'func (c *Client) Do(method, path string, body any) ([]byte, error) {\\n' +\n '\\tvar buf io.Reader\\n' +\n '\\tif body != nil {\\n' +\n '\\t\\tb, err := json.Marshal(body)\\n' +\n '\\t\\tif err != nil { return nil, fmt.Errorf(\"marshal: %w\", err) }\\n' +\n '\\t\\tbuf = bytes.NewReader(b)\\n' +\n '\\t}\\n' +\n '\\treq, _ := http.NewRequest(method, baseURL+path, buf)\\n' +\n '\\treq.Header.Set(\"X-API-Key\", c.APIKey)\\n' +\n '\\treq.Header.Set(\"Content-Type\", \"application/json\")\\n' +\n '\\tresp, err := c.HTTPClient.Do(req)\\n' +\n '\\tif err != nil { return nil, err }\\n' +\n '\\tdefer resp.Body.Close()\\n' +\n '\\tdata, _ := io.ReadAll(resp.Body)\\n' +\n '\\tif resp.StatusCode >= 400 { return nil, fmt.Errorf(\"zyphr %d: %s\", resp.StatusCode, data) }\\n' +\n '\\treturn data, nil\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'internal/notify/notify.go',\n purpose: 'Send a welcome email through Zyphr',\n contents:\n 'package notify\\n\\n' +\n 'import \"yourapp/internal/zyphr\"\\n\\n' +\n 'func SendWelcomeEmail(client *zyphr.Client, to, name string) ([]byte, error) {\\n' +\n '\\treturn client.Do(\"POST\", \"/emails\", map[string]any{\\n' +\n '\\t\\t\"to\": []map[string]string{{\"email\": to, \"name\": name}},\\n' +\n '\\t\\t\"subject\": \"Welcome, \" + name + \"!\",\\n' +\n '\\t\\t\"html\": \"<h1>Welcome, \" + name + \"!</h1>\",\\n' +\n '\\t})\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n php: {\n sdk: {\n channel: 'email',\n language: 'php',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/Services/ZyphrClient.php',\n purpose: 'Guzzle-backed Zyphr client',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Services;\\n\\n' +\n 'use GuzzleHttp\\\\Client;\\n\\n' +\n 'class ZyphrClient\\n' +\n '{\\n' +\n ' private Client $http;\\n\\n' +\n ' public function __construct()\\n' +\n ' {\\n' +\n ' $this->http = new Client([\\n' +\n \" 'base_uri' => 'https://api.zyphr.dev/v1/',\\n\" +\n \" 'headers' => [\\n\" +\n \" 'X-API-Key' => getenv('ZYPHR_API_KEY'),\\n\" +\n \" 'Content-Type' => 'application/json',\\n\" +\n ' ],\\n' +\n ' ]);\\n' +\n ' }\\n\\n' +\n ' public function sendWelcomeEmail(string $to, string $name): array\\n' +\n ' {\\n' +\n \" $response = $this->http->post('emails', [\\n\" +\n \" 'json' => [\\n\" +\n \" 'to' => [['email' => $to, 'name' => $name]],\\n\" +\n \" 'subject' => \\\"Welcome, {$name}!\\\",\\n\" +\n \" 'html' => \\\"<h1>Welcome, {$name}!</h1>\\\",\\n\" +\n ' ],\\n' +\n ' ]);\\n' +\n \" return json_decode((string) $response->getBody(), true);\\n\" +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n laravel: {\n channel: 'email',\n language: 'php',\n framework: 'laravel',\n variant: 'sdk',\n files: [\n {\n path: 'app/Services/ZyphrClient.php',\n purpose: 'Laravel-friendly Zyphr client',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Services;\\n\\n' +\n 'use Illuminate\\\\Support\\\\Facades\\\\Http;\\n\\n' +\n 'class ZyphrClient\\n' +\n '{\\n' +\n ' public function sendWelcomeEmail(string $to, string $name): array\\n' +\n ' {\\n' +\n ' $response = Http::withHeaders([\\n' +\n \" 'X-API-Key' => config('services.zyphr.api_key'),\\n\" +\n \" 'Content-Type' => 'application/json',\\n\" +\n \" ])->post('https://api.zyphr.dev/v1/emails', [\\n\" +\n \" 'to' => [['email' => $to, 'name' => $name]],\\n\" +\n \" 'subject' => \\\"Welcome, {$name}!\\\",\\n\" +\n \" 'html' => \\\"<h1>Welcome, {$name}!</h1>\\\",\\n\" +\n ' ]);\\n' +\n ' $response->throw();\\n' +\n \" return $response->json();\\n\" +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'config/services.php (snippet)',\n purpose: 'Register the Zyphr API key under services config',\n contents:\n \"'zyphr' => [\\n\" +\n \" 'api_key' => env('ZYPHR_API_KEY'),\\n\" +\n '],\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n },\n csharp: {\n sdk: {\n channel: 'email',\n language: 'csharp',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'Services/ZyphrClient.cs',\n purpose: 'Singleton-style Zyphr client wrapper',\n contents:\n 'using ZyphrDev.SDK.Api;\\n' +\n 'using ZyphrDev.SDK.Client;\\n' +\n 'using ZyphrDev.SDK.Model;\\n\\n' +\n 'namespace YourApp.Services;\\n\\n' +\n 'public class ZyphrClient\\n' +\n '{\\n' +\n ' private readonly EmailsApi _emails;\\n\\n' +\n ' public ZyphrClient()\\n' +\n ' {\\n' +\n ' var config = new Configuration\\n' +\n ' {\\n' +\n ' ApiKey = new Dictionary<string, string>\\n' +\n ' {\\n' +\n ' { \"X-API-Key\", Environment.GetEnvironmentVariable(\"ZYPHR_API_KEY\")! }\\n' +\n ' }\\n' +\n ' };\\n' +\n ' _emails = new EmailsApi(config);\\n' +\n ' }\\n\\n' +\n ' public async Task<SendEmailResponse> SendWelcomeEmailAsync(string to, string name)\\n' +\n ' {\\n' +\n ' return await _emails.SendEmailAsync(new SendEmailRequest(\\n' +\n ' to: new List<EmailAddress> { new() { Email = to, Name = name } },\\n' +\n ' subject: $\"Welcome, {name}!\",\\n' +\n ' html: $\"<h1>Welcome, {name}!</h1>\"\\n' +\n ' ));\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n aspnetcore: {\n channel: 'email',\n language: 'csharp',\n framework: 'aspnetcore',\n variant: 'sdk',\n files: [\n {\n path: 'Services/ZyphrClient.cs',\n purpose: 'DI-friendly Zyphr client',\n contents:\n 'using ZyphrDev.SDK.Api;\\n' +\n 'using ZyphrDev.SDK.Client;\\n' +\n 'using ZyphrDev.SDK.Model;\\n\\n' +\n 'namespace YourApp.Services;\\n\\n' +\n 'public class ZyphrClient\\n' +\n '{\\n' +\n ' public EmailsApi Emails { get; }\\n\\n' +\n ' public ZyphrClient(IConfiguration cfg)\\n' +\n ' {\\n' +\n ' var config = new Configuration\\n' +\n ' {\\n' +\n ' ApiKey = new Dictionary<string, string>\\n' +\n ' {\\n' +\n ' { \"X-API-Key\", cfg[\"Zyphr:ApiKey\"]! }\\n' +\n ' }\\n' +\n ' };\\n' +\n ' Emails = new EmailsApi(config);\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'Controllers/NotifyController.cs',\n purpose: 'ASP.NET Core controller that sends a welcome email',\n contents:\n 'using Microsoft.AspNetCore.Mvc;\\n' +\n 'using YourApp.Services;\\n' +\n 'using ZyphrDev.SDK.Model;\\n\\n' +\n '[ApiController]\\n' +\n '[Route(\"api/[controller]\")]\\n' +\n 'public class NotifyController : ControllerBase\\n' +\n '{\\n' +\n ' private readonly ZyphrClient _zyphr;\\n' +\n ' public NotifyController(ZyphrClient zyphr) => _zyphr = zyphr;\\n\\n' +\n ' public record NotifyIn(string To, string Name);\\n\\n' +\n ' [HttpPost]\\n' +\n ' public async Task<IActionResult> Post([FromBody] NotifyIn body)\\n' +\n ' {\\n' +\n ' var result = await _zyphr.Emails.SendEmailAsync(new SendEmailRequest(\\n' +\n ' to: new List<EmailAddress> { new() { Email = body.To, Name = body.Name } },\\n' +\n ' subject: $\"Welcome, {body.Name}!\",\\n' +\n ' html: $\"<h1>Welcome, {body.Name}!</h1>\"\\n' +\n ' ));\\n' +\n ' return Ok(result);\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n 'Register the client in Program.cs: builder.Services.AddSingleton<ZyphrClient>();',\n ],\n docsUrl: DOCS,\n },\n },\n },\n};\n","import type { QuickstartChannelMap } from '../quickstart-types.js';\n\nconst DOCS = 'https://docs.zyphr.dev/channels/in-app-messaging';\nconst ENV: string[] = ['ZYPHR_API_KEY'];\nconst NEXT_STEPS = [\n 'Add ZYPHR_API_KEY to your .env file.',\n 'Use the matching subscriberId on the client (e.g. @zyphr-dev/inbox-react) to display the message.',\n];\n\nexport const inboxChannel: QuickstartChannelMap = {\n node: {\n sdk: {\n channel: 'inbox',\n language: 'node',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'src/services/inbox.ts',\n purpose: 'Send an in-app inbox message through Zyphr',\n contents:\n \"import { Zyphr } from '@zyphr-dev/node-sdk';\\n\\n\" +\n 'const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\\n\\n' +\n 'export async function notifyReportReady(subscriberId: string, reportId: string) {\\n' +\n ' return await zyphr.inbox.sendInApp({\\n' +\n ' subscriberId,\\n' +\n \" title: 'New report ready',\\n\" +\n \" body: 'Your report finished processing — click to view.',\\n\" +\n ' actionUrl: `/reports/${reportId}`,\\n' +\n \" actionLabel: 'View report',\\n\" +\n ' });\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n python: {\n sdk: {\n channel: 'inbox',\n language: 'python',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/inbox.py',\n purpose: 'Send an in-app inbox message through Zyphr',\n contents:\n 'from .zyphr_client import zyphr_request\\n\\n' +\n 'def notify_report_ready(subscriber_id: str, report_id: str) -> dict:\\n' +\n ' return zyphr_request(\"POST\", \"/inbox\", json={\\n' +\n ' \"subscriberId\": subscriber_id,\\n' +\n ' \"title\": \"New report ready\",\\n' +\n ' \"body\": \"Your report finished processing — click to view.\",\\n' +\n ' \"actionUrl\": f\"/reports/{report_id}\",\\n' +\n ' \"actionLabel\": \"View report\",\\n' +\n ' })\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n ruby: {\n sdk: {\n channel: 'inbox',\n language: 'ruby',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/services/inbox_service.rb',\n purpose: 'Send an in-app inbox message through Zyphr',\n contents:\n 'class InboxService\\n' +\n ' def self.notify_report_ready(subscriber_id:, report_id:)\\n' +\n ' Zyphr::InboxApi.new.send_in_app(\\n' +\n ' Zyphr::SendInAppRequest.new(\\n' +\n ' subscriber_id: subscriber_id,\\n' +\n \" title: 'New report ready',\\n\" +\n \" body: 'Your report finished processing — click to view.',\\n\" +\n \" action_url: \\\"/reports/#{report_id}\\\",\\n\" +\n \" action_label: 'View report'\\n\" +\n ' )\\n' +\n ' )\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n go: {\n sdk: {\n channel: 'inbox',\n language: 'go',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'internal/notify/inbox.go',\n purpose: 'Send an in-app inbox message through Zyphr',\n contents:\n 'package notify\\n\\n' +\n 'import \"yourapp/internal/zyphr\"\\n\\n' +\n 'func NotifyReportReady(client *zyphr.Client, subscriberID, reportID string) ([]byte, error) {\\n' +\n '\\treturn client.Do(\"POST\", \"/inbox\", map[string]any{\\n' +\n '\\t\\t\"subscriberId\": subscriberID,\\n' +\n '\\t\\t\"title\": \"New report ready\",\\n' +\n '\\t\\t\"body\": \"Your report finished processing — click to view.\",\\n' +\n '\\t\\t\"actionUrl\": \"/reports/\" + reportID,\\n' +\n '\\t\\t\"actionLabel\": \"View report\",\\n' +\n '\\t})\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n php: {\n sdk: {\n channel: 'inbox',\n language: 'php',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/Services/InboxService.php',\n purpose: 'Send an in-app inbox message through Zyphr',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Services;\\n\\n' +\n 'use GuzzleHttp\\\\Client;\\n\\n' +\n 'class InboxService\\n' +\n '{\\n' +\n ' public function notifyReportReady(string $subscriberId, string $reportId): array\\n' +\n ' {\\n' +\n \" $http = new Client([\\n\" +\n \" 'base_uri' => 'https://api.zyphr.dev/v1/',\\n\" +\n \" 'headers' => [\\n\" +\n \" 'X-API-Key' => getenv('ZYPHR_API_KEY'),\\n\" +\n \" 'Content-Type' => 'application/json',\\n\" +\n ' ],\\n' +\n \" ]);\\n\" +\n \" $r = $http->post('inbox', ['json' => [\\n\" +\n \" 'subscriberId' => $subscriberId,\\n\" +\n \" 'title' => 'New report ready',\\n\" +\n \" 'body' => 'Your report finished processing — click to view.',\\n\" +\n \" 'actionUrl' => \\\"/reports/{$reportId}\\\",\\n\" +\n \" 'actionLabel' => 'View report',\\n\" +\n ' ]]);\\n' +\n \" return json_decode((string) $r->getBody(), true);\\n\" +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n csharp: {\n sdk: {\n channel: 'inbox',\n language: 'csharp',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'Services/InboxService.cs',\n purpose: 'Send an in-app inbox message through Zyphr',\n contents:\n 'using ZyphrDev.SDK.Api;\\n' +\n 'using ZyphrDev.SDK.Client;\\n' +\n 'using ZyphrDev.SDK.Model;\\n\\n' +\n 'namespace YourApp.Services;\\n\\n' +\n 'public class InboxService\\n' +\n '{\\n' +\n ' private readonly InboxApi _inbox;\\n' +\n ' public InboxService()\\n' +\n ' {\\n' +\n ' var config = new Configuration\\n' +\n ' {\\n' +\n ' ApiKey = new Dictionary<string, string>\\n' +\n ' {\\n' +\n ' { \"X-API-Key\", Environment.GetEnvironmentVariable(\"ZYPHR_API_KEY\")! }\\n' +\n ' }\\n' +\n ' };\\n' +\n ' _inbox = new InboxApi(config);\\n' +\n ' }\\n\\n' +\n ' public Task<SendInAppResponse> NotifyReportReadyAsync(string subscriberId, string reportId)\\n' +\n ' {\\n' +\n ' return _inbox.SendInAppAsync(new SendInAppRequest(\\n' +\n ' subscriberId: subscriberId,\\n' +\n ' title: \"New report ready\",\\n' +\n ' body: \"Your report finished processing — click to view.\",\\n' +\n ' actionUrl: $\"/reports/{reportId}\",\\n' +\n ' actionLabel: \"View report\"\\n' +\n ' ));\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n};\n","import type { QuickstartChannelMap } from '../quickstart-types.js';\n\nconst DOCS = 'https://docs.zyphr.dev/channels/push-notifications';\nconst ENV: string[] = ['ZYPHR_API_KEY'];\nconst NEXT_STEPS = [\n 'Add ZYPHR_API_KEY to your .env file.',\n 'Register at least one device for the target subscriber (via the SDK or dashboard) before sending.',\n 'Verify your push provider credentials (APNs/FCM) are configured in the Zyphr dashboard.',\n];\n\nexport const pushChannel: QuickstartChannelMap = {\n node: {\n sdk: {\n channel: 'push',\n language: 'node',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'src/services/push.ts',\n purpose: 'Send a push notification through Zyphr',\n contents:\n \"import { Zyphr } from '@zyphr-dev/node-sdk';\\n\\n\" +\n 'const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\\n\\n' +\n 'export async function pushOrderShipped(subscriberId: string, orderId: string) {\\n' +\n ' return await zyphr.push.sendPush({\\n' +\n ' subscriberId,\\n' +\n \" title: 'Order shipped',\\n\" +\n ' body: `Order ${orderId} is on its way.`,\\n' +\n ' data: { orderId },\\n' +\n ' });\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n python: {\n sdk: {\n channel: 'push',\n language: 'python',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/push.py',\n purpose: 'Send a push notification through Zyphr',\n contents:\n 'from .zyphr_client import zyphr_request\\n\\n' +\n 'def push_order_shipped(subscriber_id: str, order_id: str) -> dict:\\n' +\n ' return zyphr_request(\"POST\", \"/push\", json={\\n' +\n ' \"subscriberId\": subscriber_id,\\n' +\n ' \"title\": \"Order shipped\",\\n' +\n ' \"body\": f\"Order {order_id} is on its way.\",\\n' +\n ' \"data\": {\"orderId\": order_id},\\n' +\n ' })\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n ruby: {\n sdk: {\n channel: 'push',\n language: 'ruby',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/services/push_service.rb',\n purpose: 'Send a push notification through Zyphr',\n contents:\n 'class PushService\\n' +\n ' def self.order_shipped(subscriber_id:, order_id:)\\n' +\n ' Zyphr::PushApi.new.send_push(\\n' +\n ' Zyphr::SendPushRequest.new(\\n' +\n ' subscriber_id: subscriber_id,\\n' +\n \" title: 'Order shipped',\\n\" +\n \" body: \\\"Order #{order_id} is on its way.\\\",\\n\" +\n ' data: { orderId: order_id }\\n' +\n ' )\\n' +\n ' )\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n go: {\n sdk: {\n channel: 'push',\n language: 'go',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'internal/notify/push.go',\n purpose: 'Send a push notification through Zyphr',\n contents:\n 'package notify\\n\\n' +\n 'import \"yourapp/internal/zyphr\"\\n\\n' +\n 'func PushOrderShipped(client *zyphr.Client, subscriberID, orderID string) ([]byte, error) {\\n' +\n '\\treturn client.Do(\"POST\", \"/push\", map[string]any{\\n' +\n '\\t\\t\"subscriberId\": subscriberID,\\n' +\n '\\t\\t\"title\": \"Order shipped\",\\n' +\n '\\t\\t\"body\": \"Order \" + orderID + \" is on its way.\",\\n' +\n '\\t\\t\"data\": map[string]string{\"orderId\": orderID},\\n' +\n '\\t})\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n php: {\n sdk: {\n channel: 'push',\n language: 'php',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/Services/PushService.php',\n purpose: 'Send a push notification through Zyphr',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Services;\\n\\n' +\n 'use GuzzleHttp\\\\Client;\\n\\n' +\n 'class PushService\\n' +\n '{\\n' +\n ' public function orderShipped(string $subscriberId, string $orderId): array\\n' +\n ' {\\n' +\n \" $http = new Client([\\n\" +\n \" 'base_uri' => 'https://api.zyphr.dev/v1/',\\n\" +\n \" 'headers' => [\\n\" +\n \" 'X-API-Key' => getenv('ZYPHR_API_KEY'),\\n\" +\n \" 'Content-Type' => 'application/json',\\n\" +\n ' ],\\n' +\n \" ]);\\n\" +\n \" $r = $http->post('push', ['json' => [\\n\" +\n \" 'subscriberId' => $subscriberId,\\n\" +\n \" 'title' => 'Order shipped',\\n\" +\n \" 'body' => \\\"Order {$orderId} is on its way.\\\",\\n\" +\n \" 'data' => ['orderId' => $orderId],\\n\" +\n ' ]]);\\n' +\n \" return json_decode((string) $r->getBody(), true);\\n\" +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n csharp: {\n sdk: {\n channel: 'push',\n language: 'csharp',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'Services/PushService.cs',\n purpose: 'Send a push notification through Zyphr',\n contents:\n 'using ZyphrDev.SDK.Api;\\n' +\n 'using ZyphrDev.SDK.Client;\\n' +\n 'using ZyphrDev.SDK.Model;\\n\\n' +\n 'namespace YourApp.Services;\\n\\n' +\n 'public class PushService\\n' +\n '{\\n' +\n ' private readonly PushApi _push;\\n' +\n ' public PushService()\\n' +\n ' {\\n' +\n ' var config = new Configuration\\n' +\n ' {\\n' +\n ' ApiKey = new Dictionary<string, string>\\n' +\n ' {\\n' +\n ' { \"X-API-Key\", Environment.GetEnvironmentVariable(\"ZYPHR_API_KEY\")! }\\n' +\n ' }\\n' +\n ' };\\n' +\n ' _push = new PushApi(config);\\n' +\n ' }\\n\\n' +\n ' public Task<SendPushResponse> OrderShippedAsync(string subscriberId, string orderId)\\n' +\n ' {\\n' +\n ' return _push.SendPushAsync(new SendPushRequest(\\n' +\n ' subscriberId: subscriberId,\\n' +\n ' title: \"Order shipped\",\\n' +\n ' body: $\"Order {orderId} is on its way.\"\\n' +\n ' ));\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n};\n","import type { QuickstartChannelMap } from '../quickstart-types.js';\n\nconst DOCS = 'https://docs.zyphr.dev/channels/sms';\nconst ENV: string[] = ['ZYPHR_API_KEY'];\nconst NEXT_STEPS = [\n 'Add ZYPHR_API_KEY to your .env file.',\n 'Recipients MUST be in E.164 format (e.g. +14155551234).',\n 'Provision your SMS sender (phone number or alphanumeric sender ID) in the Zyphr dashboard.',\n];\n\nexport const smsChannel: QuickstartChannelMap = {\n node: {\n sdk: {\n channel: 'sms',\n language: 'node',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'src/services/sms.ts',\n purpose: 'Send an SMS through Zyphr',\n contents:\n \"import { Zyphr } from '@zyphr-dev/node-sdk';\\n\\n\" +\n 'const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\\n\\n' +\n 'export async function sendOtp(to: string, code: string) {\\n' +\n ' return await zyphr.sms.sendSms({\\n' +\n ' to,\\n' +\n ' body: `Your verification code is ${code}. Expires in 5 minutes.`,\\n' +\n ' });\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n python: {\n sdk: {\n channel: 'sms',\n language: 'python',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/sms.py',\n purpose: 'Send an SMS through Zyphr',\n contents:\n 'from .zyphr_client import zyphr_request\\n\\n' +\n 'def send_otp(to: str, code: str) -> dict:\\n' +\n ' return zyphr_request(\"POST\", \"/sms\", json={\\n' +\n ' \"to\": to,\\n' +\n ' \"body\": f\"Your verification code is {code}. Expires in 5 minutes.\",\\n' +\n ' })\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n ruby: {\n sdk: {\n channel: 'sms',\n language: 'ruby',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/services/sms_service.rb',\n purpose: 'Send an SMS through Zyphr',\n contents:\n 'class SmsService\\n' +\n ' def self.send_otp(to:, code:)\\n' +\n ' Zyphr::SMSApi.new.send_sms(\\n' +\n ' Zyphr::SendSmsRequest.new(\\n' +\n ' to: to,\\n' +\n \" body: \\\"Your verification code is #{code}. Expires in 5 minutes.\\\"\\n\" +\n ' )\\n' +\n ' )\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n go: {\n sdk: {\n channel: 'sms',\n language: 'go',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'internal/notify/sms.go',\n purpose: 'Send an SMS through Zyphr',\n contents:\n 'package notify\\n\\n' +\n 'import \"yourapp/internal/zyphr\"\\n\\n' +\n 'func SendOtp(client *zyphr.Client, to, code string) ([]byte, error) {\\n' +\n '\\treturn client.Do(\"POST\", \"/sms\", map[string]any{\\n' +\n '\\t\\t\"to\": to,\\n' +\n '\\t\\t\"body\": \"Your verification code is \" + code + \". Expires in 5 minutes.\",\\n' +\n '\\t})\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n php: {\n sdk: {\n channel: 'sms',\n language: 'php',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'app/Services/SmsService.php',\n purpose: 'Send an SMS through Zyphr',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Services;\\n\\n' +\n 'use GuzzleHttp\\\\Client;\\n\\n' +\n 'class SmsService\\n' +\n '{\\n' +\n ' public function sendOtp(string $to, string $code): array\\n' +\n ' {\\n' +\n \" $http = new Client([\\n\" +\n \" 'base_uri' => 'https://api.zyphr.dev/v1/',\\n\" +\n \" 'headers' => [\\n\" +\n \" 'X-API-Key' => getenv('ZYPHR_API_KEY'),\\n\" +\n \" 'Content-Type' => 'application/json',\\n\" +\n ' ],\\n' +\n \" ]);\\n\" +\n \" $r = $http->post('sms', ['json' => [\\n\" +\n \" 'to' => $to,\\n\" +\n \" 'body' => \\\"Your verification code is {$code}. Expires in 5 minutes.\\\",\\n\" +\n ' ]]);\\n' +\n \" return json_decode((string) $r->getBody(), true);\\n\" +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n csharp: {\n sdk: {\n channel: 'sms',\n language: 'csharp',\n framework: null,\n variant: 'sdk',\n files: [\n {\n path: 'Services/SmsService.cs',\n purpose: 'Send an SMS through Zyphr',\n contents:\n 'using ZyphrDev.SDK.Api;\\n' +\n 'using ZyphrDev.SDK.Client;\\n' +\n 'using ZyphrDev.SDK.Model;\\n\\n' +\n 'namespace YourApp.Services;\\n\\n' +\n 'public class SmsService\\n' +\n '{\\n' +\n ' private readonly SMSApi _sms;\\n' +\n ' public SmsService()\\n' +\n ' {\\n' +\n ' var config = new Configuration\\n' +\n ' {\\n' +\n ' ApiKey = new Dictionary<string, string>\\n' +\n ' {\\n' +\n ' { \"X-API-Key\", Environment.GetEnvironmentVariable(\"ZYPHR_API_KEY\")! }\\n' +\n ' }\\n' +\n ' };\\n' +\n ' _sms = new SMSApi(config);\\n' +\n ' }\\n\\n' +\n ' public Task<SendSmsResponse> SendOtpAsync(string to, string code)\\n' +\n ' {\\n' +\n ' return _sms.SendSmsAsync(new SendSmsRequest(\\n' +\n ' to: to,\\n' +\n ' body: $\"Your verification code is {code}. Expires in 5 minutes.\"\\n' +\n ' ));\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n};\n","import type { QuickstartChannelMap } from '../quickstart-types.js';\n\nconst DOCS = 'https://docs.zyphr.dev/features/webhooks-security';\n\nconst ENV: string[] = ['ZYPHR_WEBHOOK_SECRET'];\n\nconst NEXT_STEPS = [\n 'Add ZYPHR_WEBHOOK_SECRET to your .env file — get it from `zyphr.webhooks.rotateWebhookSecret(id)` or the Zyphr dashboard.',\n 'Configure the webhook endpoint URL in the Zyphr dashboard or via `create_webhook`.',\n 'ALWAYS verify signatures before processing payloads — never trust an unverified webhook.',\n 'Reject deliveries whose timestamp is more than 5 minutes from now to prevent replay attacks.',\n];\n\nexport const webhookChannel: QuickstartChannelMap = {\n node: {\n sdk: {\n channel: 'webhook',\n language: 'node',\n framework: null,\n variant: 'webhook-handler',\n files: [\n {\n path: 'src/lib/verifyZyphrWebhook.ts',\n purpose:\n 'Standard Webhooks (HMAC-SHA256) signature + timestamp verification. Mirrors the canonical snippet in apps/docs/docs/features/webhooks-security.md.',\n contents:\n \"import crypto from 'crypto';\\n\\n\" +\n 'export function verifyZyphrWebhook(\\n' +\n ' payload: string,\\n' +\n \" headers: { 'webhook-id': string; 'webhook-timestamp': string; 'webhook-signature': string },\\n\" +\n ' secret: string,\\n' +\n '): boolean {\\n' +\n \" const msgId = headers['webhook-id'];\\n\" +\n \" const timestamp = parseInt(headers['webhook-timestamp'], 10);\\n\" +\n \" const signatures = headers['webhook-signature'];\\n\\n\" +\n ' const now = Math.floor(Date.now() / 1000);\\n' +\n ' if (Math.abs(now - timestamp) > 300) return false;\\n\\n' +\n ' const signedContent = `${msgId}.${timestamp}.${payload}`;\\n' +\n ' const secretBytes = Buffer.from(\\n' +\n \" secret.startsWith('whsec_') ? secret.slice(6) : secret,\\n\" +\n \" 'hex',\\n\" +\n ' );\\n' +\n ' const expected = crypto\\n' +\n \" .createHmac('sha256', secretBytes)\\n\" +\n ' .update(signedContent)\\n' +\n \" .digest('base64');\\n\\n\" +\n \" for (const sig of signatures.split(' ')) {\\n\" +\n ' const sigValue = sig.slice(3);\\n' +\n ' if (\\n' +\n ' sigValue.length === expected.length &&\\n' +\n ' crypto.timingSafeEqual(Buffer.from(sigValue), Buffer.from(expected))\\n' +\n ' ) {\\n' +\n ' return true;\\n' +\n ' }\\n' +\n ' }\\n' +\n ' return false;\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n express: {\n channel: 'webhook',\n language: 'node',\n framework: 'express',\n variant: 'webhook-handler',\n files: [\n {\n path: 'src/lib/verifyZyphrWebhook.ts',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n \"import crypto from 'crypto';\\n\\n\" +\n 'export function verifyZyphrWebhook(payload: string, headers: Record<string,string>, secret: string): boolean {\\n' +\n \" const msgId = headers['webhook-id'];\\n\" +\n \" const timestamp = parseInt(headers['webhook-timestamp'], 10);\\n\" +\n \" const signatures = headers['webhook-signature'] || '';\\n\" +\n ' const now = Math.floor(Date.now() / 1000);\\n' +\n ' if (Math.abs(now - timestamp) > 300) return false;\\n' +\n ' const signedContent = `${msgId}.${timestamp}.${payload}`;\\n' +\n \" const secretBytes = Buffer.from(secret.startsWith('whsec_') ? secret.slice(6) : secret, 'hex');\\n\" +\n \" const expected = crypto.createHmac('sha256', secretBytes).update(signedContent).digest('base64');\\n\" +\n \" return signatures.split(' ').some((sig) => {\\n\" +\n ' const v = sig.slice(3);\\n' +\n ' return v.length === expected.length && crypto.timingSafeEqual(Buffer.from(v), Buffer.from(expected));\\n' +\n ' });\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'src/routes/zyphrWebhook.ts',\n purpose:\n 'Express route that VERIFIES the signature before processing the webhook. Uses express.raw() so we can hash the exact bytes.',\n contents:\n \"import { Router, raw } from 'express';\\n\" +\n \"import { verifyZyphrWebhook } from '../lib/verifyZyphrWebhook.js';\\n\\n\" +\n 'export const zyphrWebhookRouter = Router();\\n\\n' +\n \"zyphrWebhookRouter.post('/zyphr', raw({ type: 'application/json' }), (req, res) => {\\n\" +\n ' const payload = (req.body as Buffer).toString();\\n' +\n ' const headers = req.headers as Record<string, string>;\\n' +\n ' if (!verifyZyphrWebhook(payload, headers, process.env.ZYPHR_WEBHOOK_SECRET!)) {\\n' +\n \" return res.status(401).send('invalid signature');\\n\" +\n ' }\\n' +\n ' const event = JSON.parse(payload) as { type: string; data: unknown };\\n' +\n \" console.log('zyphr event', event.type);\\n\" +\n ' res.sendStatus(204);\\n' +\n '});\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n 'Mount the router BEFORE express.json(): app.use(zyphrWebhookRouter)',\n ],\n docsUrl: DOCS,\n },\n nextjs: {\n channel: 'webhook',\n language: 'node',\n framework: 'nextjs',\n variant: 'webhook-handler',\n files: [\n {\n path: 'src/lib/verifyZyphrWebhook.ts',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n \"import crypto from 'crypto';\\n\\n\" +\n 'export function verifyZyphrWebhook(payload: string, headers: Headers, secret: string): boolean {\\n' +\n \" const msgId = headers.get('webhook-id') || '';\\n\" +\n \" const timestamp = parseInt(headers.get('webhook-timestamp') || '0', 10);\\n\" +\n \" const signatures = headers.get('webhook-signature') || '';\\n\" +\n ' const now = Math.floor(Date.now() / 1000);\\n' +\n ' if (Math.abs(now - timestamp) > 300) return false;\\n' +\n ' const signedContent = `${msgId}.${timestamp}.${payload}`;\\n' +\n \" const secretBytes = Buffer.from(secret.startsWith('whsec_') ? secret.slice(6) : secret, 'hex');\\n\" +\n \" const expected = crypto.createHmac('sha256', secretBytes).update(signedContent).digest('base64');\\n\" +\n \" return signatures.split(' ').some((sig) => {\\n\" +\n ' const v = sig.slice(3);\\n' +\n ' return v.length === expected.length && crypto.timingSafeEqual(Buffer.from(v), Buffer.from(expected));\\n' +\n ' });\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'src/app/api/zyphr/webhook/route.ts',\n purpose: 'Next.js App Router webhook handler with signature verification',\n contents:\n \"import { NextResponse } from 'next/server';\\n\" +\n \"import { verifyZyphrWebhook } from '@/lib/verifyZyphrWebhook';\\n\\n\" +\n 'export async function POST(req: Request) {\\n' +\n ' const payload = await req.text();\\n' +\n ' if (!verifyZyphrWebhook(payload, req.headers, process.env.ZYPHR_WEBHOOK_SECRET!)) {\\n' +\n \" return new NextResponse('invalid signature', { status: 401 });\\n\" +\n ' }\\n' +\n ' const event = JSON.parse(payload) as { type: string; data: unknown };\\n' +\n \" console.log('zyphr event', event.type);\\n\" +\n ' return new NextResponse(null, { status: 204 });\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n },\n python: {\n sdk: {\n channel: 'webhook',\n language: 'python',\n framework: null,\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/zyphr_webhook.py',\n purpose: 'Standard Webhooks signature verification helper (verbatim from docs)',\n contents:\n 'import hashlib\\n' +\n 'import hmac\\n' +\n 'import base64\\n' +\n 'import time\\n\\n' +\n 'def verify_zyphr_webhook(payload: str, headers: dict, secret: str) -> bool:\\n' +\n ' msg_id = headers.get(\"webhook-id\", \"\")\\n' +\n ' timestamp = headers.get(\"webhook-timestamp\", \"\")\\n' +\n ' signature = headers.get(\"webhook-signature\", \"\")\\n\\n' +\n ' now = int(time.time())\\n' +\n ' if abs(now - int(timestamp)) > 300:\\n' +\n ' return False\\n\\n' +\n ' signed_content = f\"{msg_id}.{timestamp}.{payload}\"\\n' +\n ' secret_hex = secret.removeprefix(\"whsec_\")\\n' +\n ' secret_bytes = bytes.fromhex(secret_hex)\\n' +\n ' expected = base64.b64encode(\\n' +\n ' hmac.new(secret_bytes, signed_content.encode(), hashlib.sha256).digest()\\n' +\n ' ).decode()\\n\\n' +\n ' for sig in signature.split(\" \"):\\n' +\n ' sig_value = sig.removeprefix(\"v1,\")\\n' +\n ' if hmac.compare_digest(sig_value, expected):\\n' +\n ' return True\\n' +\n ' return False\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n flask: {\n channel: 'webhook',\n language: 'python',\n framework: 'flask',\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/zyphr_webhook.py',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n 'import hashlib, hmac, base64, time\\n\\n' +\n 'def verify_zyphr_webhook(payload: str, headers, secret: str) -> bool:\\n' +\n ' msg_id = headers.get(\"webhook-id\", \"\")\\n' +\n ' timestamp = headers.get(\"webhook-timestamp\", \"\")\\n' +\n ' signature = headers.get(\"webhook-signature\", \"\")\\n' +\n ' if abs(int(time.time()) - int(timestamp)) > 300:\\n' +\n ' return False\\n' +\n ' signed = f\"{msg_id}.{timestamp}.{payload}\"\\n' +\n ' secret_bytes = bytes.fromhex(secret.removeprefix(\"whsec_\"))\\n' +\n ' expected = base64.b64encode(hmac.new(secret_bytes, signed.encode(), hashlib.sha256).digest()).decode()\\n' +\n ' return any(hmac.compare_digest(sig.removeprefix(\"v1,\"), expected) for sig in signature.split(\" \"))\\n',\n overwrite: false,\n },\n {\n path: 'app/routes/zyphr_webhook.py',\n purpose: 'Flask blueprint that verifies the webhook signature before processing',\n contents:\n 'import os, json\\n' +\n 'from flask import Blueprint, request, abort\\n' +\n 'from ..zyphr_webhook import verify_zyphr_webhook\\n\\n' +\n 'zyphr_webhook_bp = Blueprint(\"zyphr_webhook\", __name__)\\n\\n' +\n '@zyphr_webhook_bp.route(\"/webhooks/zyphr\", methods=[\"POST\"])\\n' +\n 'def handle():\\n' +\n ' payload = request.get_data(as_text=True)\\n' +\n ' if not verify_zyphr_webhook(payload, request.headers, os.environ[\"ZYPHR_WEBHOOK_SECRET\"]):\\n' +\n ' abort(401, \"invalid signature\")\\n' +\n ' event = json.loads(payload)\\n' +\n ' print(\"zyphr event\", event.get(\"type\"))\\n' +\n ' return \"\", 204\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n fastapi: {\n channel: 'webhook',\n language: 'python',\n framework: 'fastapi',\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/routers/zyphr_webhook.py',\n purpose: 'FastAPI router that verifies the webhook signature before processing',\n contents:\n 'import os, json, hashlib, hmac, base64, time\\n' +\n 'from fastapi import APIRouter, Request, HTTPException\\n\\n' +\n 'router = APIRouter()\\n\\n' +\n 'def verify_zyphr_webhook(payload: str, headers, secret: str) -> bool:\\n' +\n ' msg_id = headers.get(\"webhook-id\", \"\")\\n' +\n ' timestamp = headers.get(\"webhook-timestamp\", \"\")\\n' +\n ' signature = headers.get(\"webhook-signature\", \"\")\\n' +\n ' if abs(int(time.time()) - int(timestamp)) > 300:\\n' +\n ' return False\\n' +\n ' signed = f\"{msg_id}.{timestamp}.{payload}\"\\n' +\n ' secret_bytes = bytes.fromhex(secret.removeprefix(\"whsec_\"))\\n' +\n ' expected = base64.b64encode(hmac.new(secret_bytes, signed.encode(), hashlib.sha256).digest()).decode()\\n' +\n ' return any(hmac.compare_digest(sig.removeprefix(\"v1,\"), expected) for sig in signature.split(\" \"))\\n\\n' +\n '@router.post(\"/webhooks/zyphr\")\\n' +\n 'async def handle(request: Request):\\n' +\n ' body = (await request.body()).decode()\\n' +\n ' if not verify_zyphr_webhook(body, request.headers, os.environ[\"ZYPHR_WEBHOOK_SECRET\"]):\\n' +\n ' raise HTTPException(status_code=401, detail=\"invalid signature\")\\n' +\n ' event = json.loads(body)\\n' +\n ' print(\"zyphr event\", event.get(\"type\"))\\n' +\n ' return {\"ok\": True}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n },\n ruby: {\n sdk: {\n channel: 'webhook',\n language: 'ruby',\n framework: null,\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/services/zyphr_webhook.rb',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n \"require 'openssl'\\n\" +\n \"require 'base64'\\n\\n\" +\n 'class ZyphrWebhook\\n' +\n ' def self.verify(payload:, headers:, secret:)\\n' +\n \" msg_id = headers['webhook-id'].to_s\\n\" +\n \" timestamp = headers['webhook-timestamp'].to_i\\n\" +\n \" signatures = headers['webhook-signature'].to_s\\n\\n\" +\n ' return false if (Time.now.to_i - timestamp).abs > 300\\n\\n' +\n ' signed = \"#{msg_id}.#{timestamp}.#{payload}\"\\n' +\n \" hex = secret.start_with?('whsec_') ? secret[6..] : secret\\n\" +\n ' secret_bytes = [hex].pack(\\'H*\\')\\n' +\n \" expected = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', secret_bytes, signed))\\n\\n\" +\n \" signatures.split(' ').any? do |sig|\\n\" +\n ' v = sig[3..]\\n' +\n ' v && Rack::Utils.secure_compare(v, expected)\\n' +\n ' end\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n rails: {\n channel: 'webhook',\n language: 'ruby',\n framework: 'rails',\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/services/zyphr_webhook.rb',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n \"require 'openssl'\\n\" +\n \"require 'base64'\\n\\n\" +\n 'class ZyphrWebhook\\n' +\n ' def self.verify(payload:, headers:, secret:)\\n' +\n \" msg_id = headers['webhook-id'].to_s\\n\" +\n \" timestamp = headers['webhook-timestamp'].to_i\\n\" +\n \" signatures = headers['webhook-signature'].to_s\\n\" +\n ' return false if (Time.now.to_i - timestamp).abs > 300\\n' +\n ' signed = \"#{msg_id}.#{timestamp}.#{payload}\"\\n' +\n \" hex = secret.start_with?('whsec_') ? secret[6..] : secret\\n\" +\n ' secret_bytes = [hex].pack(\\'H*\\')\\n' +\n \" expected = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', secret_bytes, signed))\\n\" +\n \" signatures.split(' ').any? { |sig| sig[3..] && ActiveSupport::SecurityUtils.secure_compare(sig[3..], expected) }\\n\" +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n {\n path: 'app/controllers/zyphr_webhooks_controller.rb',\n purpose: 'Rails controller that verifies the webhook signature before processing',\n contents:\n 'class ZyphrWebhooksController < ApplicationController\\n' +\n ' skip_before_action :verify_authenticity_token\\n\\n' +\n ' def create\\n' +\n \" payload = request.raw_post\\n\" +\n \" secret = ENV.fetch('ZYPHR_WEBHOOK_SECRET')\\n\" +\n ' unless ZyphrWebhook.verify(payload: payload, headers: request.headers, secret: secret)\\n' +\n ' head :unauthorized and return\\n' +\n ' end\\n' +\n ' event = JSON.parse(payload)\\n' +\n \" Rails.logger.info(\\\"zyphr event #{event['type']}\\\")\\n\" +\n ' head :no_content\\n' +\n ' end\\n' +\n 'end\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n \"Add route: post '/webhooks/zyphr', to: 'zyphr_webhooks#create'\",\n ],\n docsUrl: DOCS,\n },\n },\n },\n go: {\n sdk: {\n channel: 'webhook',\n language: 'go',\n framework: null,\n variant: 'webhook-handler',\n files: [\n {\n path: 'internal/zyphr/verify.go',\n purpose:\n 'Standard Webhooks signature verification helper (verbatim from docs).',\n contents:\n 'package zyphr\\n\\n' +\n 'import (\\n' +\n '\\t\"crypto/hmac\"\\n' +\n '\\t\"crypto/sha256\"\\n' +\n '\\t\"encoding/base64\"\\n' +\n '\\t\"encoding/hex\"\\n' +\n '\\t\"math\"\\n' +\n '\\t\"strconv\"\\n' +\n '\\t\"strings\"\\n' +\n '\\t\"time\"\\n' +\n ')\\n\\n' +\n 'func VerifyWebhook(payload, msgID, timestamp, signature, secret string) bool {\\n' +\n '\\tts, err := strconv.ParseInt(timestamp, 10, 64)\\n' +\n '\\tif err != nil { return false }\\n' +\n '\\tif math.Abs(float64(time.Now().Unix()-ts)) > 300 { return false }\\n\\n' +\n '\\tsigned := msgID + \".\" + timestamp + \".\" + payload\\n' +\n '\\tsecretHex := strings.TrimPrefix(secret, \"whsec_\")\\n' +\n '\\tsecretBytes, err := hex.DecodeString(secretHex)\\n' +\n '\\tif err != nil { return false }\\n' +\n '\\tmac := hmac.New(sha256.New, secretBytes)\\n' +\n '\\tmac.Write([]byte(signed))\\n' +\n '\\texpected := base64.StdEncoding.EncodeToString(mac.Sum(nil))\\n\\n' +\n '\\tfor _, sig := range strings.Split(signature, \" \") {\\n' +\n '\\t\\tv := strings.TrimPrefix(sig, \"v1,\")\\n' +\n '\\t\\tif hmac.Equal([]byte(v), []byte(expected)) { return true }\\n' +\n '\\t}\\n' +\n '\\treturn false\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n php: {\n sdk: {\n channel: 'webhook',\n language: 'php',\n framework: null,\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/Webhooks/ZyphrWebhook.php',\n purpose: 'Standard Webhooks signature verification helper (verbatim from docs)',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Webhooks;\\n\\n' +\n 'class ZyphrWebhook\\n' +\n '{\\n' +\n ' public static function verify(string $payload, array $headers, string $secret): bool\\n' +\n ' {\\n' +\n \" $msgId = $headers['webhook-id'] ?? '';\\n\" +\n \" $timestamp = $headers['webhook-timestamp'] ?? '';\\n\" +\n \" $signature = $headers['webhook-signature'] ?? '';\\n\" +\n ' if (abs(time() - intval($timestamp)) > 300) {\\n' +\n ' return false;\\n' +\n ' }\\n' +\n ' $signed = \"{$msgId}.{$timestamp}.{$payload}\";\\n' +\n \" $hex = str_starts_with($secret, 'whsec_') ? substr($secret, 6) : $secret;\\n\" +\n \" $expected = base64_encode(hash_hmac('sha256', $signed, hex2bin($hex), true));\\n\" +\n \" foreach (explode(' ', $signature) as $sig) {\\n\" +\n ' if (hash_equals(substr($sig, 3), $expected)) {\\n' +\n ' return true;\\n' +\n ' }\\n' +\n ' }\\n' +\n ' return false;\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n laravel: {\n channel: 'webhook',\n language: 'php',\n framework: 'laravel',\n variant: 'webhook-handler',\n files: [\n {\n path: 'app/Webhooks/ZyphrWebhook.php',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Webhooks;\\n\\n' +\n 'class ZyphrWebhook\\n' +\n '{\\n' +\n ' public static function verify(string $payload, array $headers, string $secret): bool\\n' +\n ' {\\n' +\n \" $msgId = $headers['webhook-id'][0] ?? '';\\n\" +\n \" $timestamp = $headers['webhook-timestamp'][0] ?? '';\\n\" +\n \" $signature = $headers['webhook-signature'][0] ?? '';\\n\" +\n ' if (abs(time() - intval($timestamp)) > 300) {\\n' +\n ' return false;\\n' +\n ' }\\n' +\n ' $signed = \"{$msgId}.{$timestamp}.{$payload}\";\\n' +\n \" $hex = str_starts_with($secret, 'whsec_') ? substr($secret, 6) : $secret;\\n\" +\n \" $expected = base64_encode(hash_hmac('sha256', $signed, hex2bin($hex), true));\\n\" +\n \" foreach (explode(' ', $signature) as $sig) {\\n\" +\n ' if (hash_equals(substr($sig, 3), $expected)) {\\n' +\n ' return true;\\n' +\n ' }\\n' +\n ' }\\n' +\n ' return false;\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'app/Http/Controllers/ZyphrWebhookController.php',\n purpose: 'Laravel controller that verifies the webhook signature before processing',\n contents:\n '<?php\\n\\n' +\n 'namespace App\\\\Http\\\\Controllers;\\n\\n' +\n 'use App\\\\Webhooks\\\\ZyphrWebhook;\\n' +\n 'use Illuminate\\\\Http\\\\Request;\\n\\n' +\n 'class ZyphrWebhookController extends Controller\\n' +\n '{\\n' +\n ' public function handle(Request $request)\\n' +\n ' {\\n' +\n \" $payload = $request->getContent();\\n\" +\n \" $secret = config('services.zyphr.webhook_secret');\\n\" +\n \" if (! ZyphrWebhook::verify($payload, $request->headers->all(), $secret)) {\\n\" +\n \" return response('invalid signature', 401);\\n\" +\n ' }\\n' +\n \" $event = json_decode($payload, true);\\n\" +\n \" \\\\Log::info('zyphr event ' . ($event['type'] ?? 'unknown'));\\n\" +\n \" return response()->noContent();\\n\" +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: [\n ...NEXT_STEPS,\n \"Register route in routes/api.php: Route::post('/webhooks/zyphr', [ZyphrWebhookController::class, 'handle']);\",\n \"Exclude this route from CSRF (VerifyCsrfToken::$except).\",\n ],\n docsUrl: DOCS,\n },\n },\n },\n csharp: {\n sdk: {\n channel: 'webhook',\n language: 'csharp',\n framework: null,\n variant: 'webhook-handler',\n files: [\n {\n path: 'Webhooks/ZyphrWebhookVerifier.cs',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n 'using System.Security.Cryptography;\\n' +\n 'using System.Text;\\n\\n' +\n 'namespace YourApp.Webhooks;\\n\\n' +\n 'public static class ZyphrWebhookVerifier\\n' +\n '{\\n' +\n ' public static bool Verify(string payload, IDictionary<string,string> headers, string secret)\\n' +\n ' {\\n' +\n ' var msgId = headers.TryGetValue(\"webhook-id\", out var id) ? id : \"\";\\n' +\n ' var timestamp = headers.TryGetValue(\"webhook-timestamp\", out var ts) ? long.Parse(ts) : 0;\\n' +\n ' var signatures = headers.TryGetValue(\"webhook-signature\", out var sig) ? sig : \"\";\\n\\n' +\n ' var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();\\n' +\n ' if (Math.Abs(now - timestamp) > 300) return false;\\n\\n' +\n ' var signed = $\"{msgId}.{timestamp}.{payload}\";\\n' +\n ' var hex = secret.StartsWith(\"whsec_\") ? secret[6..] : secret;\\n' +\n ' var secretBytes = Convert.FromHexString(hex);\\n' +\n ' using var hmac = new HMACSHA256(secretBytes);\\n' +\n ' var expected = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(signed)));\\n\\n' +\n ' foreach (var s in signatures.Split(\\' \\'))\\n' +\n ' {\\n' +\n ' var v = s.Length > 3 ? s[3..] : \"\";\\n' +\n ' if (CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(v), Encoding.UTF8.GetBytes(expected)))\\n' +\n ' return true;\\n' +\n ' }\\n' +\n ' return false;\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n frameworks: {\n aspnetcore: {\n channel: 'webhook',\n language: 'csharp',\n framework: 'aspnetcore',\n variant: 'webhook-handler',\n files: [\n {\n path: 'Webhooks/ZyphrWebhookVerifier.cs',\n purpose: 'Standard Webhooks signature verification helper',\n contents:\n 'using System.Security.Cryptography;\\n' +\n 'using System.Text;\\n\\n' +\n 'namespace YourApp.Webhooks;\\n\\n' +\n 'public static class ZyphrWebhookVerifier\\n' +\n '{\\n' +\n ' public static bool Verify(string payload, IHeaderDictionary headers, string secret)\\n' +\n ' {\\n' +\n ' string msgId = headers[\"webhook-id\"].ToString();\\n' +\n ' long timestamp = long.TryParse(headers[\"webhook-timestamp\"], out var t) ? t : 0;\\n' +\n ' string signatures = headers[\"webhook-signature\"].ToString();\\n' +\n ' if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - timestamp) > 300) return false;\\n' +\n ' var signed = $\"{msgId}.{timestamp}.{payload}\";\\n' +\n ' var hex = secret.StartsWith(\"whsec_\") ? secret[6..] : secret;\\n' +\n ' using var hmac = new HMACSHA256(Convert.FromHexString(hex));\\n' +\n ' var expected = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(signed)));\\n' +\n ' foreach (var s in signatures.Split(\\' \\'))\\n' +\n ' {\\n' +\n ' var v = s.Length > 3 ? s[3..] : \"\";\\n' +\n ' if (CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(v), Encoding.UTF8.GetBytes(expected)))\\n' +\n ' return true;\\n' +\n ' }\\n' +\n ' return false;\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n {\n path: 'Controllers/ZyphrWebhookController.cs',\n purpose: 'ASP.NET Core controller that verifies the webhook signature before processing',\n contents:\n 'using Microsoft.AspNetCore.Mvc;\\n' +\n 'using YourApp.Webhooks;\\n\\n' +\n '[ApiController]\\n' +\n '[Route(\"webhooks/zyphr\")]\\n' +\n 'public class ZyphrWebhookController : ControllerBase\\n' +\n '{\\n' +\n ' [HttpPost]\\n' +\n ' public async Task<IActionResult> Handle()\\n' +\n ' {\\n' +\n ' using var reader = new StreamReader(Request.Body);\\n' +\n ' var payload = await reader.ReadToEndAsync();\\n' +\n ' var secret = Environment.GetEnvironmentVariable(\"ZYPHR_WEBHOOK_SECRET\")!;\\n' +\n ' if (!ZyphrWebhookVerifier.Verify(payload, Request.Headers, secret))\\n' +\n ' return Unauthorized(\"invalid signature\");\\n' +\n ' Console.WriteLine($\"zyphr event {payload[..Math.Min(80, payload.Length)]}\");\\n' +\n ' return NoContent();\\n' +\n ' }\\n' +\n '}\\n',\n overwrite: false,\n },\n ],\n envVarsNeeded: ENV,\n nextSteps: NEXT_STEPS,\n docsUrl: DOCS,\n },\n },\n },\n};\n","import type { SdkLanguage, QuickstartChannel } from '../../schemas.js';\nimport type { QuickstartChannelMap, QuickstartResult } from '../quickstart-types.js';\nimport { emailChannel } from './email.js';\nimport { inboxChannel } from './inbox.js';\nimport { pushChannel } from './push.js';\nimport { smsChannel } from './sms.js';\nimport { webhookChannel } from './webhook.js';\n\nconst REGISTRY: Record<QuickstartChannel, QuickstartChannelMap> = {\n email: emailChannel,\n push: pushChannel,\n sms: smsChannel,\n inbox: inboxChannel,\n webhook: webhookChannel,\n};\n\nexport interface ResolveQuickstartArgs {\n channel: QuickstartChannel;\n language: SdkLanguage;\n framework?: string;\n}\n\nexport interface ResolveQuickstartResult {\n result: QuickstartResult;\n frameworkRecognized: boolean;\n}\n\nexport function resolveQuickstart(\n args: ResolveQuickstartArgs,\n): ResolveQuickstartResult | null {\n const langMap = REGISTRY[args.channel]?.[args.language];\n if (!langMap) return null;\n\n if (args.framework) {\n const key = args.framework.trim().toLowerCase();\n const fw = langMap.frameworks?.[key];\n if (fw) return { result: fw, frameworkRecognized: true };\n // unknown framework → fall back to plain SDK variant, signal recognition status\n return { result: langMap.sdk, frameworkRecognized: false };\n }\n\n return { result: langMap.sdk, frameworkRecognized: true };\n}\n\nexport const QUICKSTART_REGISTRY = REGISTRY;\n","// TODO(v0.3): auto-generate these snippets from packages/sdk/<lang>/examples/\n// so docs and MCP output never drift. For v0.2 they are hand-maintained and\n// must mirror apps/docs/docs/sdks/<lang>.md exactly.\nimport type { SdkLanguage } from '../schemas.js';\n\nexport interface InstallCommand {\n manager: string;\n command: string;\n}\n\nexport interface InitSnippet {\n imports: string;\n init: string;\n fileExample: string;\n}\n\nexport type SdkKind = 'sdk' | 'rest-client';\n\nexport interface SdkInstallEntry {\n language: SdkLanguage;\n kind: SdkKind;\n packageName: string;\n registry: string;\n registryUrl: string;\n installCommands: InstallCommand[];\n initSnippet: InitSnippet;\n envVarsNeeded: string[];\n docsUrl: string;\n notes?: string;\n}\n\nconst DOCS = 'https://docs.zyphr.dev/sdks';\n\nexport const SDK_INSTALL_TABLE: Record<SdkLanguage, SdkInstallEntry> = {\n node: {\n language: 'node',\n kind: 'sdk',\n packageName: '@zyphr-dev/node-sdk',\n registry: 'npm',\n registryUrl: 'https://www.npmjs.com/package/@zyphr-dev/node-sdk',\n installCommands: [\n { manager: 'npm', command: 'npm install @zyphr-dev/node-sdk' },\n { manager: 'yarn', command: 'yarn add @zyphr-dev/node-sdk' },\n { manager: 'pnpm', command: 'pnpm add @zyphr-dev/node-sdk' },\n ],\n initSnippet: {\n imports: \"import { Zyphr } from '@zyphr-dev/node-sdk';\",\n init: \"export const zyphr = new Zyphr({ apiKey: process.env.ZYPHR_API_KEY! });\",\n fileExample: 'src/lib/zyphr.ts',\n },\n envVarsNeeded: ['ZYPHR_API_KEY'],\n docsUrl: `${DOCS}/node`,\n },\n csharp: {\n language: 'csharp',\n kind: 'sdk',\n packageName: 'ZyphrDev.SDK',\n registry: 'NuGet',\n registryUrl: 'https://www.nuget.org/packages/ZyphrDev.SDK',\n installCommands: [\n { manager: 'dotnet', command: 'dotnet add package ZyphrDev.SDK' },\n { manager: 'nuget', command: 'Install-Package ZyphrDev.SDK' },\n ],\n initSnippet: {\n imports: [\n 'using ZyphrDev.SDK.Api;',\n 'using ZyphrDev.SDK.Client;',\n 'using ZyphrDev.SDK.Model;',\n ].join('\\n'),\n init: [\n 'var config = new Configuration',\n '{',\n ' ApiKey = new Dictionary<string, string>',\n ' {',\n ' { \"X-API-Key\", Environment.GetEnvironmentVariable(\"ZYPHR_API_KEY\")! }',\n ' }',\n '};',\n 'var emails = new EmailsApi(config);',\n ].join('\\n'),\n fileExample: 'Services/ZyphrClient.cs',\n },\n envVarsNeeded: ['ZYPHR_API_KEY'],\n docsUrl: `${DOCS}/csharp`,\n },\n ruby: {\n language: 'ruby',\n kind: 'sdk',\n packageName: 'zyphr',\n registry: 'RubyGems',\n registryUrl: 'https://rubygems.org/gems/zyphr',\n installCommands: [\n { manager: 'gem', command: 'gem install zyphr' },\n { manager: 'bundler', command: \"bundle add zyphr\" },\n ],\n initSnippet: {\n imports: \"require 'zyphr'\",\n init: [\n 'Zyphr.configure do |config|',\n \" config.api_key['X-API-Key'] = ENV.fetch('ZYPHR_API_KEY')\",\n 'end',\n ].join('\\n'),\n fileExample: 'config/initializers/zyphr.rb',\n },\n envVarsNeeded: ['ZYPHR_API_KEY'],\n docsUrl: `${DOCS}/ruby`,\n },\n python: {\n language: 'python',\n kind: 'rest-client',\n packageName: 'requests',\n registry: 'PyPI',\n registryUrl: 'https://pypi.org/project/requests/',\n installCommands: [\n { manager: 'pip', command: 'pip install requests' },\n { manager: 'poetry', command: 'poetry add requests' },\n { manager: 'uv', command: 'uv add requests' },\n ],\n initSnippet: {\n imports: 'import os\\nimport requests',\n init: [\n 'ZYPHR_API_KEY = os.environ[\"ZYPHR_API_KEY\"]',\n 'BASE_URL = \"https://api.zyphr.dev/v1\"',\n '',\n 'headers = {',\n ' \"X-API-Key\": ZYPHR_API_KEY,',\n ' \"Content-Type\": \"application/json\",',\n '}',\n '',\n 'def zyphr_request(method, path, json=None, params=None):',\n ' response = requests.request(',\n ' method, f\"{BASE_URL}{path}\", headers=headers, json=json, params=params,',\n ' )',\n ' response.raise_for_status()',\n ' return response.json()',\n ].join('\\n'),\n fileExample: 'app/zyphr_client.py',\n },\n envVarsNeeded: ['ZYPHR_API_KEY'],\n docsUrl: `${DOCS}/python`,\n notes:\n 'There is no official Zyphr Python SDK yet — the canonical integration is a thin REST wrapper around the requests library.',\n },\n go: {\n language: 'go',\n kind: 'rest-client',\n packageName: 'net/http (stdlib)',\n registry: 'stdlib',\n registryUrl: 'https://pkg.go.dev/net/http',\n installCommands: [\n { manager: 'go', command: '# No install needed — net/http ships with Go.' },\n ],\n initSnippet: {\n imports: [\n 'package zyphr',\n '',\n 'import (',\n '\\t\"bytes\"',\n '\\t\"encoding/json\"',\n '\\t\"fmt\"',\n '\\t\"io\"',\n '\\t\"net/http\"',\n '\\t\"os\"',\n ')',\n ].join('\\n'),\n init: [\n 'const baseURL = \"https://api.zyphr.dev/v1\"',\n '',\n 'type Client struct {',\n '\\tAPIKey string',\n '\\tHTTPClient *http.Client',\n '}',\n '',\n 'func NewClient() *Client {',\n '\\treturn &Client{APIKey: os.Getenv(\"ZYPHR_API_KEY\"), HTTPClient: &http.Client{}}',\n '}',\n '',\n 'func (c *Client) Do(method, path string, body any) ([]byte, error) {',\n '\\tvar buf io.Reader',\n '\\tif body != nil {',\n '\\t\\tb, err := json.Marshal(body)',\n '\\t\\tif err != nil { return nil, fmt.Errorf(\"marshal: %w\", err) }',\n '\\t\\tbuf = bytes.NewReader(b)',\n '\\t}',\n '\\treq, err := http.NewRequest(method, baseURL+path, buf)',\n '\\tif err != nil { return nil, err }',\n '\\treq.Header.Set(\"X-API-Key\", c.APIKey)',\n '\\treq.Header.Set(\"Content-Type\", \"application/json\")',\n '\\tresp, err := c.HTTPClient.Do(req)',\n '\\tif err != nil { return nil, err }',\n '\\tdefer resp.Body.Close()',\n '\\tdata, _ := io.ReadAll(resp.Body)',\n '\\tif resp.StatusCode >= 400 { return nil, fmt.Errorf(\"zyphr %d: %s\", resp.StatusCode, data) }',\n '\\treturn data, nil',\n '}',\n ].join('\\n'),\n fileExample: 'internal/zyphr/client.go',\n },\n envVarsNeeded: ['ZYPHR_API_KEY'],\n docsUrl: `${DOCS}/go`,\n notes:\n 'There is no official Zyphr Go SDK yet — the canonical integration is a thin REST wrapper around net/http.',\n },\n php: {\n language: 'php',\n kind: 'rest-client',\n packageName: 'guzzlehttp/guzzle',\n registry: 'Packagist',\n registryUrl: 'https://packagist.org/packages/guzzlehttp/guzzle',\n installCommands: [\n { manager: 'composer', command: 'composer require guzzlehttp/guzzle' },\n ],\n initSnippet: {\n imports: [\n '<?php',\n '',\n 'use GuzzleHttp\\\\Client;',\n ].join('\\n'),\n init: [\n '$client = new Client([',\n \" 'base_uri' => 'https://api.zyphr.dev/v1/',\",\n \" 'headers' => [\",\n \" 'X-API-Key' => getenv('ZYPHR_API_KEY'),\",\n \" 'Content-Type' => 'application/json',\",\n ' ],',\n ']);',\n ].join('\\n'),\n fileExample: 'app/Services/ZyphrClient.php',\n },\n envVarsNeeded: ['ZYPHR_API_KEY'],\n docsUrl: `${DOCS}/php`,\n notes:\n 'There is no official Zyphr PHP SDK yet — the canonical integration uses Guzzle (or raw cURL).',\n },\n};\n\nexport function resolveInstallEntry(\n language: SdkLanguage,\n packageManager?: string,\n): SdkInstallEntry {\n const entry = SDK_INSTALL_TABLE[language];\n if (!packageManager) return entry;\n\n const normalized = packageManager.trim().toLowerCase();\n const match = entry.installCommands.find((c) => c.manager.toLowerCase() === normalized);\n if (!match) return entry;\n\n return { ...entry, installCommands: [match] };\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { isToolEnabled, type ToolGuards } from '../config.js';\nimport { resolveQuickstart } from '../integration/quickstart/index.js';\nimport { resolveInstallEntry } from '../integration/sdk-snippets.js';\nimport { toolResult } from '../result.js';\nimport { getQuickstartShape, getSdkInstallShape } from '../schemas.js';\n\nexport function registerIntegrationTools(server: McpServer, guards: ToolGuards): void {\n if (isToolEnabled({ name: 'get_sdk_install_for_language', mutates: false }, guards)) {\n server.registerTool(\n 'get_sdk_install_for_language',\n {\n title: 'Get SDK install instructions for a language',\n description:\n 'Returns install commands, init snippet, env vars, and docs URL for the chosen language. Use this when wiring Zyphr into a new project so the AI can drop the correct package + client init code.',\n inputSchema: getSdkInstallShape,\n },\n async (args) => {\n const entry = resolveInstallEntry(args.language, args.packageManager);\n return toolResult(entry);\n },\n );\n }\n\n if (isToolEnabled({ name: 'get_quickstart_for_channel', mutates: false }, guards)) {\n server.registerTool(\n 'get_quickstart_for_channel',\n {\n title: 'Get quickstart code for a channel + language',\n description:\n 'Returns drop-in service file(s) for the chosen Zyphr channel (email/push/sms/inbox/webhook), language, and optional framework (express, nextjs, flask, fastapi, rails, laravel, aspnetcore). Webhook handlers ALWAYS verify HMAC signatures. Unknown frameworks fall back to plain SDK code.',\n inputSchema: getQuickstartShape,\n },\n async (args) => {\n const resolved = resolveQuickstart({\n channel: args.channel,\n language: args.language,\n framework: args.framework,\n });\n if (!resolved) {\n return toolResult({\n error: `No quickstart available for channel=${args.channel} language=${args.language}`,\n });\n }\n const { result, frameworkRecognized } = resolved;\n return toolResult({\n ...result,\n frameworkRecognized,\n requestedFramework: args.framework ?? null,\n });\n },\n );\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { getZyphrClient } from '../client.js';\nimport { isToolEnabled, type ToolGuards } from '../config.js';\nimport { runTool } from '../result.js';\nimport {\n createWebhookShape,\n getWebhookDeliveriesShape,\n listWebhooksShape,\n} from '../schemas.js';\n\nexport function registerWebhookTools(server: McpServer, guards: ToolGuards): void {\n if (isToolEnabled({ name: 'list_webhooks', mutates: false }, guards)) {\n server.registerTool(\n 'list_webhooks',\n {\n title: 'List webhooks',\n description: 'List webhook endpoints configured for the account.',\n inputSchema: listWebhooksShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.webhooks.listWebhooks(args.limit, args.offset);\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'create_webhook', mutates: true }, guards)) {\n server.registerTool(\n 'create_webhook',\n {\n title: 'Create webhook',\n description:\n 'Register a new webhook endpoint. Subscribe to event types like \"email.*\", \"subscriber.created\", or \"*\".',\n inputSchema: createWebhookShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.webhooks.createWebhook({\n url: args.url,\n events: args.events,\n description: args.description,\n secret: args.secret,\n metadata: args.metadata,\n headers: args.headers,\n version: args.version,\n rateLimit: args.rateLimit,\n });\n });\n },\n );\n }\n\n if (isToolEnabled({ name: 'get_webhook_deliveries', mutates: false }, guards)) {\n server.registerTool(\n 'get_webhook_deliveries',\n {\n title: 'List webhook deliveries',\n description:\n 'Inspect delivery history for a webhook endpoint. Filter by status (pending/delivering/delivered/failed/exhausted), event type, or date range. Useful for debugging why a webhook is not firing.',\n inputSchema: getWebhookDeliveriesShape,\n },\n async (args) => {\n return runTool(async () => {\n const zyphr = getZyphrClient();\n return await zyphr.webhooks.listWebhookDeliveries(\n args.webhookId,\n args.status,\n args.eventType,\n args.search,\n args.startDate ? new Date(args.startDate) : undefined,\n args.endDate ? new Date(args.endDate) : undefined,\n args.limit,\n args.offset,\n );\n });\n },\n );\n }\n}\n"],"mappings":"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","names":["DOCS","ENV","NEXT_STEPS","DOCS","ENV","NEXT_STEPS","DOCS","ENV","NEXT_STEPS","DOCS","ENV","NEXT_STEPS","DOCS"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zyphr-dev/mcp-server",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Model Context Protocol server for Zyphr — wraps the Zyphr API as MCP tools for AI clients",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@modelcontextprotocol/sdk": "^1.0.0",
32
- "@zyphr-dev/node-sdk": "^0.1.26",
32
+ "@zyphr-dev/node-sdk": "^0.1.28",
33
33
  "zod": "^3.23.0"
34
34
  },
35
35
  "devDependencies": {