postmark-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -0
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/index.js +287 -0
- package/package.json +38 -0
package/.env.example
ADDED
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 ActiveCampaign
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
# Postmark MCP Server
|
2
|
+
|
3
|
+
An MCP server implementation for Postmark email services.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
- Exposes a Model Context Protocol (MCP) server for sending emails via Postmark
|
7
|
+
- Simple configuration via environment variables
|
8
|
+
- Comprehensive error handling and graceful shutdown
|
9
|
+
- Secure logging practices (no sensitive data exposure)
|
10
|
+
- Automatic email tracking configuration
|
11
|
+
|
12
|
+
## Feedback
|
13
|
+
We'd love to hear from you! Please share your feedback and suggestions through our [feedback form](https://forms.gle/zVdZLAJPM81Vo2Wh8).
|
14
|
+
|
15
|
+
## Requirements
|
16
|
+
- Node.js (v16 or higher recommended)
|
17
|
+
- A Postmark account and server token
|
18
|
+
|
19
|
+
## Setup
|
20
|
+
|
21
|
+
1. **Clone the repository:**
|
22
|
+
```sh
|
23
|
+
git clone https://github.com/ActiveCampaign/postmark-mcp
|
24
|
+
cd postmark-mcp
|
25
|
+
```
|
26
|
+
|
27
|
+
2. **Install dependencies:**
|
28
|
+
```sh
|
29
|
+
npm install
|
30
|
+
```
|
31
|
+
|
32
|
+
3. **Configure environment variables:**
|
33
|
+
- Copy `.env.example` to `.env`:
|
34
|
+
```sh
|
35
|
+
cp .env.example .env
|
36
|
+
```
|
37
|
+
- Edit `.env` and fill in your Postmark credentials and settings.
|
38
|
+
|
39
|
+
| Variable | Description | Required |
|
40
|
+
|------------------------|--------------------------------------------------|----------|
|
41
|
+
| POSTMARK_SERVER_TOKEN | Your Postmark server API token | Yes |
|
42
|
+
| DEFAULT_SENDER_EMAIL | Default sender email address | Yes |
|
43
|
+
| DEFAULT_MESSAGE_STREAM | Postmark message stream (e.g., 'outbound') | Yes |
|
44
|
+
|
45
|
+
4. **Run the server:**
|
46
|
+
```sh
|
47
|
+
npm start
|
48
|
+
```
|
49
|
+
|
50
|
+
## Quick Install via Cursor Deeplink
|
51
|
+
|
52
|
+
You can quickly install this MCP server in Cursor by clicking the following button:
|
53
|
+
|
54
|
+
<div>
|
55
|
+
<a href="cursor://anysphere.cursor-deeplink/mcp/install?name=Postmark&config=eyJjb21tYW5kIjoibm9kZSIsImFyZ3MiOlsiaW5kZXguanMiXSwiZW52Ijp7IlBPU1RNQVJLX1NFUlZFUl9UT0tFTiI6IiIsIkRFRkFVTFRfU0VOREVSX0VNQUlMIjoiIiwiREVGQVVMVF9NRVNTQUdFX1NUUkVBTSI6Im91dGJvdW5kIn19">
|
56
|
+
<img src="https://img.shields.io/badge/Add_Postmark_MCP_Server-to_Cursor-00A4DB?style=for-the-badge&logo=cursor&logoColor=white" alt="Add Postmark MCP Server to Cursor" />
|
57
|
+
</a>
|
58
|
+
</div>
|
59
|
+
|
60
|
+
> **Note**: After clicking the button, you'll need to:
|
61
|
+
> 1. Set your `POSTMARK_SERVER_TOKEN` in the MCP configuration
|
62
|
+
> 2. Set your `DEFAULT_SENDER_EMAIL` in the MCP configuration
|
63
|
+
> 3. Set your `DEFAULT_MESSAGE_STREAM` in the MCP configuration (defaults to "outbound")
|
64
|
+
|
65
|
+
## Claude and Cursor MCP Configuration Example
|
66
|
+
|
67
|
+
```json
|
68
|
+
{
|
69
|
+
"mcpServers": {
|
70
|
+
"postmark": {
|
71
|
+
"command": "node",
|
72
|
+
"args": ["path/to/postmark-mcp/index.js"],
|
73
|
+
"env": {
|
74
|
+
"POSTMARK_SERVER_TOKEN": "your-postmark-server-token",
|
75
|
+
"DEFAULT_SENDER_EMAIL": "your-sender-email@example.com",
|
76
|
+
"DEFAULT_MESSAGE_STREAM": "your-message-stream"
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
```
|
82
|
+
|
83
|
+
## Tool Reference
|
84
|
+
|
85
|
+
This section provides a complete reference for the Postmark MCP server tools, including example prompts and expected payloads for each.
|
86
|
+
|
87
|
+
### Table of Contents
|
88
|
+
|
89
|
+
- [Email Management Tools](#email-management-tools)
|
90
|
+
- [sendEmail](#1-sendemail)
|
91
|
+
- [sendEmailWithTemplate](#2-sendemailwithtemplate)
|
92
|
+
- [Template Management Tools](#template-management-tools)
|
93
|
+
- [listTemplates](#3-listtemplates)
|
94
|
+
- [Statistics & Tracking Tools](#statistics--tracking-tools)
|
95
|
+
- [getDeliveryStats](#4-getdeliverystats)
|
96
|
+
|
97
|
+
## Email Management Tools
|
98
|
+
|
99
|
+
### 1. sendEmail
|
100
|
+
|
101
|
+
Sends a single text email.
|
102
|
+
|
103
|
+
**Example Prompt:**
|
104
|
+
```
|
105
|
+
Send an email using Postmark to recipient@example.com with the subject "Meeting Reminder" and the message "Don't forget our team meeting tomorrow at 2 PM. Please bring your quarterly statistics report (and maybe some snacks).""
|
106
|
+
```
|
107
|
+
|
108
|
+
**Expected Payload:**
|
109
|
+
```json
|
110
|
+
{
|
111
|
+
"to": "recipient@example.com",
|
112
|
+
"subject": "Meeting Reminder",
|
113
|
+
"textBody": "Don't forget our team meeting tomorrow at 2 PM. Please bring your quarterly statistics report (and maybe some snacks).",
|
114
|
+
"htmlBody": "HTML version of the email body", // Optional
|
115
|
+
"from": "sender@example.com", // Optional, uses DEFAULT_SENDER_EMAIL if not provided
|
116
|
+
"tag": "meetings" // Optional
|
117
|
+
}
|
118
|
+
```
|
119
|
+
|
120
|
+
**Response Format:**
|
121
|
+
```
|
122
|
+
Email sent successfully!
|
123
|
+
MessageID: message-id-here
|
124
|
+
To: recipient@example.com
|
125
|
+
Subject: Meeting Reminder
|
126
|
+
```
|
127
|
+
|
128
|
+
### 2. sendEmailWithTemplate
|
129
|
+
|
130
|
+
Sends an email using a pre-defined template.
|
131
|
+
|
132
|
+
**Example Prompt:**
|
133
|
+
```
|
134
|
+
Send an email with Postmark template alias "welcome" to customer@example.com with the following template variables:
|
135
|
+
{
|
136
|
+
"name": "John Doe",
|
137
|
+
"product_name": "MyApp",
|
138
|
+
"login_url": "https://myapp.com/login"
|
139
|
+
}
|
140
|
+
```
|
141
|
+
|
142
|
+
**Expected Payload:**
|
143
|
+
```json
|
144
|
+
{
|
145
|
+
"to": "customer@example.com",
|
146
|
+
"templateId": 12345, // Either templateId or templateAlias must be provided, but not both
|
147
|
+
"templateAlias": "welcome", // Either templateId or templateAlias must be provided, but not both
|
148
|
+
"templateModel": {
|
149
|
+
"name": "John Doe",
|
150
|
+
"product_name": "MyApp",
|
151
|
+
"login_url": "https://myapp.com/login"
|
152
|
+
},
|
153
|
+
"from": "sender@example.com", // Optional, uses DEFAULT_SENDER_EMAIL if not provided
|
154
|
+
"tag": "onboarding" // Optional
|
155
|
+
}
|
156
|
+
```
|
157
|
+
|
158
|
+
**Response Format:**
|
159
|
+
```
|
160
|
+
Template email sent successfully!
|
161
|
+
MessageID: message-id-here
|
162
|
+
To: recipient@example.com
|
163
|
+
Template: template-id-or-alias-here
|
164
|
+
```
|
165
|
+
|
166
|
+
## Template Management Tools
|
167
|
+
|
168
|
+
### 3. listTemplates
|
169
|
+
|
170
|
+
Lists all available templates.
|
171
|
+
|
172
|
+
**Example Prompt:**
|
173
|
+
```
|
174
|
+
Show me a list of all the email templates available in our Postmark account.
|
175
|
+
```
|
176
|
+
|
177
|
+
**Response Format:**
|
178
|
+
```
|
179
|
+
📋 Found 2 templates:
|
180
|
+
|
181
|
+
• Basic
|
182
|
+
- ID: 12345678
|
183
|
+
- Alias: basic
|
184
|
+
- Subject: none
|
185
|
+
|
186
|
+
• Welcome
|
187
|
+
- ID: 02345679
|
188
|
+
- Alias: welcome
|
189
|
+
- Subject: none
|
190
|
+
```
|
191
|
+
|
192
|
+
## Statistics & Tracking Tools
|
193
|
+
|
194
|
+
### 4. getDeliveryStats
|
195
|
+
|
196
|
+
Retrieves email delivery statistics.
|
197
|
+
|
198
|
+
**Example Prompt:**
|
199
|
+
```
|
200
|
+
Show me our Postmark email delivery statistics from 2025-05-01 to 2025-05-15 for the "marketing" tag.
|
201
|
+
```
|
202
|
+
|
203
|
+
**Expected Payload:**
|
204
|
+
```json
|
205
|
+
{
|
206
|
+
"tag": "marketing", // Optional
|
207
|
+
"fromDate": "2025-05-01", // Optional, YYYY-MM-DD format
|
208
|
+
"toDate": "2025-05-15" // Optional, YYYY-MM-DD format
|
209
|
+
}
|
210
|
+
```
|
211
|
+
|
212
|
+
**Response Format:**
|
213
|
+
```
|
214
|
+
Email Statistics Summary
|
215
|
+
|
216
|
+
Sent: 100 emails
|
217
|
+
Open Rate: 45.5% (45/99 tracked emails)
|
218
|
+
Click Rate: 15.2% (15/99 tracked links)
|
219
|
+
|
220
|
+
Period: 2025-05-01 to 2025-05-15
|
221
|
+
Tag: marketing
|
222
|
+
```
|
223
|
+
|
224
|
+
## Implementation Details
|
225
|
+
|
226
|
+
### Automatic Configuration
|
227
|
+
All emails are automatically configured with:
|
228
|
+
- `TrackOpens: true`
|
229
|
+
- `TrackLinks: "HtmlAndText"`
|
230
|
+
- Message stream from `DEFAULT_MESSAGE_STREAM` environment variable
|
231
|
+
|
232
|
+
### Error Handling
|
233
|
+
The server implements comprehensive error handling:
|
234
|
+
- Validation of all required environment variables
|
235
|
+
- Graceful shutdown on SIGTERM and SIGINT
|
236
|
+
- Proper error handling for API calls
|
237
|
+
- No exposure of sensitive information in logs
|
238
|
+
- Consistent error message formatting
|
239
|
+
|
240
|
+
### Logging
|
241
|
+
- Uses appropriate log levels (`info` for normal operations, `error` for errors)
|
242
|
+
- Excludes sensitive information from logs
|
243
|
+
- Provides clear operation status and results
|
244
|
+
|
245
|
+
---
|
246
|
+
|
247
|
+
*For more information about the Postmark API, visit [Postmark's Developer Documentation](https://postmarkapp.com/developer).*
|
package/index.js
ADDED
@@ -0,0 +1,287 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
/**
|
4
|
+
* @file Postmark MCP Server - Official SDK Implementation
|
5
|
+
* @description Universal MCP server for Postmark using the official TypeScript SDK
|
6
|
+
* @author Jabal Torres
|
7
|
+
* @version 1.0.0
|
8
|
+
* @license MIT
|
9
|
+
*/
|
10
|
+
|
11
|
+
import { config } from 'dotenv';
|
12
|
+
import { dirname } from 'path';
|
13
|
+
import { fileURLToPath } from 'url';
|
14
|
+
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
16
|
+
|
17
|
+
// Load environment variables from .env file
|
18
|
+
config();
|
19
|
+
|
20
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
21
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
22
|
+
import { z } from "zod";
|
23
|
+
import postmark from "postmark";
|
24
|
+
|
25
|
+
// Postmark configuration
|
26
|
+
const serverToken = process.env.POSTMARK_SERVER_TOKEN;
|
27
|
+
const defaultSender = process.env.DEFAULT_SENDER_EMAIL;
|
28
|
+
const defaultMessageStream = process.env.DEFAULT_MESSAGE_STREAM;
|
29
|
+
|
30
|
+
// Initialize Postmark client and MCP server
|
31
|
+
async function initializeServices() {
|
32
|
+
try {
|
33
|
+
// Validate required environment variables
|
34
|
+
if (!serverToken) {
|
35
|
+
console.error('Error: POSTMARK_SERVER_TOKEN is not set');
|
36
|
+
process.exit(1);
|
37
|
+
}
|
38
|
+
if (!defaultSender) {
|
39
|
+
console.error('Error: DEFAULT_SENDER_EMAIL is not set');
|
40
|
+
process.exit(1);
|
41
|
+
}
|
42
|
+
if (!defaultMessageStream) {
|
43
|
+
console.error('Error: DEFAULT_MESSAGE_STREAM is not set');
|
44
|
+
process.exit(1);
|
45
|
+
}
|
46
|
+
|
47
|
+
console.error('Initializing Postmark MCP server (Official SDK)...');
|
48
|
+
console.error('Default sender:', defaultSender);
|
49
|
+
console.error('Message stream:', defaultMessageStream);
|
50
|
+
|
51
|
+
// Initialize Postmark client
|
52
|
+
const client = new postmark.ServerClient(serverToken);
|
53
|
+
|
54
|
+
// Verify Postmark client by making a test API call
|
55
|
+
await client.getServer();
|
56
|
+
|
57
|
+
// Create MCP server
|
58
|
+
const mcpServer = new McpServer({
|
59
|
+
name: "postmark-mcp",
|
60
|
+
version: "1.0.0"
|
61
|
+
});
|
62
|
+
|
63
|
+
return { postmarkClient: client, mcpServer };
|
64
|
+
} catch (error) {
|
65
|
+
if (error.code || error.message) {
|
66
|
+
throw new Error(`Initialization failed: ${error.code ? `${error.code} - ` : ''}${error.message}`);
|
67
|
+
}
|
68
|
+
throw new Error('Initialization failed: An unexpected error occurred');
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
// Start the server
|
73
|
+
async function main() {
|
74
|
+
try {
|
75
|
+
const { postmarkClient, mcpServer: server } = await initializeServices();
|
76
|
+
|
77
|
+
// Register tools with validated client
|
78
|
+
registerTools(server, postmarkClient);
|
79
|
+
|
80
|
+
console.error('Connecting to MCP transport...');
|
81
|
+
const transport = new StdioServerTransport();
|
82
|
+
await server.connect(transport);
|
83
|
+
|
84
|
+
console.error('Postmark MCP server is running and ready!');
|
85
|
+
|
86
|
+
// Setup graceful shutdown
|
87
|
+
process.on('SIGTERM', () => handleShutdown(server));
|
88
|
+
process.on('SIGINT', () => handleShutdown(server));
|
89
|
+
} catch (error) {
|
90
|
+
console.error('Server initialization failed:', error.message);
|
91
|
+
process.exit(1);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
// Graceful shutdown handler
|
96
|
+
async function handleShutdown(server) {
|
97
|
+
console.error('Shutting down server...');
|
98
|
+
try {
|
99
|
+
await server.disconnect();
|
100
|
+
console.error('Server shutdown complete');
|
101
|
+
process.exit(0);
|
102
|
+
} catch (error) {
|
103
|
+
console.error('Error during shutdown:', error.message);
|
104
|
+
process.exit(1);
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
// Global error handlers
|
109
|
+
process.on('uncaughtException', (error) => {
|
110
|
+
console.error('Uncaught exception:', error.message);
|
111
|
+
process.exit(1);
|
112
|
+
});
|
113
|
+
|
114
|
+
process.on('unhandledRejection', (reason) => {
|
115
|
+
console.error('Unhandled rejection:', reason instanceof Error ? reason.message : reason);
|
116
|
+
process.exit(1);
|
117
|
+
});
|
118
|
+
|
119
|
+
// Move tool registration to a separate function for better organization
|
120
|
+
function registerTools(server, postmarkClient) {
|
121
|
+
// Define and register the sendEmail tool
|
122
|
+
server.tool(
|
123
|
+
"sendEmail",
|
124
|
+
{
|
125
|
+
to: z.string().email().describe("Recipient email address"),
|
126
|
+
subject: z.string().describe("Email subject"),
|
127
|
+
textBody: z.string().describe("Plain text body of the email"),
|
128
|
+
htmlBody: z.string().optional().describe("HTML body of the email (optional)"),
|
129
|
+
from: z.string().email().optional().describe("Sender email address (optional, uses default if not provided)"),
|
130
|
+
tag: z.string().optional().describe("Optional tag for categorization")
|
131
|
+
},
|
132
|
+
async ({ to, subject, textBody, htmlBody, from, tag }) => {
|
133
|
+
const emailData = {
|
134
|
+
From: from || defaultSender,
|
135
|
+
To: to,
|
136
|
+
Subject: subject,
|
137
|
+
TextBody: textBody,
|
138
|
+
MessageStream: defaultMessageStream,
|
139
|
+
TrackOpens: true,
|
140
|
+
TrackLinks: "HtmlAndText"
|
141
|
+
};
|
142
|
+
|
143
|
+
if (htmlBody) emailData.HtmlBody = htmlBody;
|
144
|
+
if (tag) emailData.Tag = tag;
|
145
|
+
|
146
|
+
console.error('Sending email...', { to, subject });
|
147
|
+
const result = await postmarkClient.sendEmail(emailData);
|
148
|
+
console.error('Email sent successfully:', result.MessageID);
|
149
|
+
|
150
|
+
return {
|
151
|
+
content: [{
|
152
|
+
type: "text",
|
153
|
+
text: `Email sent successfully!\nMessageID: ${result.MessageID}\nTo: ${to}\nSubject: ${subject}`
|
154
|
+
}]
|
155
|
+
};
|
156
|
+
}
|
157
|
+
);
|
158
|
+
|
159
|
+
// Define and register the sendEmailWithTemplate tool
|
160
|
+
server.tool(
|
161
|
+
"sendEmailWithTemplate",
|
162
|
+
{
|
163
|
+
to: z.string().email().describe("Recipient email address"),
|
164
|
+
templateId: z.number().optional().describe("Template ID (use either this or templateAlias)"),
|
165
|
+
templateAlias: z.string().optional().describe("Template alias (use either this or templateId)"),
|
166
|
+
templateModel: z.object({}).passthrough().describe("Data model for template variables"),
|
167
|
+
from: z.string().email().optional().describe("Sender email address (optional)"),
|
168
|
+
tag: z.string().optional().describe("Optional tag for categorization")
|
169
|
+
},
|
170
|
+
async ({ to, templateId, templateAlias, templateModel, from, tag }) => {
|
171
|
+
if (!templateId && !templateAlias) {
|
172
|
+
throw new Error("Either templateId or templateAlias must be provided");
|
173
|
+
}
|
174
|
+
|
175
|
+
const emailData = {
|
176
|
+
From: from || defaultSender,
|
177
|
+
To: to,
|
178
|
+
TemplateModel: templateModel,
|
179
|
+
MessageStream: defaultMessageStream,
|
180
|
+
TrackOpens: true,
|
181
|
+
TrackLinks: "HtmlAndText"
|
182
|
+
};
|
183
|
+
|
184
|
+
if (templateId) {
|
185
|
+
emailData.TemplateId = templateId;
|
186
|
+
} else {
|
187
|
+
emailData.TemplateAlias = templateAlias;
|
188
|
+
}
|
189
|
+
|
190
|
+
if (tag) emailData.Tag = tag;
|
191
|
+
|
192
|
+
console.error('Sending template email...', { to, templateId: templateId || templateAlias });
|
193
|
+
const result = await postmarkClient.sendEmailWithTemplate(emailData);
|
194
|
+
console.error('Template email sent successfully:', result.MessageID);
|
195
|
+
|
196
|
+
return {
|
197
|
+
content: [{
|
198
|
+
type: "text",
|
199
|
+
text: `Template email sent successfully!\nMessageID: ${result.MessageID}\nTo: ${to}\nTemplate: ${templateId || templateAlias}`
|
200
|
+
}]
|
201
|
+
};
|
202
|
+
}
|
203
|
+
);
|
204
|
+
|
205
|
+
// Define and register the listTemplates tool
|
206
|
+
server.tool(
|
207
|
+
"listTemplates",
|
208
|
+
{},
|
209
|
+
async () => {
|
210
|
+
console.error('Fetching templates...');
|
211
|
+
const result = await postmarkClient.getTemplates();
|
212
|
+
console.error(`Found ${result.Templates.length} templates`);
|
213
|
+
|
214
|
+
const templateList = result.Templates.map(t =>
|
215
|
+
`• **${t.Name}**\n - ID: ${t.TemplateId}\n - Alias: ${t.Alias || 'none'}\n - Subject: ${t.Subject || 'none'}`
|
216
|
+
).join('\n\n');
|
217
|
+
|
218
|
+
return {
|
219
|
+
content: [{
|
220
|
+
type: "text",
|
221
|
+
text: `Found ${result.Templates.length} templates:\n\n${templateList}`
|
222
|
+
}]
|
223
|
+
};
|
224
|
+
}
|
225
|
+
);
|
226
|
+
|
227
|
+
// Define and register the getDeliveryStats tool
|
228
|
+
server.tool(
|
229
|
+
"getDeliveryStats",
|
230
|
+
{
|
231
|
+
tag: z.string().optional().describe("Filter by tag (optional)"),
|
232
|
+
fromDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("Start date in YYYY-MM-DD format (optional)"),
|
233
|
+
toDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("End date in YYYY-MM-DD format (optional)")
|
234
|
+
},
|
235
|
+
async ({ tag, fromDate, toDate }) => {
|
236
|
+
const query = [];
|
237
|
+
if (fromDate) query.push(`fromdate=${encodeURIComponent(fromDate)}`);
|
238
|
+
if (toDate) query.push(`todate=${encodeURIComponent(toDate)}`);
|
239
|
+
if (tag) query.push(`tag=${encodeURIComponent(tag)}`);
|
240
|
+
|
241
|
+
const url = `https://api.postmarkapp.com/stats/outbound${query.length ? '?' + query.join('&') : ''}`;
|
242
|
+
|
243
|
+
console.error('Fetching delivery stats...');
|
244
|
+
|
245
|
+
const response = await fetch(url, {
|
246
|
+
headers: {
|
247
|
+
"Accept": "application/json",
|
248
|
+
"X-Postmark-Server-Token": serverToken
|
249
|
+
}
|
250
|
+
});
|
251
|
+
|
252
|
+
if (!response.ok) {
|
253
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
254
|
+
}
|
255
|
+
|
256
|
+
const data = await response.json();
|
257
|
+
console.error('Stats retrieved successfully');
|
258
|
+
|
259
|
+
const sent = data.Sent || 0;
|
260
|
+
const tracked = data.Tracked || 0;
|
261
|
+
const uniqueOpens = data.UniqueOpens || 0;
|
262
|
+
const totalTrackedLinks = data.TotalTrackedLinksSent || 0;
|
263
|
+
const uniqueLinksClicked = data.UniqueLinksClicked || 0;
|
264
|
+
|
265
|
+
const openRate = tracked > 0 ? ((uniqueOpens / tracked) * 100).toFixed(1) : '0.0';
|
266
|
+
const clickRate = totalTrackedLinks > 0 ? ((uniqueLinksClicked / totalTrackedLinks) * 100).toFixed(1) : '0.0';
|
267
|
+
|
268
|
+
return {
|
269
|
+
content: [{
|
270
|
+
type: "text",
|
271
|
+
text: `Email Statistics Summary\n\n` +
|
272
|
+
`Sent: ${sent} emails\n` +
|
273
|
+
`Open Rate: ${openRate}% (${uniqueOpens}/${tracked} tracked emails)\n` +
|
274
|
+
`Click Rate: ${clickRate}% (${uniqueLinksClicked}/${totalTrackedLinks} tracked links)\n\n` +
|
275
|
+
`${fromDate || toDate ? `Period: ${fromDate || 'start'} to ${toDate || 'now'}\n` : ''}` +
|
276
|
+
`${tag ? `Tag: ${tag}\n` : ''}`
|
277
|
+
}]
|
278
|
+
};
|
279
|
+
}
|
280
|
+
);
|
281
|
+
}
|
282
|
+
|
283
|
+
// Start the server
|
284
|
+
main().catch((error) => {
|
285
|
+
console.error('💥 Failed to start server:', error.message);
|
286
|
+
process.exit(1);
|
287
|
+
});
|
package/package.json
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
{
|
2
|
+
"name": "postmark-mcp",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "Universal Postmark MCP server using official SDK",
|
5
|
+
"main": "index.js",
|
6
|
+
"type": "module",
|
7
|
+
"bin": {
|
8
|
+
"postmark-mcp": "./index.js"
|
9
|
+
},
|
10
|
+
"files": [
|
11
|
+
"index.js",
|
12
|
+
"README.md",
|
13
|
+
"LICENSE",
|
14
|
+
".env.example"
|
15
|
+
],
|
16
|
+
"scripts": {
|
17
|
+
"start": "node index.js",
|
18
|
+
"inspector": "npx @modelcontextprotocol/inspector index.js"
|
19
|
+
},
|
20
|
+
"repository": {
|
21
|
+
"type": "git",
|
22
|
+
"url": "git+https://github.com/ActiveCampaign/postmark-mcp.git"
|
23
|
+
},
|
24
|
+
"homepage": "https://github.com/ActiveCampaign/postmark-mcp#readme",
|
25
|
+
"bugs": {
|
26
|
+
"url": "https://github.com/ActiveCampaign/postmark-mcp/issues"
|
27
|
+
},
|
28
|
+
"keywords": ["postmark", "email", "mcp", "ai", "model-context-protocol"],
|
29
|
+
"author": "Jabal Torres",
|
30
|
+
"license": "MIT",
|
31
|
+
"dependencies": {
|
32
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
33
|
+
"dotenv": "^16.4.5",
|
34
|
+
"node-fetch": "^3.3.2",
|
35
|
+
"postmark": "^4.0.5",
|
36
|
+
"zod": "^3.23.8"
|
37
|
+
}
|
38
|
+
}
|