@vrplatform/email 1.0.2-39672497282 → 1.0.2-40036090988
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/build/main/utils/find-postmark-email.js +40 -7
- package/build/main/utils/find-postmark-email.js.map +1 -1
- package/build/main/utils/send-email.js +41 -8
- package/build/main/utils/send-email.js.map +1 -1
- package/build/module/utils/find-postmark-email.js +1 -1
- package/build/module/utils/find-postmark-email.js.map +1 -1
- package/build/module/utils/send-email.js +1 -1
- package/build/module/utils/send-email.js.map +1 -1
- package/package.json +2 -2
- package/src/utils/find-postmark-email.ts +1 -1
- package/src/utils/send-email.ts +1 -1
|
@@ -1,15 +1,48 @@
|
|
|
1
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.findPostmarkEmail = findPostmarkEmail;
|
|
4
37
|
const utils_1 = require("@vrplatform/utils");
|
|
5
|
-
const
|
|
38
|
+
const z = __importStar(require("zod"));
|
|
6
39
|
const send_email_1 = require("./send-email");
|
|
7
|
-
const postmarkMessagesResponseSchema =
|
|
8
|
-
TotalCount:
|
|
9
|
-
Messages:
|
|
10
|
-
MessageID:
|
|
11
|
-
ReceivedAt:
|
|
12
|
-
Metadata:
|
|
40
|
+
const postmarkMessagesResponseSchema = z.object({
|
|
41
|
+
TotalCount: z.number().int().nonnegative(),
|
|
42
|
+
Messages: z.array(z.object({
|
|
43
|
+
MessageID: z.string().min(1),
|
|
44
|
+
ReceivedAt: z.string().refine((value) => (0, utils_1.day)(value).isValid()),
|
|
45
|
+
Metadata: z.object({ deliveryId: z.string() }).passthrough(),
|
|
13
46
|
})),
|
|
14
47
|
});
|
|
15
48
|
async function findPostmarkEmail(deliveryId, options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find-postmark-email.js","sourceRoot":"src/","sources":["utils/find-postmark-email.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"find-postmark-email.js","sourceRoot":"src/","sources":["utils/find-postmark-email.ts"],"names":[],"mappings":"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","sourcesContent":["import { day } from '@vrplatform/utils';\nimport * as z from 'zod';\nimport { EmailDeliveryError } from './send-email';\n\nconst postmarkMessagesResponseSchema = z.object({\n TotalCount: z.number().int().nonnegative(),\n Messages: z.array(\n z.object({\n MessageID: z.string().min(1),\n ReceivedAt: z.string().refine((value) => day(value).isValid()),\n Metadata: z.object({ deliveryId: z.string() }).passthrough(),\n })\n ),\n});\n\nexport async function findPostmarkEmail(\n deliveryId: string,\n options: {\n fetch?: (\n input: string | URL | Request,\n init?: RequestInit\n ) => Promise<Response>;\n postmarkToken?: string;\n }\n) {\n if (!options.postmarkToken) {\n throw new EmailDeliveryError(\n 'Postmark is not configured',\n undefined,\n false,\n false\n );\n }\n\n const url = new URL('https://api.postmarkapp.com/messages/outbound');\n url.searchParams.set('count', '1');\n url.searchParams.set('offset', '0');\n url.searchParams.set('messagestream', 'outbound');\n url.searchParams.set('metadata_deliveryId', deliveryId);\n const response = await (options.fetch ?? fetch)(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n 'X-Postmark-Server-Token': options.postmarkToken,\n },\n });\n const providerResponse: unknown = await response.json().catch(() => ({}));\n\n if (!response.ok) {\n throw new EmailDeliveryError(\n 'Postmark message lookup failed',\n {\n provider: 'postmark',\n providerCategory: 'rejected',\n httpStatus: response.status,\n },\n false,\n response.status === 429 || response.status >= 500\n );\n }\n\n const result = postmarkMessagesResponseSchema.safeParse(providerResponse);\n if (!result.success) {\n throw new EmailDeliveryError('Postmark returned an invalid response');\n }\n if (result.data.TotalCount === 0) return undefined;\n\n const message = result.data.Messages.find(\n ({ Metadata }) => Metadata.deliveryId === deliveryId\n );\n if (!message) {\n throw new EmailDeliveryError('Postmark returned an invalid response');\n }\n\n return {\n httpStatus: response.status,\n messageId: message.MessageID,\n submittedAt: message.ReceivedAt,\n };\n}\n"]}
|
|
@@ -1,24 +1,57 @@
|
|
|
1
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.EmailDeliveryError = void 0;
|
|
4
37
|
exports.formatSenderIdentity = formatSenderIdentity;
|
|
5
38
|
exports.sendEmail = sendEmail;
|
|
6
39
|
exports.settleEmailDeliveries = settleEmailDeliveries;
|
|
7
40
|
const utils_1 = require("@vrplatform/utils");
|
|
8
|
-
const
|
|
9
|
-
const postmarkErrorCodeSchema =
|
|
10
|
-
const postmarkResponseErrorCodeSchema =
|
|
41
|
+
const z = __importStar(require("zod"));
|
|
42
|
+
const postmarkErrorCodeSchema = z.number().int().nonnegative();
|
|
43
|
+
const postmarkResponseErrorCodeSchema = z
|
|
11
44
|
.object({ ErrorCode: postmarkErrorCodeSchema })
|
|
12
45
|
.transform(({ ErrorCode }) => ErrorCode);
|
|
13
|
-
const postmarkHeaderErrorCodeSchema =
|
|
46
|
+
const postmarkHeaderErrorCodeSchema = z
|
|
14
47
|
.string()
|
|
15
48
|
.regex(/^\d+$/)
|
|
16
49
|
.transform(Number)
|
|
17
50
|
.pipe(postmarkErrorCodeSchema);
|
|
18
|
-
const postmarkSuccessResponseSchema =
|
|
19
|
-
ErrorCode:
|
|
20
|
-
MessageID:
|
|
21
|
-
SubmittedAt:
|
|
51
|
+
const postmarkSuccessResponseSchema = z.object({
|
|
52
|
+
ErrorCode: z.literal(0),
|
|
53
|
+
MessageID: z.string().min(1),
|
|
54
|
+
SubmittedAt: z.string().refine((value) => (0, utils_1.day)(value).isValid()),
|
|
22
55
|
});
|
|
23
56
|
function parseOptional(schema, value) {
|
|
24
57
|
const result = schema.safeParse(value);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-email.js","sourceRoot":"src/","sources":["utils/send-email.ts"],"names":[],"mappings":"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","sourcesContent":["import { day } from '@vrplatform/utils';\nimport { z } from 'zod';\n\nconst postmarkErrorCodeSchema = z.number().int().nonnegative();\nconst postmarkResponseErrorCodeSchema = z\n .object({ ErrorCode: postmarkErrorCodeSchema })\n .transform(({ ErrorCode }) => ErrorCode);\nconst postmarkHeaderErrorCodeSchema = z\n .string()\n .regex(/^\\d+$/)\n .transform(Number)\n .pipe(postmarkErrorCodeSchema);\nconst postmarkSuccessResponseSchema = z.object({\n ErrorCode: z.literal(0),\n MessageID: z.string().min(1),\n SubmittedAt: z.string().refine((value) => day(value).isValid()),\n});\n\nfunction parseOptional<T>(schema: z.ZodType<T>, value: unknown) {\n const result = schema.safeParse(value);\n if (result.success) return result.data;\n}\n\ntype EmailDeliveryFailureDetails = {\n httpStatus: number;\n postmarkErrorCode?: number;\n provider: 'postmark';\n providerCategory: 'inactiveRecipient' | 'rejected';\n};\n\ntype EmailDeliveryErrorDetails =\n | EmailDeliveryFailureDetails\n | {\n failureCount: number;\n failures: EmailDeliveryFailureDetails[];\n };\n\nexport class EmailDeliveryError extends Error {\n constructor(\n message: string,\n public readonly details?: EmailDeliveryErrorDetails,\n public readonly expected = false,\n public readonly retryable = true\n ) {\n super(message);\n this.name = 'EmailDeliveryError';\n }\n}\n\n/**\n * Formats a verified sender address with a display name for the From header.\n * Registrations store bare addresses; recipients should still see the company\n * name the way they see `VRPlatform <notifications@vrplatform.app>`.\n */\nexport function formatSenderIdentity(\n name: string | null | undefined,\n address: string\n) {\n const display = (name ?? '').replace(/[\\r\\n\"<>]/g, '').trim();\n return display ? `\"${display}\" <${address}>` : address;\n}\n\nexport async function sendEmail(\n data: {\n to: string;\n subject: string;\n text: string;\n html: string;\n replyTo?: string;\n tag?: string;\n metadata?: Record<string, string>;\n from?: string;\n headers?: Record<string, string>;\n },\n options: {\n fetch?: (\n input: string | URL | Request,\n init?: RequestInit\n ) => Promise<Response>;\n postmarkToken?: string;\n }\n) {\n if (!options.postmarkToken) {\n throw new EmailDeliveryError(\n 'Postmark is not configured',\n undefined,\n false,\n false\n );\n }\n\n const response = await (options.fetch ?? fetch)(\n 'https://api.postmarkapp.com/email',\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n 'X-Postmark-Server-Token': options.postmarkToken,\n },\n body: JSON.stringify({\n From: data.from ?? 'VRPlatform <notifications@vrplatform.app>',\n To: data.to,\n Subject: data.subject,\n ReplyTo: data.replyTo,\n TextBody: data.text,\n HtmlBody: data.html,\n MessageStream: 'outbound',\n Tag: data.tag,\n Metadata: data.metadata,\n Headers: data.headers\n ? Object.entries(data.headers).map(([Name, Value]) => ({\n Name,\n Value,\n }))\n : undefined,\n }),\n }\n );\n\n const providerResponse: unknown = await response.json().catch(() => ({}));\n const postmarkErrorCode =\n parseOptional(postmarkResponseErrorCodeSchema, providerResponse) ??\n parseOptional(\n postmarkHeaderErrorCodeSchema,\n response.headers.get('X-PM-ApiErrorCode')\n );\n\n if (\n !response.ok ||\n (postmarkErrorCode !== undefined && postmarkErrorCode !== 0)\n ) {\n const inactiveRecipient =\n response.status === 422 && postmarkErrorCode === 406;\n const details: EmailDeliveryFailureDetails = {\n provider: 'postmark',\n providerCategory: inactiveRecipient ? 'inactiveRecipient' : 'rejected',\n httpStatus: response.status,\n ...(postmarkErrorCode !== undefined ? { postmarkErrorCode } : {}),\n };\n\n if (inactiveRecipient) {\n throw new EmailDeliveryError(\n 'Postmark recipient is inactive',\n details,\n true,\n false\n );\n }\n\n throw new EmailDeliveryError(\n 'Postmark rejected the email',\n details,\n false,\n response.status === 429 || response.status >= 500\n );\n }\n\n const result = postmarkSuccessResponseSchema.safeParse(providerResponse);\n if (!result.success) {\n throw new EmailDeliveryError('Postmark returned an invalid response', {\n provider: 'postmark',\n providerCategory: 'rejected',\n httpStatus: response.status,\n ...(postmarkErrorCode !== undefined ? { postmarkErrorCode } : {}),\n });\n }\n\n return {\n httpStatus: response.status,\n messageId: result.data.MessageID,\n submittedAt: result.data.SubmittedAt,\n };\n}\n\nexport async function settleEmailDeliveries(deliveries: Promise<unknown>[]) {\n const failures = (await Promise.allSettled(deliveries)).filter(\n (result) => result.status === 'rejected'\n );\n if (failures.length === 0) return;\n\n const unexpectedDeliveryError = failures.find(\n (failure) =>\n failure.reason instanceof EmailDeliveryError && !failure.reason.expected\n )?.reason;\n const allExpected = failures.every(\n (failure) =>\n failure.reason instanceof EmailDeliveryError && failure.reason.expected\n );\n const firstDeliveryError = failures.find(\n (failure) => failure.reason instanceof EmailDeliveryError\n )?.reason;\n const failureDetails = failures.flatMap((failure) => {\n if (\n !(failure.reason instanceof EmailDeliveryError) ||\n !failure.reason.details ||\n !('provider' in failure.reason.details)\n ) {\n return [];\n }\n const { httpStatus, postmarkErrorCode, provider, providerCategory } =\n failure.reason.details;\n return [\n {\n httpStatus,\n provider,\n providerCategory,\n ...(postmarkErrorCode !== undefined ? { postmarkErrorCode } : {}),\n },\n ];\n });\n\n const details: EmailDeliveryErrorDetails = {\n failureCount: failures.length,\n failures: failureDetails,\n };\n if (unexpectedDeliveryError instanceof EmailDeliveryError) {\n if (unexpectedDeliveryError.message === 'Postmark is not configured') {\n throw new EmailDeliveryError(\n 'Postmark is not configured',\n details,\n false,\n false\n );\n }\n throw new EmailDeliveryError(\n 'Postmark rejected the email',\n details,\n false,\n unexpectedDeliveryError.retryable\n );\n }\n if (allExpected && firstDeliveryError instanceof EmailDeliveryError) {\n throw new EmailDeliveryError(\n 'Postmark recipient is inactive',\n details,\n true,\n false\n );\n }\n throw new EmailDeliveryError('Email delivery failed', details);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"send-email.js","sourceRoot":"src/","sources":["utils/send-email.ts"],"names":[],"mappings":"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","sourcesContent":["import { day } from '@vrplatform/utils';\nimport * as z from 'zod';\n\nconst postmarkErrorCodeSchema = z.number().int().nonnegative();\nconst postmarkResponseErrorCodeSchema = z\n .object({ ErrorCode: postmarkErrorCodeSchema })\n .transform(({ ErrorCode }) => ErrorCode);\nconst postmarkHeaderErrorCodeSchema = z\n .string()\n .regex(/^\\d+$/)\n .transform(Number)\n .pipe(postmarkErrorCodeSchema);\nconst postmarkSuccessResponseSchema = z.object({\n ErrorCode: z.literal(0),\n MessageID: z.string().min(1),\n SubmittedAt: z.string().refine((value) => day(value).isValid()),\n});\n\nfunction parseOptional<T>(schema: z.ZodType<T>, value: unknown) {\n const result = schema.safeParse(value);\n if (result.success) return result.data;\n}\n\ntype EmailDeliveryFailureDetails = {\n httpStatus: number;\n postmarkErrorCode?: number;\n provider: 'postmark';\n providerCategory: 'inactiveRecipient' | 'rejected';\n};\n\ntype EmailDeliveryErrorDetails =\n | EmailDeliveryFailureDetails\n | {\n failureCount: number;\n failures: EmailDeliveryFailureDetails[];\n };\n\nexport class EmailDeliveryError extends Error {\n constructor(\n message: string,\n public readonly details?: EmailDeliveryErrorDetails,\n public readonly expected = false,\n public readonly retryable = true\n ) {\n super(message);\n this.name = 'EmailDeliveryError';\n }\n}\n\n/**\n * Formats a verified sender address with a display name for the From header.\n * Registrations store bare addresses; recipients should still see the company\n * name the way they see `VRPlatform <notifications@vrplatform.app>`.\n */\nexport function formatSenderIdentity(\n name: string | null | undefined,\n address: string\n) {\n const display = (name ?? '').replace(/[\\r\\n\"<>]/g, '').trim();\n return display ? `\"${display}\" <${address}>` : address;\n}\n\nexport async function sendEmail(\n data: {\n to: string;\n subject: string;\n text: string;\n html: string;\n replyTo?: string;\n tag?: string;\n metadata?: Record<string, string>;\n from?: string;\n headers?: Record<string, string>;\n },\n options: {\n fetch?: (\n input: string | URL | Request,\n init?: RequestInit\n ) => Promise<Response>;\n postmarkToken?: string;\n }\n) {\n if (!options.postmarkToken) {\n throw new EmailDeliveryError(\n 'Postmark is not configured',\n undefined,\n false,\n false\n );\n }\n\n const response = await (options.fetch ?? fetch)(\n 'https://api.postmarkapp.com/email',\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n 'X-Postmark-Server-Token': options.postmarkToken,\n },\n body: JSON.stringify({\n From: data.from ?? 'VRPlatform <notifications@vrplatform.app>',\n To: data.to,\n Subject: data.subject,\n ReplyTo: data.replyTo,\n TextBody: data.text,\n HtmlBody: data.html,\n MessageStream: 'outbound',\n Tag: data.tag,\n Metadata: data.metadata,\n Headers: data.headers\n ? Object.entries(data.headers).map(([Name, Value]) => ({\n Name,\n Value,\n }))\n : undefined,\n }),\n }\n );\n\n const providerResponse: unknown = await response.json().catch(() => ({}));\n const postmarkErrorCode =\n parseOptional(postmarkResponseErrorCodeSchema, providerResponse) ??\n parseOptional(\n postmarkHeaderErrorCodeSchema,\n response.headers.get('X-PM-ApiErrorCode')\n );\n\n if (\n !response.ok ||\n (postmarkErrorCode !== undefined && postmarkErrorCode !== 0)\n ) {\n const inactiveRecipient =\n response.status === 422 && postmarkErrorCode === 406;\n const details: EmailDeliveryFailureDetails = {\n provider: 'postmark',\n providerCategory: inactiveRecipient ? 'inactiveRecipient' : 'rejected',\n httpStatus: response.status,\n ...(postmarkErrorCode !== undefined ? { postmarkErrorCode } : {}),\n };\n\n if (inactiveRecipient) {\n throw new EmailDeliveryError(\n 'Postmark recipient is inactive',\n details,\n true,\n false\n );\n }\n\n throw new EmailDeliveryError(\n 'Postmark rejected the email',\n details,\n false,\n response.status === 429 || response.status >= 500\n );\n }\n\n const result = postmarkSuccessResponseSchema.safeParse(providerResponse);\n if (!result.success) {\n throw new EmailDeliveryError('Postmark returned an invalid response', {\n provider: 'postmark',\n providerCategory: 'rejected',\n httpStatus: response.status,\n ...(postmarkErrorCode !== undefined ? { postmarkErrorCode } : {}),\n });\n }\n\n return {\n httpStatus: response.status,\n messageId: result.data.MessageID,\n submittedAt: result.data.SubmittedAt,\n };\n}\n\nexport async function settleEmailDeliveries(deliveries: Promise<unknown>[]) {\n const failures = (await Promise.allSettled(deliveries)).filter(\n (result) => result.status === 'rejected'\n );\n if (failures.length === 0) return;\n\n const unexpectedDeliveryError = failures.find(\n (failure) =>\n failure.reason instanceof EmailDeliveryError && !failure.reason.expected\n )?.reason;\n const allExpected = failures.every(\n (failure) =>\n failure.reason instanceof EmailDeliveryError && failure.reason.expected\n );\n const firstDeliveryError = failures.find(\n (failure) => failure.reason instanceof EmailDeliveryError\n )?.reason;\n const failureDetails = failures.flatMap((failure) => {\n if (\n !(failure.reason instanceof EmailDeliveryError) ||\n !failure.reason.details ||\n !('provider' in failure.reason.details)\n ) {\n return [];\n }\n const { httpStatus, postmarkErrorCode, provider, providerCategory } =\n failure.reason.details;\n return [\n {\n httpStatus,\n provider,\n providerCategory,\n ...(postmarkErrorCode !== undefined ? { postmarkErrorCode } : {}),\n },\n ];\n });\n\n const details: EmailDeliveryErrorDetails = {\n failureCount: failures.length,\n failures: failureDetails,\n };\n if (unexpectedDeliveryError instanceof EmailDeliveryError) {\n if (unexpectedDeliveryError.message === 'Postmark is not configured') {\n throw new EmailDeliveryError(\n 'Postmark is not configured',\n details,\n false,\n false\n );\n }\n throw new EmailDeliveryError(\n 'Postmark rejected the email',\n details,\n false,\n unexpectedDeliveryError.retryable\n );\n }\n if (allExpected && firstDeliveryError instanceof EmailDeliveryError) {\n throw new EmailDeliveryError(\n 'Postmark recipient is inactive',\n details,\n true,\n false\n );\n }\n throw new EmailDeliveryError('Email delivery failed', details);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find-postmark-email.js","sourceRoot":"src/","sources":["utils/find-postmark-email.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACxC,OAAO,
|
|
1
|
+
{"version":3,"file":"find-postmark-email.js","sourceRoot":"src/","sources":["utils/find-postmark-email.ts"],"names":[],"mappings":"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","sourcesContent":["import { day } from '@vrplatform/utils';\nimport * as z from 'zod';\nimport { EmailDeliveryError } from './send-email';\n\nconst postmarkMessagesResponseSchema = z.object({\n TotalCount: z.number().int().nonnegative(),\n Messages: z.array(\n z.object({\n MessageID: z.string().min(1),\n ReceivedAt: z.string().refine((value) => day(value).isValid()),\n Metadata: z.object({ deliveryId: z.string() }).passthrough(),\n })\n ),\n});\n\nexport async function findPostmarkEmail(\n deliveryId: string,\n options: {\n fetch?: (\n input: string | URL | Request,\n init?: RequestInit\n ) => Promise<Response>;\n postmarkToken?: string;\n }\n) {\n if (!options.postmarkToken) {\n throw new EmailDeliveryError(\n 'Postmark is not configured',\n undefined,\n false,\n false\n );\n }\n\n const url = new URL('https://api.postmarkapp.com/messages/outbound');\n url.searchParams.set('count', '1');\n url.searchParams.set('offset', '0');\n url.searchParams.set('messagestream', 'outbound');\n url.searchParams.set('metadata_deliveryId', deliveryId);\n const response = await (options.fetch ?? fetch)(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n 'X-Postmark-Server-Token': options.postmarkToken,\n },\n });\n const providerResponse: unknown = await response.json().catch(() => ({}));\n\n if (!response.ok) {\n throw new EmailDeliveryError(\n 'Postmark message lookup failed',\n {\n provider: 'postmark',\n providerCategory: 'rejected',\n httpStatus: response.status,\n },\n false,\n response.status === 429 || response.status >= 500\n );\n }\n\n const result = postmarkMessagesResponseSchema.safeParse(providerResponse);\n if (!result.success) {\n throw new EmailDeliveryError('Postmark returned an invalid response');\n }\n if (result.data.TotalCount === 0) return undefined;\n\n const message = result.data.Messages.find(\n ({ Metadata }) => Metadata.deliveryId === deliveryId\n );\n if (!message) {\n throw new EmailDeliveryError('Postmark returned an invalid response');\n }\n\n return {\n httpStatus: response.status,\n messageId: message.MessageID,\n submittedAt: message.ReceivedAt,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-email.js","sourceRoot":"src/","sources":["utils/send-email.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACxC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;AAC/D,MAAM,+BAA+B,GAAG,CAAC;KACtC,MAAM,CAAC,EAAE,SAAS,EAAE,uBAAuB,EAAE,CAAC;KAC9C,SAAS,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC;AAC3C,MAAM,6BAA6B,GAAG,CAAC;KACpC,MAAM,EAAE;KACR,KAAK,CAAC,OAAO,CAAC;KACd,SAAS,CAAC,MAAM,CAAC;KACjB,IAAI,CAAC,uBAAuB,CAAC,CAAC;AACjC,MAAM,6BAA6B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACvB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;CAChE,CAAC,CAAC;AAEH,SAAS,aAAa,CAAI,MAAoB,EAAE,KAAc;IAC5D,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC;AACzC,CAAC;AAgBD,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAGzB;IACA;IACA;IAJlB,YACE,OAAe,EACC,OAAmC,EACnC,WAAW,KAAK,EAChB,YAAY,IAAI;QAEhC,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,YAAO,GAAP,OAAO,CAA4B;QACnC,aAAQ,GAAR,QAAQ,CAAQ;QAChB,cAAS,GAAT,SAAS,CAAO;QAGhC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAA+B,EAC/B,OAAe;IAEf,MAAM,OAAO,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9D,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;AACzD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAUC,EACD,OAMC;IAED,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;QAC3B,MAAM,IAAI,kBAAkB,CAC1B,4BAA4B,EAC5B,SAAS,EACT,KAAK,EACL,KAAK,CACN,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,CAC7C,mCAAmC,EACnC;QACE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,MAAM,EAAE,kBAAkB;YAC1B,cAAc,EAAE,kBAAkB;YAClC,yBAAyB,EAAE,OAAO,CAAC,aAAa;SACjD;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,2CAA2C;YAC9D,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,aAAa,EAAE,UAAU;YACzB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACnB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;oBACnD,IAAI;oBACJ,KAAK;iBACN,CAAC,CAAC;gBACL,CAAC,CAAC,SAAS;SACd,CAAC;KACH,CACF,CAAC;IAEF,MAAM,gBAAgB,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1E,MAAM,iBAAiB,GACrB,aAAa,CAAC,+BAA+B,EAAE,gBAAgB,CAAC;QAChE,aAAa,CACX,6BAA6B,EAC7B,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAC1C,CAAC;IAEJ,IACE,CAAC,QAAQ,CAAC,EAAE;QACZ,CAAC,iBAAiB,KAAK,SAAS,IAAI,iBAAiB,KAAK,CAAC,CAAC,EAC5D,CAAC;QACD,MAAM,iBAAiB,GACrB,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,iBAAiB,KAAK,GAAG,CAAC;QACvD,MAAM,OAAO,GAAgC;YAC3C,QAAQ,EAAE,UAAU;YACpB,gBAAgB,EAAE,iBAAiB,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,UAAU;YACtE,UAAU,EAAE,QAAQ,CAAC,MAAM;YAC3B,GAAG,CAAC,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,CAAC;QAEF,IAAI,iBAAiB,EAAE,CAAC;YACtB,MAAM,IAAI,kBAAkB,CAC1B,gCAAgC,EAChC,OAAO,EACP,IAAI,EACJ,KAAK,CACN,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,kBAAkB,CAC1B,6BAA6B,EAC7B,OAAO,EACP,KAAK,EACL,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,CAClD,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,6BAA6B,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;IACzE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,kBAAkB,CAAC,uCAAuC,EAAE;YACpE,QAAQ,EAAE,UAAU;YACpB,gBAAgB,EAAE,UAAU;YAC5B,UAAU,EAAE,QAAQ,CAAC,MAAM;YAC3B,GAAG,CAAC,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,UAAU,EAAE,QAAQ,CAAC,MAAM;QAC3B,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS;QAChC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW;KACrC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,UAA8B;IACxE,MAAM,QAAQ,GAAG,CAAC,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAC5D,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,UAAU,CACzC,CAAC;IACF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAElC,MAAM,uBAAuB,GAAG,QAAQ,CAAC,IAAI,CAC3C,CAAC,OAAO,EAAE,EAAE,CACV,OAAO,CAAC,MAAM,YAAY,kBAAkB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAC3E,EAAE,MAAM,CAAC;IACV,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAChC,CAAC,OAAO,EAAE,EAAE,CACV,OAAO,CAAC,MAAM,YAAY,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,QAAQ,CAC1E,CAAC;IACF,MAAM,kBAAkB,GAAG,QAAQ,CAAC,IAAI,CACtC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,YAAY,kBAAkB,CAC1D,EAAE,MAAM,CAAC;IACV,MAAM,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAClD,IACE,CAAC,CAAC,OAAO,CAAC,MAAM,YAAY,kBAAkB,CAAC;YAC/C,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO;YACvB,CAAC,CAAC,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EACvC,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,EAAE,UAAU,EAAE,iBAAiB,EAAE,QAAQ,EAAE,gBAAgB,EAAE,GACjE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;QACzB,OAAO;YACL;gBACE,UAAU;gBACV,QAAQ;gBACR,gBAAgB;gBAChB,GAAG,CAAC,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAClE;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAA8B;QACzC,YAAY,EAAE,QAAQ,CAAC,MAAM;QAC7B,QAAQ,EAAE,cAAc;KACzB,CAAC;IACF,IAAI,uBAAuB,YAAY,kBAAkB,EAAE,CAAC;QAC1D,IAAI,uBAAuB,CAAC,OAAO,KAAK,4BAA4B,EAAE,CAAC;YACrE,MAAM,IAAI,kBAAkB,CAC1B,4BAA4B,EAC5B,OAAO,EACP,KAAK,EACL,KAAK,CACN,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,kBAAkB,CAC1B,6BAA6B,EAC7B,OAAO,EACP,KAAK,EACL,uBAAuB,CAAC,SAAS,CAClC,CAAC;IACJ,CAAC;IACD,IAAI,WAAW,IAAI,kBAAkB,YAAY,kBAAkB,EAAE,CAAC;QACpE,MAAM,IAAI,kBAAkB,CAC1B,gCAAgC,EAChC,OAAO,EACP,IAAI,EACJ,KAAK,CACN,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;AACjE,CAAC","sourcesContent":["import { day } from '@vrplatform/utils';\nimport { z } from 'zod';\n\nconst postmarkErrorCodeSchema = z.number().int().nonnegative();\nconst postmarkResponseErrorCodeSchema = z\n .object({ ErrorCode: postmarkErrorCodeSchema })\n .transform(({ ErrorCode }) => ErrorCode);\nconst postmarkHeaderErrorCodeSchema = z\n .string()\n .regex(/^\\d+$/)\n .transform(Number)\n .pipe(postmarkErrorCodeSchema);\nconst postmarkSuccessResponseSchema = z.object({\n ErrorCode: z.literal(0),\n MessageID: z.string().min(1),\n SubmittedAt: z.string().refine((value) => day(value).isValid()),\n});\n\nfunction parseOptional<T>(schema: z.ZodType<T>, value: unknown) {\n const result = schema.safeParse(value);\n if (result.success) return result.data;\n}\n\ntype EmailDeliveryFailureDetails = {\n httpStatus: number;\n postmarkErrorCode?: number;\n provider: 'postmark';\n providerCategory: 'inactiveRecipient' | 'rejected';\n};\n\ntype EmailDeliveryErrorDetails =\n | EmailDeliveryFailureDetails\n | {\n failureCount: number;\n failures: EmailDeliveryFailureDetails[];\n };\n\nexport class EmailDeliveryError extends Error {\n constructor(\n message: string,\n public readonly details?: EmailDeliveryErrorDetails,\n public readonly expected = false,\n public readonly retryable = true\n ) {\n super(message);\n this.name = 'EmailDeliveryError';\n }\n}\n\n/**\n * Formats a verified sender address with a display name for the From header.\n * Registrations store bare addresses; recipients should still see the company\n * name the way they see `VRPlatform <notifications@vrplatform.app>`.\n */\nexport function formatSenderIdentity(\n name: string | null | undefined,\n address: string\n) {\n const display = (name ?? '').replace(/[\\r\\n\"<>]/g, '').trim();\n return display ? `\"${display}\" <${address}>` : address;\n}\n\nexport async function sendEmail(\n data: {\n to: string;\n subject: string;\n text: string;\n html: string;\n replyTo?: string;\n tag?: string;\n metadata?: Record<string, string>;\n from?: string;\n headers?: Record<string, string>;\n },\n options: {\n fetch?: (\n input: string | URL | Request,\n init?: RequestInit\n ) => Promise<Response>;\n postmarkToken?: string;\n }\n) {\n if (!options.postmarkToken) {\n throw new EmailDeliveryError(\n 'Postmark is not configured',\n undefined,\n false,\n false\n );\n }\n\n const response = await (options.fetch ?? fetch)(\n 'https://api.postmarkapp.com/email',\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n 'X-Postmark-Server-Token': options.postmarkToken,\n },\n body: JSON.stringify({\n From: data.from ?? 'VRPlatform <notifications@vrplatform.app>',\n To: data.to,\n Subject: data.subject,\n ReplyTo: data.replyTo,\n TextBody: data.text,\n HtmlBody: data.html,\n MessageStream: 'outbound',\n Tag: data.tag,\n Metadata: data.metadata,\n Headers: data.headers\n ? Object.entries(data.headers).map(([Name, Value]) => ({\n Name,\n Value,\n }))\n : undefined,\n }),\n }\n );\n\n const providerResponse: unknown = await response.json().catch(() => ({}));\n const postmarkErrorCode =\n parseOptional(postmarkResponseErrorCodeSchema, providerResponse) ??\n parseOptional(\n postmarkHeaderErrorCodeSchema,\n response.headers.get('X-PM-ApiErrorCode')\n );\n\n if (\n !response.ok ||\n (postmarkErrorCode !== undefined && postmarkErrorCode !== 0)\n ) {\n const inactiveRecipient =\n response.status === 422 && postmarkErrorCode === 406;\n const details: EmailDeliveryFailureDetails = {\n provider: 'postmark',\n providerCategory: inactiveRecipient ? 'inactiveRecipient' : 'rejected',\n httpStatus: response.status,\n ...(postmarkErrorCode !== undefined ? { postmarkErrorCode } : {}),\n };\n\n if (inactiveRecipient) {\n throw new EmailDeliveryError(\n 'Postmark recipient is inactive',\n details,\n true,\n false\n );\n }\n\n throw new EmailDeliveryError(\n 'Postmark rejected the email',\n details,\n false,\n response.status === 429 || response.status >= 500\n );\n }\n\n const result = postmarkSuccessResponseSchema.safeParse(providerResponse);\n if (!result.success) {\n throw new EmailDeliveryError('Postmark returned an invalid response', {\n provider: 'postmark',\n providerCategory: 'rejected',\n httpStatus: response.status,\n ...(postmarkErrorCode !== undefined ? { postmarkErrorCode } : {}),\n });\n }\n\n return {\n httpStatus: response.status,\n messageId: result.data.MessageID,\n submittedAt: result.data.SubmittedAt,\n };\n}\n\nexport async function settleEmailDeliveries(deliveries: Promise<unknown>[]) {\n const failures = (await Promise.allSettled(deliveries)).filter(\n (result) => result.status === 'rejected'\n );\n if (failures.length === 0) return;\n\n const unexpectedDeliveryError = failures.find(\n (failure) =>\n failure.reason instanceof EmailDeliveryError && !failure.reason.expected\n )?.reason;\n const allExpected = failures.every(\n (failure) =>\n failure.reason instanceof EmailDeliveryError && failure.reason.expected\n );\n const firstDeliveryError = failures.find(\n (failure) => failure.reason instanceof EmailDeliveryError\n )?.reason;\n const failureDetails = failures.flatMap((failure) => {\n if (\n !(failure.reason instanceof EmailDeliveryError) ||\n !failure.reason.details ||\n !('provider' in failure.reason.details)\n ) {\n return [];\n }\n const { httpStatus, postmarkErrorCode, provider, providerCategory } =\n failure.reason.details;\n return [\n {\n httpStatus,\n provider,\n providerCategory,\n ...(postmarkErrorCode !== undefined ? { postmarkErrorCode } : {}),\n },\n ];\n });\n\n const details: EmailDeliveryErrorDetails = {\n failureCount: failures.length,\n failures: failureDetails,\n };\n if (unexpectedDeliveryError instanceof EmailDeliveryError) {\n if (unexpectedDeliveryError.message === 'Postmark is not configured') {\n throw new EmailDeliveryError(\n 'Postmark is not configured',\n details,\n false,\n false\n );\n }\n throw new EmailDeliveryError(\n 'Postmark rejected the email',\n details,\n false,\n unexpectedDeliveryError.retryable\n );\n }\n if (allExpected && firstDeliveryError instanceof EmailDeliveryError) {\n throw new EmailDeliveryError(\n 'Postmark recipient is inactive',\n details,\n true,\n false\n );\n }\n throw new EmailDeliveryError('Email delivery failed', details);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"send-email.js","sourceRoot":"src/","sources":["utils/send-email.ts"],"names":[],"mappings":"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","sourcesContent":["import { day } from '@vrplatform/utils';\nimport * as z from 'zod';\n\nconst postmarkErrorCodeSchema = z.number().int().nonnegative();\nconst postmarkResponseErrorCodeSchema = z\n .object({ ErrorCode: postmarkErrorCodeSchema })\n .transform(({ ErrorCode }) => ErrorCode);\nconst postmarkHeaderErrorCodeSchema = z\n .string()\n .regex(/^\\d+$/)\n .transform(Number)\n .pipe(postmarkErrorCodeSchema);\nconst postmarkSuccessResponseSchema = z.object({\n ErrorCode: z.literal(0),\n MessageID: z.string().min(1),\n SubmittedAt: z.string().refine((value) => day(value).isValid()),\n});\n\nfunction parseOptional<T>(schema: z.ZodType<T>, value: unknown) {\n const result = schema.safeParse(value);\n if (result.success) return result.data;\n}\n\ntype EmailDeliveryFailureDetails = {\n httpStatus: number;\n postmarkErrorCode?: number;\n provider: 'postmark';\n providerCategory: 'inactiveRecipient' | 'rejected';\n};\n\ntype EmailDeliveryErrorDetails =\n | EmailDeliveryFailureDetails\n | {\n failureCount: number;\n failures: EmailDeliveryFailureDetails[];\n };\n\nexport class EmailDeliveryError extends Error {\n constructor(\n message: string,\n public readonly details?: EmailDeliveryErrorDetails,\n public readonly expected = false,\n public readonly retryable = true\n ) {\n super(message);\n this.name = 'EmailDeliveryError';\n }\n}\n\n/**\n * Formats a verified sender address with a display name for the From header.\n * Registrations store bare addresses; recipients should still see the company\n * name the way they see `VRPlatform <notifications@vrplatform.app>`.\n */\nexport function formatSenderIdentity(\n name: string | null | undefined,\n address: string\n) {\n const display = (name ?? '').replace(/[\\r\\n\"<>]/g, '').trim();\n return display ? `\"${display}\" <${address}>` : address;\n}\n\nexport async function sendEmail(\n data: {\n to: string;\n subject: string;\n text: string;\n html: string;\n replyTo?: string;\n tag?: string;\n metadata?: Record<string, string>;\n from?: string;\n headers?: Record<string, string>;\n },\n options: {\n fetch?: (\n input: string | URL | Request,\n init?: RequestInit\n ) => Promise<Response>;\n postmarkToken?: string;\n }\n) {\n if (!options.postmarkToken) {\n throw new EmailDeliveryError(\n 'Postmark is not configured',\n undefined,\n false,\n false\n );\n }\n\n const response = await (options.fetch ?? fetch)(\n 'https://api.postmarkapp.com/email',\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n 'X-Postmark-Server-Token': options.postmarkToken,\n },\n body: JSON.stringify({\n From: data.from ?? 'VRPlatform <notifications@vrplatform.app>',\n To: data.to,\n Subject: data.subject,\n ReplyTo: data.replyTo,\n TextBody: data.text,\n HtmlBody: data.html,\n MessageStream: 'outbound',\n Tag: data.tag,\n Metadata: data.metadata,\n Headers: data.headers\n ? Object.entries(data.headers).map(([Name, Value]) => ({\n Name,\n Value,\n }))\n : undefined,\n }),\n }\n );\n\n const providerResponse: unknown = await response.json().catch(() => ({}));\n const postmarkErrorCode =\n parseOptional(postmarkResponseErrorCodeSchema, providerResponse) ??\n parseOptional(\n postmarkHeaderErrorCodeSchema,\n response.headers.get('X-PM-ApiErrorCode')\n );\n\n if (\n !response.ok ||\n (postmarkErrorCode !== undefined && postmarkErrorCode !== 0)\n ) {\n const inactiveRecipient =\n response.status === 422 && postmarkErrorCode === 406;\n const details: EmailDeliveryFailureDetails = {\n provider: 'postmark',\n providerCategory: inactiveRecipient ? 'inactiveRecipient' : 'rejected',\n httpStatus: response.status,\n ...(postmarkErrorCode !== undefined ? { postmarkErrorCode } : {}),\n };\n\n if (inactiveRecipient) {\n throw new EmailDeliveryError(\n 'Postmark recipient is inactive',\n details,\n true,\n false\n );\n }\n\n throw new EmailDeliveryError(\n 'Postmark rejected the email',\n details,\n false,\n response.status === 429 || response.status >= 500\n );\n }\n\n const result = postmarkSuccessResponseSchema.safeParse(providerResponse);\n if (!result.success) {\n throw new EmailDeliveryError('Postmark returned an invalid response', {\n provider: 'postmark',\n providerCategory: 'rejected',\n httpStatus: response.status,\n ...(postmarkErrorCode !== undefined ? { postmarkErrorCode } : {}),\n });\n }\n\n return {\n httpStatus: response.status,\n messageId: result.data.MessageID,\n submittedAt: result.data.SubmittedAt,\n };\n}\n\nexport async function settleEmailDeliveries(deliveries: Promise<unknown>[]) {\n const failures = (await Promise.allSettled(deliveries)).filter(\n (result) => result.status === 'rejected'\n );\n if (failures.length === 0) return;\n\n const unexpectedDeliveryError = failures.find(\n (failure) =>\n failure.reason instanceof EmailDeliveryError && !failure.reason.expected\n )?.reason;\n const allExpected = failures.every(\n (failure) =>\n failure.reason instanceof EmailDeliveryError && failure.reason.expected\n );\n const firstDeliveryError = failures.find(\n (failure) => failure.reason instanceof EmailDeliveryError\n )?.reason;\n const failureDetails = failures.flatMap((failure) => {\n if (\n !(failure.reason instanceof EmailDeliveryError) ||\n !failure.reason.details ||\n !('provider' in failure.reason.details)\n ) {\n return [];\n }\n const { httpStatus, postmarkErrorCode, provider, providerCategory } =\n failure.reason.details;\n return [\n {\n httpStatus,\n provider,\n providerCategory,\n ...(postmarkErrorCode !== undefined ? { postmarkErrorCode } : {}),\n },\n ];\n });\n\n const details: EmailDeliveryErrorDetails = {\n failureCount: failures.length,\n failures: failureDetails,\n };\n if (unexpectedDeliveryError instanceof EmailDeliveryError) {\n if (unexpectedDeliveryError.message === 'Postmark is not configured') {\n throw new EmailDeliveryError(\n 'Postmark is not configured',\n details,\n false,\n false\n );\n }\n throw new EmailDeliveryError(\n 'Postmark rejected the email',\n details,\n false,\n unexpectedDeliveryError.retryable\n );\n }\n if (allExpected && firstDeliveryError instanceof EmailDeliveryError) {\n throw new EmailDeliveryError(\n 'Postmark recipient is inactive',\n details,\n true,\n false\n );\n }\n throw new EmailDeliveryError('Email delivery failed', details);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vrplatform/email",
|
|
3
|
-
"version": "1.0.2-
|
|
3
|
+
"version": "1.0.2-40036090988",
|
|
4
4
|
"description": "VRTrust transactional email templates and Postmark transport",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"main": "build/main/index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@react-email/preview": "0.0.10",
|
|
32
32
|
"@react-email/render": "0.0.17",
|
|
33
33
|
"@react-email/text": "0.0.9",
|
|
34
|
-
"zod": "^4.
|
|
34
|
+
"zod": "^4.6.6"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@vrplatform/utils": "1.0.21",
|
package/src/utils/send-email.ts
CHANGED