@wopr-network/platform-core 1.61.2 → 1.63.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/dist/email/client.d.ts +18 -6
- package/dist/email/client.js +30 -10
- package/dist/email/client.test.js +2 -2
- package/dist/email/index.d.ts +1 -1
- package/dist/email/postmark-transport.d.ts +19 -0
- package/dist/email/postmark-transport.js +45 -0
- package/package.json +2 -1
- package/src/email/client.test.ts +2 -2
- package/src/email/client.ts +37 -10
- package/src/email/index.ts +1 -1
- package/src/email/postmark-transport.ts +59 -0
package/dist/email/client.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Email Client — Template-based transactional email sender.
|
|
3
3
|
*
|
|
4
|
-
* Supports
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* SES takes priority when both are configured.
|
|
4
|
+
* Supports three backends (first match wins):
|
|
5
|
+
* 1. **AWS SES**: Set AWS_SES_REGION env var (+ AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
|
|
6
|
+
* 2. **Postmark**: Set POSTMARK_API_KEY env var
|
|
7
|
+
* 3. **Resend**: Set RESEND_API_KEY env var
|
|
9
8
|
*/
|
|
10
9
|
export interface EmailClientConfig {
|
|
11
10
|
apiKey: string;
|
|
@@ -49,7 +48,20 @@ export declare class EmailClient {
|
|
|
49
48
|
/** Send a transactional email. */
|
|
50
49
|
send(opts: SendTemplateEmailOpts): Promise<EmailSendResult>;
|
|
51
50
|
}
|
|
52
|
-
export
|
|
51
|
+
export interface EmailClientOverrides {
|
|
52
|
+
/** Sender address — overrides EMAIL_FROM env var. */
|
|
53
|
+
from?: string;
|
|
54
|
+
/** Reply-to address — overrides EMAIL_REPLY_TO env var. */
|
|
55
|
+
replyTo?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create a lazily-initialized singleton EmailClient.
|
|
59
|
+
*
|
|
60
|
+
* Optional overrides (from DB-driven product config) take precedence
|
|
61
|
+
* over env vars. Pass them on first call; subsequent calls return the
|
|
62
|
+
* cached singleton.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getEmailClient(overrides?: EmailClientOverrides): EmailClient;
|
|
53
65
|
/** Reset the singleton (for testing). */
|
|
54
66
|
export declare function resetEmailClient(): void;
|
|
55
67
|
/** Replace the singleton (for testing). */
|
package/dist/email/client.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Email Client — Template-based transactional email sender.
|
|
3
3
|
*
|
|
4
|
-
* Supports
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* SES takes priority when both are configured.
|
|
4
|
+
* Supports three backends (first match wins):
|
|
5
|
+
* 1. **AWS SES**: Set AWS_SES_REGION env var (+ AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
|
|
6
|
+
* 2. **Postmark**: Set POSTMARK_API_KEY env var
|
|
7
|
+
* 3. **Resend**: Set RESEND_API_KEY env var
|
|
9
8
|
*/
|
|
10
9
|
import { Resend } from "resend";
|
|
11
10
|
import { logger } from "../config/logger.js";
|
|
11
|
+
import { PostmarkTransport } from "./postmark-transport.js";
|
|
12
12
|
import { SesTransport } from "./ses-transport.js";
|
|
13
13
|
/**
|
|
14
14
|
* Transactional email client with pluggable transport.
|
|
@@ -94,7 +94,8 @@ class ResendTransport {
|
|
|
94
94
|
*
|
|
95
95
|
* Backend selection (first match wins):
|
|
96
96
|
* 1. AWS SES — AWS_SES_REGION is set
|
|
97
|
-
* 2.
|
|
97
|
+
* 2. Postmark — POSTMARK_API_KEY is set
|
|
98
|
+
* 3. Resend — RESEND_API_KEY is set
|
|
98
99
|
*
|
|
99
100
|
* Common env vars:
|
|
100
101
|
* - EMAIL_FROM (default: "noreply@wopr.bot") — sender address
|
|
@@ -105,6 +106,9 @@ class ResendTransport {
|
|
|
105
106
|
* - AWS_ACCESS_KEY_ID
|
|
106
107
|
* - AWS_SECRET_ACCESS_KEY
|
|
107
108
|
*
|
|
109
|
+
* Postmark env vars:
|
|
110
|
+
* - POSTMARK_API_KEY (server token from Postmark dashboard)
|
|
111
|
+
*
|
|
108
112
|
* Resend env vars:
|
|
109
113
|
* - RESEND_API_KEY
|
|
110
114
|
*
|
|
@@ -113,10 +117,17 @@ class ResendTransport {
|
|
|
113
117
|
* - RESEND_REPLY_TO → falls back if EMAIL_REPLY_TO is not set
|
|
114
118
|
*/
|
|
115
119
|
let _client = null;
|
|
116
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Create a lazily-initialized singleton EmailClient.
|
|
122
|
+
*
|
|
123
|
+
* Optional overrides (from DB-driven product config) take precedence
|
|
124
|
+
* over env vars. Pass them on first call; subsequent calls return the
|
|
125
|
+
* cached singleton.
|
|
126
|
+
*/
|
|
127
|
+
export function getEmailClient(overrides) {
|
|
117
128
|
if (!_client) {
|
|
118
|
-
const from = process.env.EMAIL_FROM || process.env.RESEND_FROM || "noreply@wopr.bot";
|
|
119
|
-
const replyTo = process.env.EMAIL_REPLY_TO || process.env.RESEND_REPLY_TO || "support@wopr.bot";
|
|
129
|
+
const from = overrides?.from || process.env.EMAIL_FROM || process.env.RESEND_FROM || "noreply@wopr.bot";
|
|
130
|
+
const replyTo = overrides?.replyTo || process.env.EMAIL_REPLY_TO || process.env.RESEND_REPLY_TO || "support@wopr.bot";
|
|
120
131
|
const sesRegion = process.env.AWS_SES_REGION;
|
|
121
132
|
if (sesRegion) {
|
|
122
133
|
const transport = new SesTransport({
|
|
@@ -129,10 +140,19 @@ export function getEmailClient() {
|
|
|
129
140
|
_client = new EmailClient(transport);
|
|
130
141
|
logger.info("Email client initialized with AWS SES", { region: sesRegion, from });
|
|
131
142
|
}
|
|
143
|
+
else if (process.env.POSTMARK_API_KEY) {
|
|
144
|
+
const transport = new PostmarkTransport({
|
|
145
|
+
apiKey: process.env.POSTMARK_API_KEY,
|
|
146
|
+
from,
|
|
147
|
+
replyTo,
|
|
148
|
+
});
|
|
149
|
+
_client = new EmailClient(transport);
|
|
150
|
+
logger.info("Email client initialized with Postmark", { from });
|
|
151
|
+
}
|
|
132
152
|
else {
|
|
133
153
|
const apiKey = process.env.RESEND_API_KEY;
|
|
134
154
|
if (!apiKey) {
|
|
135
|
-
throw new Error("
|
|
155
|
+
throw new Error("Set AWS_SES_REGION, POSTMARK_API_KEY, or RESEND_API_KEY environment variable");
|
|
136
156
|
}
|
|
137
157
|
_client = new EmailClient({ apiKey, from, replyTo });
|
|
138
158
|
logger.info("Email client initialized with Resend", { from });
|
|
@@ -91,8 +91,8 @@ describe("getEmailClient / setEmailClient / resetEmailClient", () => {
|
|
|
91
91
|
delete process.env.RESEND_FROM;
|
|
92
92
|
delete process.env.RESEND_REPLY_TO;
|
|
93
93
|
});
|
|
94
|
-
it("should throw if
|
|
95
|
-
expect(() => getEmailClient()).toThrow("
|
|
94
|
+
it("should throw if no email provider is configured", () => {
|
|
95
|
+
expect(() => getEmailClient()).toThrow("Set AWS_SES_REGION, POSTMARK_API_KEY, or RESEND_API_KEY");
|
|
96
96
|
});
|
|
97
97
|
it("should create client from env vars", () => {
|
|
98
98
|
process.env.RESEND_API_KEY = "re_test123";
|
package/dist/email/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export type { BillingEmailServiceConfig, BillingEmailType } from "./billing-emails.js";
|
|
12
12
|
export { BillingEmailService } from "./billing-emails.js";
|
|
13
|
-
export type { EmailClientConfig, EmailSendResult, SendTemplateEmailOpts } from "./client.js";
|
|
13
|
+
export type { EmailClientConfig, EmailClientOverrides, EmailSendResult, SendTemplateEmailOpts } from "./client.js";
|
|
14
14
|
export { EmailClient, getEmailClient, resetEmailClient, setEmailClient } from "./client.js";
|
|
15
15
|
export { DEFAULT_TEMPLATES } from "./default-templates.js";
|
|
16
16
|
export type { IBillingEmailRepository } from "./drizzle-billing-email-repository.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postmark email transport.
|
|
3
|
+
*
|
|
4
|
+
* Env vars:
|
|
5
|
+
* POSTMARK_API_KEY — server API token from Postmark
|
|
6
|
+
*/
|
|
7
|
+
import type { EmailSendResult, EmailTransport, SendTemplateEmailOpts } from "./client.js";
|
|
8
|
+
export interface PostmarkTransportConfig {
|
|
9
|
+
apiKey: string;
|
|
10
|
+
from: string;
|
|
11
|
+
replyTo?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare class PostmarkTransport implements EmailTransport {
|
|
14
|
+
private client;
|
|
15
|
+
private from;
|
|
16
|
+
private replyTo;
|
|
17
|
+
constructor(config: PostmarkTransportConfig);
|
|
18
|
+
send(opts: SendTemplateEmailOpts): Promise<EmailSendResult>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postmark email transport.
|
|
3
|
+
*
|
|
4
|
+
* Env vars:
|
|
5
|
+
* POSTMARK_API_KEY — server API token from Postmark
|
|
6
|
+
*/
|
|
7
|
+
import { ServerClient } from "postmark";
|
|
8
|
+
import { logger } from "../config/logger.js";
|
|
9
|
+
export class PostmarkTransport {
|
|
10
|
+
client;
|
|
11
|
+
from;
|
|
12
|
+
replyTo;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.client = new ServerClient(config.apiKey);
|
|
15
|
+
this.from = config.from;
|
|
16
|
+
this.replyTo = config.replyTo;
|
|
17
|
+
}
|
|
18
|
+
async send(opts) {
|
|
19
|
+
const result = await this.client.sendEmail({
|
|
20
|
+
From: this.from,
|
|
21
|
+
To: opts.to,
|
|
22
|
+
Subject: opts.subject,
|
|
23
|
+
HtmlBody: opts.html,
|
|
24
|
+
TextBody: opts.text,
|
|
25
|
+
ReplyTo: this.replyTo,
|
|
26
|
+
MessageStream: "outbound",
|
|
27
|
+
});
|
|
28
|
+
if (result.ErrorCode !== 0) {
|
|
29
|
+
logger.error("Failed to send email via Postmark", {
|
|
30
|
+
to: opts.to,
|
|
31
|
+
template: opts.templateName,
|
|
32
|
+
error: result.Message,
|
|
33
|
+
code: result.ErrorCode,
|
|
34
|
+
});
|
|
35
|
+
throw new Error(`Postmark error ${result.ErrorCode}: ${result.Message}`);
|
|
36
|
+
}
|
|
37
|
+
logger.info("Email sent via Postmark", {
|
|
38
|
+
emailId: result.MessageID,
|
|
39
|
+
to: opts.to,
|
|
40
|
+
template: opts.templateName,
|
|
41
|
+
userId: opts.userId,
|
|
42
|
+
});
|
|
43
|
+
return { id: result.MessageID, success: true };
|
|
44
|
+
}
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wopr-network/platform-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.63.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -138,6 +138,7 @@
|
|
|
138
138
|
"@scure/bip39": "^2.0.1",
|
|
139
139
|
"handlebars": "^4.7.8",
|
|
140
140
|
"js-yaml": "^4.1.1",
|
|
141
|
+
"postmark": "^4.0.7",
|
|
141
142
|
"viem": "^2.47.4",
|
|
142
143
|
"yaml": "^2.8.2"
|
|
143
144
|
}
|
package/src/email/client.test.ts
CHANGED
|
@@ -116,8 +116,8 @@ describe("getEmailClient / setEmailClient / resetEmailClient", () => {
|
|
|
116
116
|
delete process.env.RESEND_REPLY_TO;
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
it("should throw if
|
|
120
|
-
expect(() => getEmailClient()).toThrow("
|
|
119
|
+
it("should throw if no email provider is configured", () => {
|
|
120
|
+
expect(() => getEmailClient()).toThrow("Set AWS_SES_REGION, POSTMARK_API_KEY, or RESEND_API_KEY");
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
it("should create client from env vars", () => {
|
package/src/email/client.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Email Client — Template-based transactional email sender.
|
|
3
3
|
*
|
|
4
|
-
* Supports
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* SES takes priority when both are configured.
|
|
4
|
+
* Supports three backends (first match wins):
|
|
5
|
+
* 1. **AWS SES**: Set AWS_SES_REGION env var (+ AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
|
|
6
|
+
* 2. **Postmark**: Set POSTMARK_API_KEY env var
|
|
7
|
+
* 3. **Resend**: Set RESEND_API_KEY env var
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
import { Resend } from "resend";
|
|
12
11
|
import { logger } from "../config/logger.js";
|
|
12
|
+
import { PostmarkTransport } from "./postmark-transport.js";
|
|
13
13
|
import { SesTransport } from "./ses-transport.js";
|
|
14
14
|
|
|
15
15
|
export interface EmailClientConfig {
|
|
@@ -134,7 +134,8 @@ class ResendTransport implements EmailTransport {
|
|
|
134
134
|
*
|
|
135
135
|
* Backend selection (first match wins):
|
|
136
136
|
* 1. AWS SES — AWS_SES_REGION is set
|
|
137
|
-
* 2.
|
|
137
|
+
* 2. Postmark — POSTMARK_API_KEY is set
|
|
138
|
+
* 3. Resend — RESEND_API_KEY is set
|
|
138
139
|
*
|
|
139
140
|
* Common env vars:
|
|
140
141
|
* - EMAIL_FROM (default: "noreply@wopr.bot") — sender address
|
|
@@ -145,6 +146,9 @@ class ResendTransport implements EmailTransport {
|
|
|
145
146
|
* - AWS_ACCESS_KEY_ID
|
|
146
147
|
* - AWS_SECRET_ACCESS_KEY
|
|
147
148
|
*
|
|
149
|
+
* Postmark env vars:
|
|
150
|
+
* - POSTMARK_API_KEY (server token from Postmark dashboard)
|
|
151
|
+
*
|
|
148
152
|
* Resend env vars:
|
|
149
153
|
* - RESEND_API_KEY
|
|
150
154
|
*
|
|
@@ -154,10 +158,25 @@ class ResendTransport implements EmailTransport {
|
|
|
154
158
|
*/
|
|
155
159
|
let _client: EmailClient | null = null;
|
|
156
160
|
|
|
157
|
-
export
|
|
161
|
+
export interface EmailClientOverrides {
|
|
162
|
+
/** Sender address — overrides EMAIL_FROM env var. */
|
|
163
|
+
from?: string;
|
|
164
|
+
/** Reply-to address — overrides EMAIL_REPLY_TO env var. */
|
|
165
|
+
replyTo?: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Create a lazily-initialized singleton EmailClient.
|
|
170
|
+
*
|
|
171
|
+
* Optional overrides (from DB-driven product config) take precedence
|
|
172
|
+
* over env vars. Pass them on first call; subsequent calls return the
|
|
173
|
+
* cached singleton.
|
|
174
|
+
*/
|
|
175
|
+
export function getEmailClient(overrides?: EmailClientOverrides): EmailClient {
|
|
158
176
|
if (!_client) {
|
|
159
|
-
const from = process.env.EMAIL_FROM || process.env.RESEND_FROM || "noreply@wopr.bot";
|
|
160
|
-
const replyTo =
|
|
177
|
+
const from = overrides?.from || process.env.EMAIL_FROM || process.env.RESEND_FROM || "noreply@wopr.bot";
|
|
178
|
+
const replyTo =
|
|
179
|
+
overrides?.replyTo || process.env.EMAIL_REPLY_TO || process.env.RESEND_REPLY_TO || "support@wopr.bot";
|
|
161
180
|
|
|
162
181
|
const sesRegion = process.env.AWS_SES_REGION;
|
|
163
182
|
if (sesRegion) {
|
|
@@ -170,10 +189,18 @@ export function getEmailClient(): EmailClient {
|
|
|
170
189
|
});
|
|
171
190
|
_client = new EmailClient(transport);
|
|
172
191
|
logger.info("Email client initialized with AWS SES", { region: sesRegion, from });
|
|
192
|
+
} else if (process.env.POSTMARK_API_KEY) {
|
|
193
|
+
const transport = new PostmarkTransport({
|
|
194
|
+
apiKey: process.env.POSTMARK_API_KEY,
|
|
195
|
+
from,
|
|
196
|
+
replyTo,
|
|
197
|
+
});
|
|
198
|
+
_client = new EmailClient(transport);
|
|
199
|
+
logger.info("Email client initialized with Postmark", { from });
|
|
173
200
|
} else {
|
|
174
201
|
const apiKey = process.env.RESEND_API_KEY;
|
|
175
202
|
if (!apiKey) {
|
|
176
|
-
throw new Error("
|
|
203
|
+
throw new Error("Set AWS_SES_REGION, POSTMARK_API_KEY, or RESEND_API_KEY environment variable");
|
|
177
204
|
}
|
|
178
205
|
_client = new EmailClient({ apiKey, from, replyTo });
|
|
179
206
|
logger.info("Email client initialized with Resend", { from });
|
package/src/email/index.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
export type { BillingEmailServiceConfig, BillingEmailType } from "./billing-emails.js";
|
|
13
13
|
export { BillingEmailService } from "./billing-emails.js";
|
|
14
|
-
export type { EmailClientConfig, EmailSendResult, SendTemplateEmailOpts } from "./client.js";
|
|
14
|
+
export type { EmailClientConfig, EmailClientOverrides, EmailSendResult, SendTemplateEmailOpts } from "./client.js";
|
|
15
15
|
export { EmailClient, getEmailClient, resetEmailClient, setEmailClient } from "./client.js";
|
|
16
16
|
export { DEFAULT_TEMPLATES } from "./default-templates.js";
|
|
17
17
|
export type { IBillingEmailRepository } from "./drizzle-billing-email-repository.js";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postmark email transport.
|
|
3
|
+
*
|
|
4
|
+
* Env vars:
|
|
5
|
+
* POSTMARK_API_KEY — server API token from Postmark
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ServerClient } from "postmark";
|
|
9
|
+
import { logger } from "../config/logger.js";
|
|
10
|
+
import type { EmailSendResult, EmailTransport, SendTemplateEmailOpts } from "./client.js";
|
|
11
|
+
|
|
12
|
+
export interface PostmarkTransportConfig {
|
|
13
|
+
apiKey: string;
|
|
14
|
+
from: string;
|
|
15
|
+
replyTo?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class PostmarkTransport implements EmailTransport {
|
|
19
|
+
private client: ServerClient;
|
|
20
|
+
private from: string;
|
|
21
|
+
private replyTo: string | undefined;
|
|
22
|
+
|
|
23
|
+
constructor(config: PostmarkTransportConfig) {
|
|
24
|
+
this.client = new ServerClient(config.apiKey);
|
|
25
|
+
this.from = config.from;
|
|
26
|
+
this.replyTo = config.replyTo;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async send(opts: SendTemplateEmailOpts): Promise<EmailSendResult> {
|
|
30
|
+
const result = await this.client.sendEmail({
|
|
31
|
+
From: this.from,
|
|
32
|
+
To: opts.to,
|
|
33
|
+
Subject: opts.subject,
|
|
34
|
+
HtmlBody: opts.html,
|
|
35
|
+
TextBody: opts.text,
|
|
36
|
+
ReplyTo: this.replyTo,
|
|
37
|
+
MessageStream: "outbound",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (result.ErrorCode !== 0) {
|
|
41
|
+
logger.error("Failed to send email via Postmark", {
|
|
42
|
+
to: opts.to,
|
|
43
|
+
template: opts.templateName,
|
|
44
|
+
error: result.Message,
|
|
45
|
+
code: result.ErrorCode,
|
|
46
|
+
});
|
|
47
|
+
throw new Error(`Postmark error ${result.ErrorCode}: ${result.Message}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
logger.info("Email sent via Postmark", {
|
|
51
|
+
emailId: result.MessageID,
|
|
52
|
+
to: opts.to,
|
|
53
|
+
template: opts.templateName,
|
|
54
|
+
userId: opts.userId,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return { id: result.MessageID, success: true };
|
|
58
|
+
}
|
|
59
|
+
}
|