@nymbalhq/email-mailgun 0.0.0-stage → 1.5.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/README.md CHANGED
@@ -1,3 +1,44 @@
1
- # Temporary Holding Version
1
+ # @nymbalhq/email-mailgun
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The `mailgun` email provider for Nymbal. It sends every transactional mail over Mailgun's SMTP relay, and takes
4
+ Mailgun's bounce and complaint webhooks, so a permanent failure or a spam complaint stops alert mail to that address.
5
+
6
+ ```sh
7
+ npm install @nymbalhq/email-mailgun nodemailer
8
+ ```
9
+
10
+ ```ts
11
+ // nymbal.config.ts
12
+ commerce: {
13
+ email: {
14
+ provider: 'mailgun',
15
+ config: {
16
+ from: 'shop@mg.example.com',
17
+ region: 'us', // 'eu' for a domain in Mailgun's EU region
18
+ smtp: { user: 'postmaster@mg.example.com', pass: env('MAILGUN_SMTP_PASSWORD') },
19
+ webhookSigningKey: env('MAILGUN_WEBHOOK_SIGNING_KEY'),
20
+ },
21
+ },
22
+ }
23
+ ```
24
+
25
+ - `smtp.host` defaults to `smtp.mailgun.org` (`smtp.eu.mailgun.org` for `region: 'eu'`) and `smtp.port` to 587.
26
+ - `smtp.pass` and `webhookSigningKey` may be left out of the config and saved in the admin's Settings instead.
27
+ - `webhookSigningKey` is the domain's HTTP webhook signing key (Mailgun: Sending, Webhooks). Without it the store
28
+ refuses every webhook post and the email slot's health is `degraded`.
29
+
30
+ ## Webhooks
31
+
32
+ In Mailgun, for the sending domain, point these webhooks at `<api origin>/api/webhooks/mailgun`:
33
+
34
+ - Permanent failure
35
+ - Temporary failure
36
+ - Spam complaint
37
+
38
+ A permanent failure is recorded as a hard bounce (`mailgun.bounced.v1`) and a complaint as `mailgun.complained.v1`;
39
+ both stop alert mail to the address. A temporary failure is a soft bounce and stops nothing. Any other event posted
40
+ to the same URL is answered 200 and ignored.
41
+
42
+ Only a post signed with the key, with a timestamp within five minutes of the server's clock, is accepted. Each
43
+ signature is accepted for one event: Mailgun's retry of that event is accepted again, the same signature on any other
44
+ event is refused. That memory is per API process.
@@ -0,0 +1,32 @@
1
+ import { Logger, EmailAdapter, AdapterContext, AdapterSetting } from '@nymbalhq/types';
2
+
3
+ interface MailgunEmailAdapterConfig {
4
+ from?: string;
5
+ /** Mailgun's SMTP relay: the domain's SMTP login, with the host from `region` and port 587 unless given. */
6
+ smtp?: {
7
+ host?: string;
8
+ port?: number;
9
+ user?: string;
10
+ pass?: string;
11
+ secure?: boolean;
12
+ };
13
+ region?: 'us' | 'eu';
14
+ /** The domain's HTTP webhook signing key. Without it every webhook post is refused, and health says so. */
15
+ webhookSigningKey?: string;
16
+ }
17
+ declare function createMailgunEmailAdapter(deps: {
18
+ logger: Logger;
19
+ config?: MailgunEmailAdapterConfig;
20
+ }): EmailAdapter;
21
+
22
+ /** What this package serves: the kernel refuses it on any other slot or provider. */
23
+ declare const slot = "email";
24
+ declare const provider = "mailgun";
25
+
26
+ /** The fields the admin's Settings may fill; without the signing key the slot runs, degraded, and refuses every post. */
27
+ declare const settings: AdapterSetting[];
28
+ /** The kernel's entry: `email: { provider: 'mailgun', config: MailgunEmailAdapterConfig }`. */
29
+ declare function createAdapter(ctx: AdapterContext): EmailAdapter;
30
+
31
+ export { createAdapter, createMailgunEmailAdapter, provider, settings, slot };
32
+ export type { MailgunEmailAdapterConfig };
package/dist/index.js ADDED
@@ -0,0 +1,197 @@
1
+ // src/index.ts
2
+ import {
3
+ ConfigError
4
+ } from "@nymbalhq/types";
5
+
6
+ // src/mailgun-adapter.ts
7
+ import { createSmtpTransportSender } from "@nymbalhq/email-smtp";
8
+ import {
9
+ AdapterError,
10
+ boundedHealthCheck,
11
+ EVT_CART_ABANDONED,
12
+ EVT_CUSTOMER_CREATED,
13
+ EVT_ORDER_PLACED,
14
+ EVT_ORDER_SHIPPED
15
+ } from "@nymbalhq/types";
16
+
17
+ // src/webhook.ts
18
+ import { createHmac, timingSafeEqual } from "crypto";
19
+ import { emailFeedbackType } from "@nymbalhq/types";
20
+ var MAILGUN_BOUNCED = emailFeedbackType("mailgun", "bounced");
21
+ var MAILGUN_COMPLAINED = emailFeedbackType("mailgun", "complained");
22
+ var WINDOW_SECONDS = 300;
23
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
24
+ function notVerified() {
25
+ return { verified: false, deliveryId: "", events: [] };
26
+ }
27
+ function parse(rawBody) {
28
+ try {
29
+ const body = JSON.parse(rawBody.toString("utf8"));
30
+ return isObject(body) ? body : null;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ function signedWith(key, timestamp, token, signature) {
36
+ const expected = Buffer.from(
37
+ createHmac("sha256", key).update(timestamp).update(token).digest("hex"),
38
+ "utf8"
39
+ );
40
+ const given = Buffer.from(signature, "utf8");
41
+ return given.length === expected.length && timingSafeEqual(given, expected);
42
+ }
43
+ function messageIdOf(data) {
44
+ const message = data["message"];
45
+ const headers = isObject(message) ? message["headers"] : void 0;
46
+ const id = isObject(headers) ? headers["message-id"] : void 0;
47
+ return typeof id === "string" ? id : null;
48
+ }
49
+ function occurredAtOf(data) {
50
+ const seconds = data["timestamp"];
51
+ if (typeof seconds !== "number") return null;
52
+ const at = new Date(seconds * 1e3);
53
+ return Number.isNaN(at.getTime()) ? null : at.toISOString();
54
+ }
55
+ function eventsOf(data) {
56
+ const recipient = data["recipient"];
57
+ const messageId = messageIdOf(data);
58
+ const occurredAt = occurredAtOf(data);
59
+ if (data["event"] === "failed") {
60
+ const bounce = data["severity"] === "permanent" ? "hard" : "soft";
61
+ return [{ type: MAILGUN_BOUNCED, payload: { recipient, bounce, messageId, occurredAt } }];
62
+ }
63
+ if (data["event"] === "complained") {
64
+ return [{ type: MAILGUN_COMPLAINED, payload: { recipient, messageId, occurredAt } }];
65
+ }
66
+ return [];
67
+ }
68
+ function createMailgunWebhookReceiver(signingKey) {
69
+ const tokens = /* @__PURE__ */ new Map();
70
+ return async (rawBody) => {
71
+ const now = Date.now();
72
+ for (const [token2, entry] of tokens) {
73
+ if (entry.expiresAt < now) tokens.delete(token2);
74
+ }
75
+ const key = signingKey();
76
+ const body = parse(rawBody);
77
+ const signature = body?.["signature"];
78
+ const data = body?.["event-data"];
79
+ if (key === void 0 || !isObject(signature) || !isObject(data)) return notVerified();
80
+ const { timestamp, token, signature: given } = signature;
81
+ if (typeof timestamp !== "string" || typeof token !== "string" || typeof given !== "string") {
82
+ return notVerified();
83
+ }
84
+ if (!signedWith(key, timestamp, token, given)) return notVerified();
85
+ const signedAt = Number(timestamp);
86
+ if (!Number.isFinite(signedAt) || Math.abs(now / 1e3 - signedAt) > WINDOW_SECONDS) {
87
+ return notVerified();
88
+ }
89
+ const eventId = data["id"];
90
+ if (typeof eventId !== "string" || eventId === "") return notVerified();
91
+ const seen = tokens.get(token);
92
+ if (seen !== void 0 && seen.eventId !== eventId) return notVerified();
93
+ if (seen === void 0) {
94
+ tokens.set(token, { eventId, expiresAt: (signedAt + WINDOW_SECONDS) * 1e3 });
95
+ }
96
+ return { verified: true, deliveryId: eventId, events: eventsOf(data) };
97
+ };
98
+ }
99
+
100
+ // src/mailgun-adapter.ts
101
+ var RELAY_HOSTS = { us: "smtp.mailgun.org", eu: "smtp.eu.mailgun.org" };
102
+ var RELAY_PORT = 587;
103
+ var NO_KEY = "webhook signing key not set";
104
+ function missingLogin(cfg) {
105
+ const missing = [];
106
+ if (typeof cfg.smtp?.user !== "string" || cfg.smtp.user === "") missing.push("smtp.user");
107
+ if (typeof cfg.smtp?.pass !== "string" || cfg.smtp.pass === "") missing.push("smtp.pass");
108
+ return missing;
109
+ }
110
+ function loginText(missing) {
111
+ return `Mailgun SMTP login incomplete: ${missing.join(", ")} not set`;
112
+ }
113
+ function transportConfig(cfg) {
114
+ const smtp = cfg.smtp;
115
+ return {
116
+ ...cfg.from !== void 0 && { from: cfg.from },
117
+ smtp: {
118
+ host: smtp?.host ?? RELAY_HOSTS[cfg.region === "eu" ? "eu" : "us"],
119
+ port: smtp?.port ?? RELAY_PORT,
120
+ ...smtp?.user !== void 0 && { user: smtp.user },
121
+ ...smtp?.pass !== void 0 && { pass: smtp.pass },
122
+ ...smtp?.secure !== void 0 && { secure: smtp.secure }
123
+ }
124
+ };
125
+ }
126
+ function createMailgunEmailAdapter(deps) {
127
+ const log = deps.logger.child({ adapter: "email", provider: "mailgun" });
128
+ let cfg = deps.config ?? {};
129
+ const sender = createSmtpTransportSender(transportConfig(cfg), log);
130
+ const signingKey = () => typeof cfg.webhookSigningKey === "string" && cfg.webhookSigningKey !== "" ? cfg.webhookSigningKey : void 0;
131
+ const receive = createMailgunWebhookReceiver(signingKey);
132
+ return {
133
+ kind: "email",
134
+ providerName: "mailgun",
135
+ capabilities: () => ["transactional", "list-sync"],
136
+ producesEvents: [MAILGUN_BOUNCED, MAILGUN_COMPLAINED],
137
+ consumesEvents: [EVT_ORDER_PLACED, EVT_ORDER_SHIPPED, EVT_CUSTOMER_CREATED, EVT_CART_ABANDONED],
138
+ async initialize(rawConfig) {
139
+ cfg = { ...cfg, ...rawConfig };
140
+ sender.configure(transportConfig(cfg));
141
+ },
142
+ healthCheck: () => boundedHealthCheck(async () => {
143
+ const smtp = await sender.verify();
144
+ const missing = missingLogin(cfg);
145
+ const problems = [
146
+ ...missing.length > 0 ? [`${loginText(missing)}: Mailgun refuses every send`] : [],
147
+ ...signingKey() === void 0 ? [`${NO_KEY}: every Mailgun webhook post is refused`] : []
148
+ ];
149
+ if (problems.length === 0) return smtp;
150
+ return smtp.status === "healthy" ? { status: "degraded", detail: problems.join("; ") } : {
151
+ status: smtp.status,
152
+ detail: [smtp.detail ?? "SMTP verify failed", ...problems].join("; ")
153
+ };
154
+ }),
155
+ sendTransactional: (params) => {
156
+ const missing = missingLogin(cfg);
157
+ if (missing.length > 0) {
158
+ return Promise.reject(
159
+ new AdapterError("email.misconfigured", loginText(missing), { context: { missing } })
160
+ );
161
+ }
162
+ return sender.sendTransactional(params);
163
+ },
164
+ syncCustomerToList: (customer) => sender.syncCustomerToList(customer),
165
+ removeCustomerFromList: (customerId) => sender.removeCustomerFromList(customerId),
166
+ receiveWebhook: (rawBody) => receive(rawBody)
167
+ };
168
+ }
169
+
170
+ // src/index.ts
171
+ var slot = "email";
172
+ var provider = "mailgun";
173
+ var settings = [
174
+ { path: "smtp.user", label: "Mailgun SMTP user", secret: false },
175
+ { path: "smtp.pass", label: "Mailgun SMTP password", secret: true },
176
+ { path: "webhookSigningKey", label: "Webhook signing key", secret: true, optional: true }
177
+ ];
178
+ function createAdapter(ctx) {
179
+ if (ctx.slot !== "email") {
180
+ throw new ConfigError(`@nymbalhq/email-mailgun serves email, not ${ctx.slot}`, {
181
+ code: "adapter.wrong_slot",
182
+ context: { slot: ctx.slot }
183
+ });
184
+ }
185
+ return createMailgunEmailAdapter({
186
+ logger: ctx.logger,
187
+ config: ctx.config
188
+ });
189
+ }
190
+ export {
191
+ createAdapter,
192
+ createMailgunEmailAdapter,
193
+ provider,
194
+ settings,
195
+ slot
196
+ };
197
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/mailgun-adapter.ts","../src/webhook.ts"],"sourcesContent":["import {\n ConfigError,\n type AdapterContext,\n type AdapterSetting,\n type EmailAdapter,\n} from '@nymbalhq/types'\nimport { createMailgunEmailAdapter, type MailgunEmailAdapterConfig } from './mailgun-adapter.js'\n\n/** What this package serves: the kernel refuses it on any other slot or provider. */\nexport const slot = 'email'\nexport const provider = 'mailgun'\n\nexport { createMailgunEmailAdapter, type MailgunEmailAdapterConfig }\n\n/** The fields the admin's Settings may fill; without the signing key the slot runs, degraded, and refuses every post. */\nexport const settings: AdapterSetting[] = [\n { path: 'smtp.user', label: 'Mailgun SMTP user', secret: false },\n { path: 'smtp.pass', label: 'Mailgun SMTP password', secret: true },\n { path: 'webhookSigningKey', label: 'Webhook signing key', secret: true, optional: true },\n]\n\n/** The kernel's entry: `email: { provider: 'mailgun', config: MailgunEmailAdapterConfig }`. */\nexport function createAdapter(ctx: AdapterContext): EmailAdapter {\n if (ctx.slot !== 'email') {\n throw new ConfigError(`@nymbalhq/email-mailgun serves email, not ${ctx.slot}`, {\n code: 'adapter.wrong_slot',\n context: { slot: ctx.slot },\n })\n }\n return createMailgunEmailAdapter({\n logger: ctx.logger,\n config: ctx.config as unknown as MailgunEmailAdapterConfig,\n })\n}\n","import { createSmtpTransportSender, type SmtpTransportConfig } from '@nymbalhq/email-smtp'\nimport {\n AdapterError,\n boundedHealthCheck,\n EVT_CART_ABANDONED,\n EVT_CUSTOMER_CREATED,\n EVT_ORDER_PLACED,\n EVT_ORDER_SHIPPED,\n type EmailAdapter,\n type Logger,\n} from '@nymbalhq/types'\nimport { createMailgunWebhookReceiver, MAILGUN_BOUNCED, MAILGUN_COMPLAINED } from './webhook.js'\n\nexport interface MailgunEmailAdapterConfig {\n from?: string\n /** Mailgun's SMTP relay: the domain's SMTP login, with the host from `region` and port 587 unless given. */\n smtp?: {\n host?: string\n port?: number\n user?: string\n pass?: string\n secure?: boolean\n }\n region?: 'us' | 'eu'\n /** The domain's HTTP webhook signing key. Without it every webhook post is refused, and health says so. */\n webhookSigningKey?: string\n}\n\nconst RELAY_HOSTS = { us: 'smtp.mailgun.org', eu: 'smtp.eu.mailgun.org' } as const\nconst RELAY_PORT = 587\nconst NO_KEY = 'webhook signing key not set'\n\n/** Mailgun refuses an unauthenticated relay, and nodemailer only logs in with both fields: name each one missing. */\nfunction missingLogin(cfg: MailgunEmailAdapterConfig): string[] {\n const missing: string[] = []\n if (typeof cfg.smtp?.user !== 'string' || cfg.smtp.user === '') missing.push('smtp.user')\n if (typeof cfg.smtp?.pass !== 'string' || cfg.smtp.pass === '') missing.push('smtp.pass')\n return missing\n}\n\nfunction loginText(missing: string[]): string {\n return `Mailgun SMTP login incomplete: ${missing.join(', ')} not set`\n}\n\nfunction transportConfig(cfg: MailgunEmailAdapterConfig): SmtpTransportConfig {\n const smtp = cfg.smtp\n return {\n ...(cfg.from !== undefined && { from: cfg.from }),\n smtp: {\n host: smtp?.host ?? RELAY_HOSTS[cfg.region === 'eu' ? 'eu' : 'us'],\n port: smtp?.port ?? RELAY_PORT,\n ...(smtp?.user !== undefined && { user: smtp.user }),\n ...(smtp?.pass !== undefined && { pass: smtp.pass }),\n ...(smtp?.secure !== undefined && { secure: smtp.secure }),\n },\n }\n}\n\nexport function createMailgunEmailAdapter(deps: {\n logger: Logger\n config?: MailgunEmailAdapterConfig\n}): EmailAdapter {\n const log = deps.logger.child({ adapter: 'email', provider: 'mailgun' })\n let cfg: MailgunEmailAdapterConfig = deps.config ?? {}\n const sender = createSmtpTransportSender(transportConfig(cfg), log)\n const signingKey = () =>\n typeof cfg.webhookSigningKey === 'string' && cfg.webhookSigningKey !== ''\n ? cfg.webhookSigningKey\n : undefined\n const receive = createMailgunWebhookReceiver(signingKey)\n\n return {\n kind: 'email',\n providerName: 'mailgun',\n capabilities: () => ['transactional', 'list-sync'],\n producesEvents: [MAILGUN_BOUNCED, MAILGUN_COMPLAINED],\n consumesEvents: [EVT_ORDER_PLACED, EVT_ORDER_SHIPPED, EVT_CUSTOMER_CREATED, EVT_CART_ABANDONED],\n async initialize(rawConfig) {\n cfg = { ...cfg, ...(rawConfig as MailgunEmailAdapterConfig) }\n sender.configure(transportConfig(cfg))\n },\n healthCheck: () =>\n boundedHealthCheck(async () => {\n const smtp = await sender.verify()\n const missing = missingLogin(cfg)\n const problems = [\n ...(missing.length > 0 ? [`${loginText(missing)}: Mailgun refuses every send`] : []),\n ...(signingKey() === undefined\n ? [`${NO_KEY}: every Mailgun webhook post is refused`]\n : []),\n ]\n if (problems.length === 0) return smtp\n // A relay that is down, or refuses the login, keeps its status; each missing field is named beside it.\n return smtp.status === 'healthy'\n ? { status: 'degraded', detail: problems.join('; ') }\n : {\n status: smtp.status,\n detail: [smtp.detail ?? 'SMTP verify failed', ...problems].join('; '),\n }\n }),\n sendTransactional: (params) => {\n const missing = missingLogin(cfg)\n if (missing.length > 0) {\n return Promise.reject(\n new AdapterError('email.misconfigured', loginText(missing), { context: { missing } }),\n )\n }\n return sender.sendTransactional(params)\n },\n syncCustomerToList: (customer) => sender.syncCustomerToList(customer),\n removeCustomerFromList: (customerId) => sender.removeCustomerFromList(customerId),\n receiveWebhook: (rawBody) => receive(rawBody),\n }\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto'\nimport { emailFeedbackType, type WebhookDelivery, type WebhookEventDraft } from '@nymbalhq/types'\n\nexport const MAILGUN_BOUNCED = emailFeedbackType('mailgun', 'bounced')\nexport const MAILGUN_COMPLAINED = emailFeedbackType('mailgun', 'complained')\n\n/** How far `signature.timestamp` may be from now, either way. */\nconst WINDOW_SECONDS = 300\n\ntype Json = Record<string, unknown>\n\nconst isObject = (value: unknown): value is Json =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nfunction notVerified(): WebhookDelivery {\n return { verified: false, deliveryId: '', events: [] }\n}\n\nfunction parse(rawBody: Buffer): Json | null {\n try {\n const body: unknown = JSON.parse(rawBody.toString('utf8'))\n return isObject(body) ? body : null\n } catch {\n return null\n }\n}\n\n/** Mailgun's HMAC is over `timestamp` then `token`, the strings as posted; `event-data` is not signed. */\nfunction signedWith(key: string, timestamp: string, token: string, signature: string): boolean {\n const expected = Buffer.from(\n createHmac('sha256', key).update(timestamp).update(token).digest('hex'),\n 'utf8',\n )\n const given = Buffer.from(signature, 'utf8')\n return given.length === expected.length && timingSafeEqual(given, expected)\n}\n\nfunction messageIdOf(data: Json): string | null {\n const message = data['message']\n const headers = isObject(message) ? message['headers'] : undefined\n const id = isObject(headers) ? headers['message-id'] : undefined\n return typeof id === 'string' ? id : null\n}\n\n/** `event-data.timestamp`, epoch seconds with a fraction, as ISO 8601; null leaves the kernel to refuse it. */\nfunction occurredAtOf(data: Json): string | null {\n const seconds = data['timestamp']\n if (typeof seconds !== 'number') return null\n const at = new Date(seconds * 1000)\n return Number.isNaN(at.getTime()) ? null : at.toISOString()\n}\n\n/**\n * A permanent failure is a hard bounce and any other failure soft; events other than failures and complaints carry\n * none. The recipient is handed on as given: the kernel normalises it, and refuses a delivery whose payload is malformed.\n */\nfunction eventsOf(data: Json): WebhookEventDraft[] {\n const recipient = data['recipient']\n const messageId = messageIdOf(data)\n const occurredAt = occurredAtOf(data)\n if (data['event'] === 'failed') {\n const bounce = data['severity'] === 'permanent' ? 'hard' : 'soft'\n return [{ type: MAILGUN_BOUNCED, payload: { recipient, bounce, messageId, occurredAt } }]\n }\n if (data['event'] === 'complained') {\n return [{ type: MAILGUN_COMPLAINED, payload: { recipient, messageId, occurredAt } }]\n }\n return []\n}\n\n/**\n * Mailgun's webhook posts, verified. Since the body is unsigned, one captured signature would carry any `event-data`\n * while its timestamp is fresh, so each accepted token is bound to its event id until the timestamp leaves the window:\n * the same token with the same id is Mailgun's retry, with another id a forgery. The binding is per process.\n */\nexport function createMailgunWebhookReceiver(\n signingKey: () => string | undefined,\n): (rawBody: Buffer) => Promise<WebhookDelivery> {\n const tokens = new Map<string, { eventId: string; expiresAt: number }>()\n\n return async (rawBody) => {\n const now = Date.now()\n for (const [token, entry] of tokens) {\n if (entry.expiresAt < now) tokens.delete(token)\n }\n\n const key = signingKey()\n const body = parse(rawBody)\n const signature = body?.['signature']\n const data = body?.['event-data']\n if (key === undefined || !isObject(signature) || !isObject(data)) return notVerified()\n const { timestamp, token, signature: given } = signature\n if (typeof timestamp !== 'string' || typeof token !== 'string' || typeof given !== 'string') {\n return notVerified()\n }\n if (!signedWith(key, timestamp, token, given)) return notVerified()\n const signedAt = Number(timestamp)\n if (!Number.isFinite(signedAt) || Math.abs(now / 1000 - signedAt) > WINDOW_SECONDS) {\n return notVerified()\n }\n const eventId = data['id']\n if (typeof eventId !== 'string' || eventId === '') return notVerified()\n const seen = tokens.get(token)\n if (seen !== undefined && seen.eventId !== eventId) return notVerified()\n if (seen === undefined) {\n tokens.set(token, { eventId, expiresAt: (signedAt + WINDOW_SECONDS) * 1000 })\n }\n\n return { verified: true, deliveryId: eventId, events: eventsOf(data) }\n }\n}\n"],"mappings":"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","names":["token"]}
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "@nymbalhq/email-mailgun",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.5.0",
4
+ "description": "Mailgun email for Nymbal: the `mailgun` email provider (SMTP relay, bounce and complaint webhooks)",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/nymbalhq/nymbal.git",
9
+ "directory": "packages/adapters/email/mailgun"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "type": "module",
15
+ "engines": {
16
+ "node": ">=22"
17
+ },
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "dependencies": {
30
+ "@nymbalhq/email-smtp": "^1.5.0"
31
+ },
32
+ "peerDependencies": {
33
+ "@nymbalhq/types": "^1.5.0",
34
+ "nodemailer": "^6.9.16"
35
+ },
36
+ "devDependencies": {
37
+ "@nymbalhq/types": "1.5.0",
38
+ "@types/nodemailer": "^6.4.17",
39
+ "@typescript/native": "npm:typescript@^7.0.2",
40
+ "fast-check": "^4.10.2",
41
+ "nodemailer": "^6.9.16",
42
+ "tsup": "^9.0.0",
43
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
44
+ "vitest": "^3.0.0"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup",
48
+ "dev": "tsup --watch",
49
+ "typecheck": "tsc -p tsconfig.json --noEmit --rootDir ../../../.."
50
+ }
6
51
  }