@cb4kas17/medusa-notification-postmark 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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 const-code
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.
22
+
package/README.md ADDED
@@ -0,0 +1,348 @@
1
+ # @const-code/medusa-notification-postmark
2
+
3
+ A Postmark notification provider for Medusa v2. Supports sending emails using custom HTML (React Email, Handlebars, plain HTML) or Postmark's hosted templates.
4
+
5
+ ## Features
6
+
7
+ - **Dual Mode** - Use custom HTML or Postmark's hosted templates
8
+ - **Auto CID Conversion** - Automatically converts `data:image` URLs to email attachments
9
+ - **Message Streams** - Supports transactional, broadcasts, and custom streams
10
+ - **Full Postmark API** - Tracking, tags, metadata, and custom headers
11
+ - **TypeScript** - Fully typed for type safety
12
+ - **Zero Templates** - Bring your own templates or use Postmark's
13
+ - **Framework Agnostic** - Works with any rendering engine
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @const-code/medusa-notification-postmark postmark
19
+ ```
20
+
21
+ Or with yarn:
22
+
23
+ ```bash
24
+ yarn add @const-code/medusa-notification-postmark postmark
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ### 1. Configure in `medusa-config.ts`
30
+
31
+ ```typescript
32
+ import { defineConfig } from "@medusajs/framework/utils"
33
+
34
+ export default defineConfig({
35
+ modules: [
36
+ {
37
+ resolve: "@const-code/medusa-notification-postmark",
38
+ options: {
39
+ serverToken: process.env.POSTMARK_SERVER_TOKEN,
40
+ from: "orders@yourstore.com",
41
+ messageStream: "outbound", // Optional
42
+ trackOpens: true, // Optional
43
+ },
44
+ },
45
+ ],
46
+ })
47
+ ```
48
+
49
+ ### 2. Add Environment Variables
50
+
51
+ ```env
52
+ POSTMARK_SERVER_TOKEN=your-server-token-here
53
+ ```
54
+
55
+ ### 3. Use in Subscribers
56
+
57
+ **Option A: Custom HTML (React Email)**
58
+
59
+ ```typescript
60
+ import { render } from "@react-email/components"
61
+ import { OrderEmail } from "../emails/order"
62
+
63
+ export default async function({ event, container }) {
64
+ const notificationService = container.resolve("notification")
65
+ const order = event.data
66
+
67
+ // Render your template
68
+ const html = await render(<OrderEmail order={order} />)
69
+
70
+ // Send via Postmark
71
+ await notificationService.createNotifications({
72
+ to: order.email,
73
+ channel: "email",
74
+ template: "order-placed",
75
+ data: {
76
+ subject: `Order #${order.display_id} Confirmed`,
77
+ html: html,
78
+ },
79
+ })
80
+ }
81
+ ```
82
+
83
+ **Option B: Postmark Templates**
84
+
85
+ ```typescript
86
+ export default async function({ event, container }) {
87
+ const notificationService = container.resolve("notification")
88
+ const user = event.data
89
+
90
+ // Use Postmark template from dashboard
91
+ await notificationService.createNotifications({
92
+ to: user.email,
93
+ channel: "email",
94
+ template: "welcome",
95
+ data: {
96
+ templateAlias: "welcome-email", // or templateId: 12345
97
+ templateModel: {
98
+ user_name: user.name,
99
+ login_url: "https://yourstore.com/login",
100
+ },
101
+ },
102
+ })
103
+ }
104
+ ```
105
+
106
+ ## Configuration Options
107
+
108
+ ### Provider Options
109
+
110
+ ```typescript
111
+ {
112
+ // Required
113
+ serverToken: string, // Postmark API token
114
+ from: string, // Default from address
115
+
116
+ // Optional
117
+ messageStream?: string, // Default: "outbound"
118
+ convertDataUrlsToCID?: boolean, // Auto-convert images (default: true)
119
+ trackOpens?: boolean, // Track email opens
120
+ trackLinks?: "None" | "HtmlAndText" | "HtmlOnly" | "TextOnly"
121
+ }
122
+ ```
123
+
124
+ ### Notification Data Options
125
+
126
+ **For Custom HTML:**
127
+
128
+ ```typescript
129
+ {
130
+ html: string, // REQUIRED: Rendered HTML
131
+ subject: string, // REQUIRED: Email subject
132
+ from?: string, // Override default from
133
+ replyTo?: string, // Reply-to address
134
+ cc?: string, // CC recipients
135
+ bcc?: string, // BCC recipients
136
+ tag?: string, // Postmark tag
137
+ metadata?: object, // Custom metadata
138
+ attachments?: Attachment[], // Manual attachments
139
+ trackOpens?: boolean, // Override tracking
140
+ trackLinks?: string, // Override tracking
141
+ messageStream?: string, // Override stream
142
+ headers?: Array<{ // Custom headers
143
+ Name: string,
144
+ Value: string
145
+ }>,
146
+ }
147
+ ```
148
+
149
+ **For Postmark Templates:**
150
+
151
+ ```typescript
152
+ {
153
+ templateId?: number, // Template ID OR
154
+ templateAlias?: string, // Template alias (pick one)
155
+ templateModel: object, // Variables for template
156
+
157
+ // Optional (same as above)
158
+ from?: string,
159
+ replyTo?: string,
160
+ cc?: string,
161
+ bcc?: string,
162
+ tag?: string,
163
+ metadata?: object,
164
+ attachments?: Attachment[],
165
+ trackOpens?: boolean,
166
+ trackLinks?: string,
167
+ messageStream?: string,
168
+ headers?: Array<{ Name: string, Value: string }>,
169
+ }
170
+ ```
171
+
172
+ ## Examples
173
+
174
+ ### Example 1: QR Codes (Auto-converted to CID)
175
+
176
+ ```typescript
177
+ import QRCode from "qrcode"
178
+
179
+ const qrCode = await QRCode.toDataURL(ticketId)
180
+ const html = await render(<TicketEmail qrCode={qrCode} />)
181
+
182
+ // The provider automatically converts data:image to CID attachments
183
+ await notificationService.createNotifications({
184
+ to: email,
185
+ channel: "email",
186
+ template: "ticket",
187
+ data: {
188
+ subject: "Your Ticket",
189
+ html: html,
190
+ },
191
+ })
192
+ ```
193
+
194
+ ### Example 2: Message Streams
195
+
196
+ ```typescript
197
+ // Transactional email
198
+ await notificationService.createNotifications({
199
+ to: email,
200
+ channel: "email",
201
+ template: "receipt",
202
+ data: {
203
+ subject: "Your Receipt",
204
+ html: html,
205
+ messageStream: "outbound", // Default
206
+ },
207
+ })
208
+
209
+ // Marketing email
210
+ await notificationService.createNotifications({
211
+ to: email,
212
+ channel: "email",
213
+ template: "newsletter",
214
+ data: {
215
+ subject: "Monthly Newsletter",
216
+ html: html,
217
+ messageStream: "broadcasts",
218
+ },
219
+ })
220
+ ```
221
+
222
+ ### Example 3: Attachments
223
+
224
+ ```typescript
225
+ await notificationService.createNotifications({
226
+ to: email,
227
+ channel: "email",
228
+ template: "invoice",
229
+ data: {
230
+ subject: "Your Invoice",
231
+ html: html,
232
+ attachments: [
233
+ {
234
+ Name: "invoice.pdf",
235
+ Content: "base64-content-here",
236
+ ContentType: "application/pdf",
237
+ },
238
+ ],
239
+ },
240
+ })
241
+ ```
242
+
243
+ ### Example 4: Metadata & Tags
244
+
245
+ ```typescript
246
+ await notificationService.createNotifications({
247
+ to: email,
248
+ channel: "email",
249
+ template: "order",
250
+ data: {
251
+ subject: "Order Confirmed",
252
+ html: html,
253
+ tag: "order-confirmation",
254
+ metadata: {
255
+ orderId: order.id,
256
+ customerId: customer.id,
257
+ },
258
+ },
259
+ })
260
+ ```
261
+
262
+ ## Message Streams
263
+
264
+ | Stream | Purpose | Example Use Cases |
265
+ |--------|---------|-------------------|
266
+ | `outbound` | Transactional emails (default) | Orders, receipts, password resets |
267
+ | `broadcasts` | Marketing emails | Newsletters, promotions |
268
+ | Custom | Any stream you create in Postmark | VIP customers, internal alerts |
269
+
270
+ ## TypeScript Support
271
+
272
+ All TypeScript types are exported:
273
+
274
+ ```typescript
275
+ import {
276
+ PostmarkOptions,
277
+ NotificationPayload,
278
+ Attachment,
279
+ convertDataUrlsToCID,
280
+ } from "@const-code/medusa-notification-postmark"
281
+ ```
282
+
283
+ ## Utility Functions
284
+
285
+ ### convertDataUrlsToCID
286
+
287
+ Manually convert data URLs to CID attachments:
288
+
289
+ ```typescript
290
+ import { convertDataUrlsToCID } from "@const-code/medusa-notification-postmark"
291
+
292
+ const html = `<img src="data:image/png;base64,..." />`
293
+ const { html: finalHtml, attachments } = convertDataUrlsToCID(html)
294
+
295
+ // Returns:
296
+ // finalHtml: <img src="cid:image-0" />
297
+ // attachments: [{ Name: "image-0.png", Content: "...", ContentID: "cid:image-0" }]
298
+ ```
299
+
300
+ ## When to Use Each Approach
301
+
302
+ ### Custom HTML
303
+
304
+ - Full control over design
305
+ - Version controlled with your code
306
+ - TypeScript type safety
307
+ - Dynamic logic in templates
308
+
309
+ Best for: Order confirmations, receipts, password resets
310
+
311
+ ### Postmark Templates
312
+
313
+ - Non-technical team can edit templates
314
+ - A/B testing in Postmark dashboard
315
+ - No deployment needed for updates
316
+ - Multi-language support
317
+
318
+ Best for: Marketing emails, newsletters, campaigns
319
+
320
+ ## FAQ
321
+
322
+ **Q: Do I need to include templates in this package?**
323
+ A: No. This is a transport-only provider. Bring your own templates or use Postmark's hosted templates.
324
+
325
+ **Q: Can I use both approaches in the same project?**
326
+ A: Yes. You can use custom HTML for transactional emails and Postmark templates for marketing emails.
327
+
328
+ **Q: Are QR codes supported?**
329
+ A: Yes. Any `data:image` URL is automatically converted to CID attachments.
330
+
331
+ **Q: Can I disable CID conversion?**
332
+ A: Yes. Set `convertDataUrlsToCID: false` in the provider options.
333
+
334
+ ## License
335
+
336
+ MIT
337
+
338
+ ## Contributing
339
+
340
+ Contributions are welcome. Please open an issue or pull request on GitHub.
341
+
342
+ ## Links
343
+
344
+ - [GitHub](https://github.com/const-code/medusa-notification-postmark)
345
+ - [npm](https://www.npmjs.com/package/@const-code/medusa-notification-postmark)
346
+ - [Postmark Documentation](https://postmarkapp.com/developer)
347
+ - [Medusa Documentation](https://docs.medusajs.com)
348
+
@@ -0,0 +1,7 @@
1
+ import PostmarkNotificationService from "./service";
2
+ declare const _default: import("@medusajs/types").ModuleProviderExports;
3
+ export default _default;
4
+ export * from "./types";
5
+ export * from "./utils";
6
+ export { PostmarkNotificationService };
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,2BAA2B,MAAM,WAAW,CAAA;;AAEnD,wBAEE;AAGF,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,OAAO,EAAE,2BAA2B,EAAE,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.PostmarkNotificationService = void 0;
21
+ const utils_1 = require("@medusajs/framework/utils");
22
+ const service_1 = __importDefault(require("./service"));
23
+ exports.PostmarkNotificationService = service_1.default;
24
+ exports.default = (0, utils_1.ModuleProvider)(utils_1.Modules.NOTIFICATION, {
25
+ services: [service_1.default],
26
+ });
27
+ // Export types for users
28
+ __exportStar(require("./types"), exports);
29
+ __exportStar(require("./utils"), exports);
@@ -0,0 +1,39 @@
1
+ import { AbstractNotificationProviderService } from "@medusajs/framework/utils";
2
+ import { Logger } from "@medusajs/framework/types";
3
+ import { ServerClient } from "postmark";
4
+ import { PostmarkOptions, NotificationPayload } from "./types";
5
+ export default class PostmarkNotificationService extends AbstractNotificationProviderService {
6
+ static identifier: string;
7
+ protected client: ServerClient;
8
+ protected options: PostmarkOptions;
9
+ protected logger: Logger;
10
+ constructor({ logger }: {
11
+ logger: Logger;
12
+ }, options: PostmarkOptions);
13
+ /**
14
+ * Send notification via Postmark
15
+ *
16
+ * Supports two modes:
17
+ * 1. Custom HTML - Pass `html` and `subject` in data
18
+ * 2. Postmark Template - Pass `templateId` or `templateAlias` in data
19
+ *
20
+ * @param notification - Notification payload from Medusa
21
+ * @returns Object with sent message ID
22
+ */
23
+ send(notification: NotificationPayload): Promise<{
24
+ id: string;
25
+ }>;
26
+ /**
27
+ * Send email with custom HTML
28
+ */
29
+ private sendWithHtml;
30
+ /**
31
+ * Send email using Postmark template
32
+ */
33
+ private sendWithTemplate;
34
+ /**
35
+ * Add optional fields to email options
36
+ */
37
+ private addOptionalFields;
38
+ }
39
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mCAAmC,EAAE,MAAM,2BAA2B,CAAA;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EACL,eAAe,EACf,mBAAmB,EAIpB,MAAM,SAAS,CAAA;AAGhB,MAAM,CAAC,OAAO,OAAO,2BAA4B,SAAQ,mCAAmC;IAC1F,MAAM,CAAC,UAAU,SAAa;IAE9B,SAAS,CAAC,MAAM,EAAE,YAAY,CAAA;IAC9B,SAAS,CAAC,OAAO,EAAE,eAAe,CAAA;IAClC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAA;gBAGtB,EAAE,MAAM,EAAE,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAC9B,OAAO,EAAE,eAAe;IAqB1B;;;;;;;;;OASG;IACG,IAAI,CAAC,YAAY,EAAE,mBAAmB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IActE;;OAEG;YACW,YAAY;IA4D1B;;OAEG;YACW,gBAAgB;IAkD9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAgC1B"}
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("@medusajs/framework/utils");
4
+ const postmark_1 = require("postmark");
5
+ const utils_2 = require("./utils");
6
+ class PostmarkNotificationService extends utils_1.AbstractNotificationProviderService {
7
+ constructor({ logger }, options) {
8
+ super();
9
+ if (!options.serverToken) {
10
+ throw new Error("Postmark server token is required");
11
+ }
12
+ if (!options.from) {
13
+ throw new Error("From email address is required");
14
+ }
15
+ this.logger = logger;
16
+ this.options = {
17
+ messageStream: "outbound",
18
+ convertDataUrlsToCID: true,
19
+ ...options,
20
+ };
21
+ this.client = new postmark_1.ServerClient(options.serverToken);
22
+ }
23
+ /**
24
+ * Send notification via Postmark
25
+ *
26
+ * Supports two modes:
27
+ * 1. Custom HTML - Pass `html` and `subject` in data
28
+ * 2. Postmark Template - Pass `templateId` or `templateAlias` in data
29
+ *
30
+ * @param notification - Notification payload from Medusa
31
+ * @returns Object with sent message ID
32
+ */
33
+ async send(notification) {
34
+ const { to, data } = notification;
35
+ // Check if using Postmark templates or custom HTML
36
+ const templateId = data?.templateId;
37
+ const templateAlias = data?.templateAlias;
38
+ if (templateId || templateAlias) {
39
+ return this.sendWithTemplate(to, data);
40
+ }
41
+ else {
42
+ return this.sendWithHtml(to, data);
43
+ }
44
+ }
45
+ /**
46
+ * Send email with custom HTML
47
+ */
48
+ async sendWithHtml(to, data) {
49
+ const html = data?.html;
50
+ const subject = data?.subject;
51
+ const from = data?.from || this.options.from;
52
+ const replyTo = data?.replyTo;
53
+ const messageStream = data?.messageStream || this.options.messageStream;
54
+ if (!html) {
55
+ throw new Error("No HTML content provided. Pass 'html' in notification data or use 'templateId'/'templateAlias' for Postmark templates");
56
+ }
57
+ if (!subject) {
58
+ throw new Error("No subject provided. Pass 'subject' in notification data");
59
+ }
60
+ // Handle attachments (convert data URLs to CID if enabled)
61
+ let finalHtml = html;
62
+ let attachments = [];
63
+ if (this.options.convertDataUrlsToCID && html.includes('data:image')) {
64
+ const converted = (0, utils_2.convertDataUrlsToCID)(html, data?.attachments || []);
65
+ finalHtml = converted.html;
66
+ attachments = converted.attachments;
67
+ }
68
+ else if (data?.attachments) {
69
+ attachments = data.attachments;
70
+ }
71
+ // Build email options
72
+ const emailOptions = {
73
+ From: from,
74
+ To: to,
75
+ Subject: subject,
76
+ HtmlBody: finalHtml,
77
+ MessageStream: messageStream,
78
+ };
79
+ // Add optional fields
80
+ this.addOptionalFields(emailOptions, data, attachments);
81
+ try {
82
+ this.logger.debug(`Sending email via Postmark (custom HTML) to ${to}`);
83
+ const response = await this.client.sendEmail(emailOptions);
84
+ this.logger.info(`Email sent successfully via Postmark: ${response.MessageID}`);
85
+ return { id: response.MessageID };
86
+ }
87
+ catch (error) {
88
+ this.logger.error(`Failed to send email via Postmark: ${error?.message || "Unknown error"}`);
89
+ throw error;
90
+ }
91
+ }
92
+ /**
93
+ * Send email using Postmark template
94
+ */
95
+ async sendWithTemplate(to, data) {
96
+ const templateId = data?.templateId;
97
+ const templateAlias = data?.templateAlias;
98
+ const templateModel = data?.templateModel || {};
99
+ const from = data?.from || this.options.from;
100
+ const replyTo = data?.replyTo;
101
+ const messageStream = data?.messageStream || this.options.messageStream;
102
+ if (!templateId && !templateAlias) {
103
+ throw new Error("Either 'templateId' (number) or 'templateAlias' (string) must be provided for Postmark templates");
104
+ }
105
+ // Build template options
106
+ const templateOptions = {
107
+ From: from,
108
+ To: to,
109
+ TemplateModel: templateModel,
110
+ MessageStream: messageStream,
111
+ };
112
+ // Use TemplateId or TemplateAlias
113
+ if (templateAlias) {
114
+ templateOptions.TemplateAlias = templateAlias;
115
+ }
116
+ else {
117
+ templateOptions.TemplateId = templateId;
118
+ }
119
+ // Add optional fields
120
+ const attachments = data?.attachments || [];
121
+ this.addOptionalFields(templateOptions, data, attachments);
122
+ try {
123
+ this.logger.debug(`Sending email via Postmark (template ${templateId || templateAlias}) to ${to}`);
124
+ const response = await this.client.sendEmailWithTemplate(templateOptions);
125
+ this.logger.info(`Email sent successfully via Postmark template: ${response.MessageID}`);
126
+ return { id: response.MessageID };
127
+ }
128
+ catch (error) {
129
+ this.logger.error(`Failed to send email via Postmark template: ${error?.message || "Unknown error"}`);
130
+ throw error;
131
+ }
132
+ }
133
+ /**
134
+ * Add optional fields to email options
135
+ */
136
+ addOptionalFields(emailOptions, data, attachments) {
137
+ const replyTo = data?.replyTo;
138
+ if (replyTo)
139
+ emailOptions.ReplyTo = replyTo;
140
+ if (data?.cc)
141
+ emailOptions.Cc = data.cc;
142
+ if (data?.bcc)
143
+ emailOptions.Bcc = data.bcc;
144
+ if (data?.tag)
145
+ emailOptions.Tag = data.tag;
146
+ if (data?.metadata)
147
+ emailOptions.Metadata = data.metadata;
148
+ if (attachments.length > 0)
149
+ emailOptions.Attachments = attachments;
150
+ // Tracking options
151
+ if (data?.trackOpens !== undefined) {
152
+ emailOptions.TrackOpens = data.trackOpens;
153
+ }
154
+ else if (this.options.trackOpens !== undefined) {
155
+ emailOptions.TrackOpens = this.options.trackOpens;
156
+ }
157
+ if (data?.trackLinks !== undefined) {
158
+ emailOptions.TrackLinks = data.trackLinks;
159
+ }
160
+ else if (this.options.trackLinks !== undefined) {
161
+ emailOptions.TrackLinks = this.options.trackLinks;
162
+ }
163
+ // Headers
164
+ if (data?.headers) {
165
+ emailOptions.Headers = data.headers;
166
+ }
167
+ }
168
+ }
169
+ PostmarkNotificationService.identifier = "postmark";
170
+ exports.default = PostmarkNotificationService;
@@ -0,0 +1,74 @@
1
+ export interface PostmarkOptions {
2
+ /** Postmark server token */
3
+ serverToken: string;
4
+ /** Default from email address */
5
+ from: string;
6
+ /** Postmark message stream (default: "outbound") */
7
+ messageStream?: string;
8
+ /** Automatically convert data URLs to CID attachments (default: true) */
9
+ convertDataUrlsToCID?: boolean;
10
+ /** Track email opens (default: undefined - uses Postmark server default) */
11
+ trackOpens?: boolean;
12
+ /** Track link clicks */
13
+ trackLinks?: "None" | "HtmlAndText" | "HtmlOnly" | "TextOnly";
14
+ }
15
+ export interface NotificationPayload {
16
+ /** Recipient email address */
17
+ to: string;
18
+ /** Notification channel (e.g., "email") */
19
+ channel: string;
20
+ /** Template identifier (optional - for reference) */
21
+ template: string;
22
+ /** Notification data */
23
+ data: Record<string, unknown> | null;
24
+ }
25
+ export interface EmailOptions {
26
+ From: string;
27
+ To: string;
28
+ Subject: string;
29
+ HtmlBody: string;
30
+ MessageStream: string;
31
+ ReplyTo?: string;
32
+ Cc?: string;
33
+ Bcc?: string;
34
+ Tag?: string;
35
+ Metadata?: Record<string, string>;
36
+ Attachments?: Attachment[];
37
+ TrackOpens?: boolean;
38
+ TrackLinks?: "None" | "HtmlAndText" | "HtmlOnly" | "TextOnly";
39
+ Headers?: Array<{
40
+ Name: string;
41
+ Value: string;
42
+ }>;
43
+ }
44
+ export interface TemplateOptions {
45
+ From: string;
46
+ To: string;
47
+ TemplateId?: number;
48
+ TemplateAlias?: string;
49
+ TemplateModel: Record<string, any>;
50
+ MessageStream: string;
51
+ ReplyTo?: string;
52
+ Cc?: string;
53
+ Bcc?: string;
54
+ Tag?: string;
55
+ Metadata?: Record<string, string>;
56
+ Attachments?: Attachment[];
57
+ TrackOpens?: boolean;
58
+ TrackLinks?: "None" | "HtmlAndText" | "HtmlOnly" | "TextOnly";
59
+ Headers?: Array<{
60
+ Name: string;
61
+ Value: string;
62
+ }>;
63
+ }
64
+ export interface Attachment {
65
+ /** File name */
66
+ Name: string;
67
+ /** Base64-encoded content */
68
+ Content: string;
69
+ /** MIME type */
70
+ ContentType: string;
71
+ /** Content ID for inline images (e.g., "cid:logo") */
72
+ ContentID?: string;
73
+ }
74
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,4BAA4B;IAC5B,WAAW,EAAE,MAAM,CAAA;IAEnB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAA;IAEZ,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB,yEAAyE;IACzE,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAE9B,4EAA4E;IAC5E,UAAU,CAAC,EAAE,OAAO,CAAA;IAEpB,wBAAwB;IACxB,UAAU,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,UAAU,GAAG,UAAU,CAAA;CAC9D;AAED,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,EAAE,EAAE,MAAM,CAAA;IAEV,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAA;IAEf,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAA;IAEhB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACjC,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,UAAU,GAAG,UAAU,CAAA;IAC7D,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACjD;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAClC,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACjC,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,UAAU,GAAG,UAAU,CAAA;IAC7D,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACjD;AAED,MAAM,WAAW,UAAU;IACzB,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAA;IAEZ,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAA;IAEf,gBAAgB;IAChB,WAAW,EAAE,MAAM,CAAA;IAEnB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,25 @@
1
+ import { Attachment } from "./types";
2
+ /**
3
+ * Convert data URLs in HTML to CID attachments
4
+ * Recursively finds all data:image URLs and converts them to CID references
5
+ *
6
+ * @param html - HTML content with potential data URLs
7
+ * @param existingAttachments - Any existing attachments to preserve
8
+ * @returns Object with modified HTML and all attachments
9
+ */
10
+ export declare function convertDataUrlsToCID(html: string, existingAttachments?: any[]): {
11
+ html: string;
12
+ attachments: Attachment[];
13
+ };
14
+ /**
15
+ * Convert any object with data URLs to CID attachments
16
+ * Useful for templates that pass images in data object
17
+ *
18
+ * @param data - Any data object that might contain data URLs
19
+ * @returns Object with modified data and attachments
20
+ */
21
+ export declare function convertObjectDataUrlsToCID(data: any): {
22
+ data: any;
23
+ attachments: Attachment[];
24
+ };
25
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEpC;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,mBAAmB,GAAE,GAAG,EAAO,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,UAAU,EAAE,CAAA;CAAE,CA0B7C;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,GAAG,GACR;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,WAAW,EAAE,UAAU,EAAE,CAAA;CAAE,CAwC1C"}
package/dist/utils.js ADDED
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertDataUrlsToCID = convertDataUrlsToCID;
4
+ exports.convertObjectDataUrlsToCID = convertObjectDataUrlsToCID;
5
+ /**
6
+ * Convert data URLs in HTML to CID attachments
7
+ * Recursively finds all data:image URLs and converts them to CID references
8
+ *
9
+ * @param html - HTML content with potential data URLs
10
+ * @param existingAttachments - Any existing attachments to preserve
11
+ * @returns Object with modified HTML and all attachments
12
+ */
13
+ function convertDataUrlsToCID(html, existingAttachments = []) {
14
+ const attachments = [...existingAttachments];
15
+ let finalHtml = html;
16
+ let imageCount = 0;
17
+ // Find all data URLs in the HTML
18
+ const dataUrlRegex = /data:image\/(png|jpeg|jpg|gif|webp);base64,([A-Za-z0-9+/=]+)/g;
19
+ let match;
20
+ while ((match = dataUrlRegex.exec(html)) !== null) {
21
+ const [fullMatch, imageType, base64Content] = match;
22
+ const cid = `image-${imageCount++}`;
23
+ // Create CID attachment
24
+ attachments.push({
25
+ Name: `${cid}.${imageType}`,
26
+ Content: base64Content,
27
+ ContentType: `image/${imageType}`,
28
+ ContentID: `cid:${cid}`,
29
+ });
30
+ // Replace data URL with CID reference
31
+ finalHtml = finalHtml.replace(fullMatch, `cid:${cid}`);
32
+ }
33
+ return { html: finalHtml, attachments };
34
+ }
35
+ /**
36
+ * Convert any object with data URLs to CID attachments
37
+ * Useful for templates that pass images in data object
38
+ *
39
+ * @param data - Any data object that might contain data URLs
40
+ * @returns Object with modified data and attachments
41
+ */
42
+ function convertObjectDataUrlsToCID(data) {
43
+ const attachments = [];
44
+ let imageCount = 0;
45
+ function processValue(value) {
46
+ if (typeof value === 'string' && value.startsWith('data:image')) {
47
+ const match = value.match(/data:image\/(png|jpeg|jpg|gif|webp);base64,([A-Za-z0-9+/=]+)/);
48
+ if (match) {
49
+ const [, imageType, base64Content] = match;
50
+ const cid = `image-${imageCount++}`;
51
+ attachments.push({
52
+ Name: `${cid}.${imageType}`,
53
+ Content: base64Content,
54
+ ContentType: `image/${imageType}`,
55
+ ContentID: `cid:${cid}`,
56
+ });
57
+ return `cid:${cid}`;
58
+ }
59
+ }
60
+ if (Array.isArray(value)) {
61
+ return value.map(processValue);
62
+ }
63
+ if (value && typeof value === 'object') {
64
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, processValue(v)]));
65
+ }
66
+ return value;
67
+ }
68
+ return {
69
+ data: processValue(data),
70
+ attachments,
71
+ };
72
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@cb4kas17/medusa-notification-postmark",
3
+ "version": "1.0.0",
4
+ "description": "Generic Postmark notification provider for Medusa v2 - supports custom HTML and Postmark templates",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc --watch",
15
+ "prepublishOnly": "npm run build",
16
+ "test": "jest"
17
+ },
18
+ "keywords": [
19
+ "medusa",
20
+ "medusa-plugin",
21
+ "medusa-v2",
22
+ "notification",
23
+ "postmark",
24
+ "email",
25
+ "notification-provider"
26
+ ],
27
+ "author": "const-code",
28
+ "license": "MIT",
29
+ "peerDependencies": {
30
+ "@medusajs/framework": "^2.0.0",
31
+ "postmark": "^4.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@medusajs/framework": "^2.13.1",
35
+ "@types/jest": "^29.0.0",
36
+ "@types/node": "^20.19.33",
37
+ "jest": "^29.0.0",
38
+ "postmark": "^4.0.5",
39
+ "typescript": "^5.9.3"
40
+ },
41
+ "directories": {
42
+ "example": "examples"
43
+ }
44
+ }