@brilab-mailer/contracts 0.2.1 → 0.2.2
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/cjs/index.js +23 -0
- package/cjs/lib/mailer-config-properties.js +6 -0
- package/cjs/lib/mailer-contracts.js +10 -0
- package/cjs/lib/mailer-messages.js +176 -0
- package/cjs/lib/mailer-provider.js +2 -0
- package/cjs/lib/mailer-send-error.js +57 -0
- package/cjs/lib/mailer-send-result.js +2 -0
- package/cjs/lib/mailer-templates.js +2 -0
- package/cjs/package.json +3 -0
- package/package.json +3 -3
package/cjs/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./lib/mailer-contracts.js"), exports);
|
|
18
|
+
__exportStar(require("./lib/mailer-provider.js"), exports);
|
|
19
|
+
__exportStar(require("./lib/mailer-send-result.js"), exports);
|
|
20
|
+
__exportStar(require("./lib/mailer-send-error.js"), exports);
|
|
21
|
+
__exportStar(require("./lib/mailer-templates.js"), exports);
|
|
22
|
+
__exportStar(require("./lib/mailer-config-properties.js"), exports);
|
|
23
|
+
__exportStar(require("./lib/mailer-messages.js"), exports);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAILER_RETRY_BACKOFF_MS = exports.MAILER_RETRY_ATTEMPTS = void 0;
|
|
4
|
+
// MAILER
|
|
5
|
+
exports.MAILER_RETRY_ATTEMPTS = 'MAILER_RETRY_ATTEMPTS';
|
|
6
|
+
exports.MAILER_RETRY_BACKOFF_MS = 'MAILER_RETRY_BACKOFF_MS';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAILER_TEMPLATE_ENGINE_OPTIONS = exports.MAILER_TEMPLATE_ENGINE = exports.MAILER_PROVIDER_NATIVE_CLIENT = exports.MAILER_PROVIDER_OPTIONS = exports.MAILER_PROVIDER = exports.MAILER_OPTIONS = exports.MAILER_ASYNC = void 0;
|
|
4
|
+
exports.MAILER_ASYNC = Symbol('MAILER_ASYNC');
|
|
5
|
+
exports.MAILER_OPTIONS = Symbol('MAILER_OPTIONS');
|
|
6
|
+
exports.MAILER_PROVIDER = Symbol('MAILER_PROVIDER');
|
|
7
|
+
exports.MAILER_PROVIDER_OPTIONS = Symbol('MAILER_PROVIDER_OPTIONS');
|
|
8
|
+
exports.MAILER_PROVIDER_NATIVE_CLIENT = Symbol('MAILER_PROVIDER_NATIVE_CLIENT');
|
|
9
|
+
exports.MAILER_TEMPLATE_ENGINE = Symbol('MAILER_TEMPLATE_ENGINE');
|
|
10
|
+
exports.MAILER_TEMPLATE_ENGINE_OPTIONS = Symbol('MAILER_TEMPLATE_ENGINE_OPTIONS');
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatAddressList = exports.formatAddress = exports.resolveBodies = exports.normalizeDataBodyString = exports.MailerMessagesBuilder = void 0;
|
|
4
|
+
class MailerMessagesBuilder {
|
|
5
|
+
draft = {
|
|
6
|
+
to: [],
|
|
7
|
+
cc: [],
|
|
8
|
+
bcc: [],
|
|
9
|
+
meta: {},
|
|
10
|
+
headers: {},
|
|
11
|
+
attachments: [],
|
|
12
|
+
tags: {},
|
|
13
|
+
};
|
|
14
|
+
generateDraft() {
|
|
15
|
+
return this.draft;
|
|
16
|
+
}
|
|
17
|
+
prepare() {
|
|
18
|
+
const draft = this.generateDraft();
|
|
19
|
+
if (!draft.from?.email)
|
|
20
|
+
throw new Error('[Mailer] from.email is required');
|
|
21
|
+
if (!draft.to?.length)
|
|
22
|
+
throw new Error('[Mailer] to[] is required');
|
|
23
|
+
if (!draft.subject)
|
|
24
|
+
throw new Error('[Mailer] subject is required');
|
|
25
|
+
const text = typeof draft.text === 'string' ? draft.text : draft.text?.toString();
|
|
26
|
+
const html = typeof draft.html === 'string' ? draft.html : draft.html?.toString();
|
|
27
|
+
if (!text && !html)
|
|
28
|
+
throw new Error('[Mailer] message has no content (text or html)');
|
|
29
|
+
const content = text && html
|
|
30
|
+
? { kind: 'mixed', text, html }
|
|
31
|
+
: html
|
|
32
|
+
? { kind: 'html', html }
|
|
33
|
+
: { kind: 'text', text: text };
|
|
34
|
+
return {
|
|
35
|
+
...draft,
|
|
36
|
+
from: draft.from,
|
|
37
|
+
to: draft.to,
|
|
38
|
+
subject: draft.subject,
|
|
39
|
+
content,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
setFrom(fromOrEmail, name) {
|
|
43
|
+
const fromEmail = typeof fromOrEmail === 'string' ? fromOrEmail : fromOrEmail.email;
|
|
44
|
+
const fromName = typeof fromOrEmail === 'string' ? name : fromOrEmail.name;
|
|
45
|
+
this.draft.from = { email: fromEmail, name: fromName };
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
setReplyTo(replyToOrEmail, name) {
|
|
49
|
+
this.draft.replyTo = typeof replyToOrEmail === 'string'
|
|
50
|
+
? { email: replyToOrEmail, name }
|
|
51
|
+
: replyToOrEmail;
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
resolveAddress(input) {
|
|
55
|
+
return typeof input === 'string' ? { email: input } : input;
|
|
56
|
+
}
|
|
57
|
+
toAddressList(input) {
|
|
58
|
+
const arr = Array.isArray(input) ? input : [input];
|
|
59
|
+
return arr.map((i) => this.resolveAddress(i));
|
|
60
|
+
}
|
|
61
|
+
/** Merge new recipients into an existing list, de-duplicating by email. */
|
|
62
|
+
mergeAddresses(prev, next) {
|
|
63
|
+
const result = [...prev];
|
|
64
|
+
for (const addr of next) {
|
|
65
|
+
const idx = result.findIndex((p) => p.email === addr.email);
|
|
66
|
+
if (idx !== -1)
|
|
67
|
+
result[idx] = { ...result[idx], ...addr };
|
|
68
|
+
else
|
|
69
|
+
result.push(addr);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
addTo(input) {
|
|
74
|
+
this.draft.to = this.mergeAddresses(this.draft.to || [], this.toAddressList(input));
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
setTo(input) {
|
|
78
|
+
this.draft.to = this.toAddressList(input);
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
addCc(input) {
|
|
82
|
+
this.draft.cc = this.mergeAddresses(this.draft.cc || [], this.toAddressList(input));
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
setCc(input) {
|
|
86
|
+
this.draft.cc = this.toAddressList(input);
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
addBcc(input) {
|
|
90
|
+
this.draft.bcc = this.mergeAddresses(this.draft.bcc || [], this.toAddressList(input));
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
setBcc(input) {
|
|
94
|
+
this.draft.bcc = this.toAddressList(input);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
setSubject(subject) {
|
|
98
|
+
this.draft.subject = subject;
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
setHeader(name, value) {
|
|
102
|
+
this.draft.headers = { ...(this.draft.headers || {}), [name]: value };
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
setHeaders(headers) {
|
|
106
|
+
this.draft.headers = { ...(this.draft.headers || {}), ...headers };
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
addAttachment(attachment) {
|
|
110
|
+
this.draft.attachments = [...(this.draft.attachments || []), attachment];
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
setTag(name, value) {
|
|
114
|
+
this.draft.tags = { ...(this.draft.tags || {}), [name]: value };
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
setIdempotencyKey(key) {
|
|
118
|
+
this.draft.idempotencyKey = key;
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
setText(text) {
|
|
122
|
+
this.draft.text = text;
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
setHtml(html) {
|
|
126
|
+
this.draft.html = html;
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.MailerMessagesBuilder = MailerMessagesBuilder;
|
|
131
|
+
/** Normalizes a string/Buffer body to a plain string. */
|
|
132
|
+
const normalizeDataBodyString = (body) => {
|
|
133
|
+
if (!body)
|
|
134
|
+
return '';
|
|
135
|
+
if (typeof body === 'string')
|
|
136
|
+
return body;
|
|
137
|
+
return body.toString();
|
|
138
|
+
};
|
|
139
|
+
exports.normalizeDataBodyString = normalizeDataBodyString;
|
|
140
|
+
/** Resolves the message content into plain `text`/`html` strings. */
|
|
141
|
+
const resolveBodies = (resolved) => {
|
|
142
|
+
const content = resolved.content;
|
|
143
|
+
switch (content.kind) {
|
|
144
|
+
case 'text':
|
|
145
|
+
return { text: (0, exports.normalizeDataBodyString)(content.text) };
|
|
146
|
+
case 'html':
|
|
147
|
+
return { html: (0, exports.normalizeDataBodyString)(content.html) };
|
|
148
|
+
case 'mixed':
|
|
149
|
+
return {
|
|
150
|
+
text: (0, exports.normalizeDataBodyString)(content.text),
|
|
151
|
+
html: (0, exports.normalizeDataBodyString)(content.html),
|
|
152
|
+
};
|
|
153
|
+
default:
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
exports.resolveBodies = resolveBodies;
|
|
158
|
+
/**
|
|
159
|
+
* Formats an address into an RFC 5322 string, quoting the display name when it
|
|
160
|
+
* contains special characters. Used by providers that take `"Name <email>"`.
|
|
161
|
+
*/
|
|
162
|
+
const formatAddress = (address) => {
|
|
163
|
+
if (!address.name)
|
|
164
|
+
return address.email;
|
|
165
|
+
const needsQuote = /[",:;<>@\[\]\\]/.test(address.name);
|
|
166
|
+
const name = needsQuote ? `"${address.name.replace(/"/g, '\\"')}"` : address.name;
|
|
167
|
+
return `${name} <${address.email}>`;
|
|
168
|
+
};
|
|
169
|
+
exports.formatAddress = formatAddress;
|
|
170
|
+
/** Formats a list of addresses into a comma-separated RFC 5322 string. */
|
|
171
|
+
const formatAddressList = (addresses) => {
|
|
172
|
+
if (!addresses?.length)
|
|
173
|
+
return undefined;
|
|
174
|
+
return addresses.map(exports.formatAddress).join(', ');
|
|
175
|
+
};
|
|
176
|
+
exports.formatAddressList = formatAddressList;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toMailerSendError = exports.MailerSendError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Normalized provider error. Carries a `retryable` flag so the retry layer can
|
|
6
|
+
* skip permanent failures (4xx validation) and only retry transient ones
|
|
7
|
+
* (network, 429, 5xx).
|
|
8
|
+
*/
|
|
9
|
+
class MailerSendError extends Error {
|
|
10
|
+
provider;
|
|
11
|
+
code;
|
|
12
|
+
statusCode;
|
|
13
|
+
retryable;
|
|
14
|
+
cause;
|
|
15
|
+
constructor(params) {
|
|
16
|
+
super(`[${params.provider}] ${params.message}`);
|
|
17
|
+
this.name = 'MailerSendError';
|
|
18
|
+
this.provider = params.provider;
|
|
19
|
+
this.code = params.code;
|
|
20
|
+
this.statusCode = params.statusCode;
|
|
21
|
+
this.cause = params.cause;
|
|
22
|
+
this.retryable = params.retryable ?? deriveRetryable(params.statusCode);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.MailerSendError = MailerSendError;
|
|
26
|
+
/** 429 and 5xx are transient; other classified statuses are permanent. */
|
|
27
|
+
const deriveRetryable = (statusCode) => {
|
|
28
|
+
if (statusCode === undefined)
|
|
29
|
+
return true; // network/unknown → retry
|
|
30
|
+
if (statusCode === 429)
|
|
31
|
+
return true;
|
|
32
|
+
return statusCode >= 500;
|
|
33
|
+
};
|
|
34
|
+
/** Best-effort extraction of a status code from an arbitrary SDK error. */
|
|
35
|
+
const extractStatusCode = (err) => {
|
|
36
|
+
const candidate = err?.statusCode ??
|
|
37
|
+
err?.status ??
|
|
38
|
+
err?.response?.statusCode ??
|
|
39
|
+
err?.response?.status ??
|
|
40
|
+
err?.$metadata?.httpStatusCode;
|
|
41
|
+
return typeof candidate === 'number' ? candidate : undefined;
|
|
42
|
+
};
|
|
43
|
+
/** Wraps any thrown SDK error into a normalized `MailerSendError`. */
|
|
44
|
+
const toMailerSendError = (provider, err) => {
|
|
45
|
+
if (err instanceof MailerSendError)
|
|
46
|
+
return err;
|
|
47
|
+
const e = err;
|
|
48
|
+
const message = e?.message ?? String(err);
|
|
49
|
+
return new MailerSendError({
|
|
50
|
+
provider,
|
|
51
|
+
message,
|
|
52
|
+
code: e?.code ?? e?.name,
|
|
53
|
+
statusCode: extractStatusCode(e),
|
|
54
|
+
cause: err,
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
exports.toMailerSendError = toMailerSendError;
|
package/cjs/package.json
ADDED
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brilab-mailer/contracts",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"author": "Bohdan Radchenko <radchenkobs@gmail.com>",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./index.js",
|
|
6
|
+
"main": "./cjs/index.js",
|
|
7
7
|
"module": "./index.js",
|
|
8
8
|
"types": "./index.d.ts",
|
|
9
9
|
"exports": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
".": {
|
|
12
12
|
"types": "./index.d.ts",
|
|
13
13
|
"import": "./index.js",
|
|
14
|
-
"
|
|
14
|
+
"require": "./cjs/index.js"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"files": [
|