@unireq/smtp 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/chunk-UQESMDZP.js +199 -0
- package/dist/chunk-UQESMDZP.js.map +1 -0
- package/dist/index.d.ts +305 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/nodemailer-KYPPEHYG.js +9 -0
- package/dist/nodemailer-KYPPEHYG.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Olivier Orabona
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// src/connectors/nodemailer.ts
|
|
2
|
+
function parseSmtpUri(uri) {
|
|
3
|
+
const url = new URL(uri);
|
|
4
|
+
let port = Number(url.port);
|
|
5
|
+
let secure = false;
|
|
6
|
+
if (url.protocol === "smtps:") {
|
|
7
|
+
secure = true;
|
|
8
|
+
port = port || 465;
|
|
9
|
+
} else {
|
|
10
|
+
port = port || 587;
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
host: url.hostname,
|
|
14
|
+
port,
|
|
15
|
+
secure,
|
|
16
|
+
user: decodeURIComponent(url.username),
|
|
17
|
+
pass: decodeURIComponent(url.password)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
var NodemailerConnector = class {
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.options = options;
|
|
23
|
+
}
|
|
24
|
+
transporter = null;
|
|
25
|
+
nodemailer = null;
|
|
26
|
+
capabilities = {
|
|
27
|
+
smtp: true,
|
|
28
|
+
smtps: true,
|
|
29
|
+
starttls: true,
|
|
30
|
+
oauth2: true,
|
|
31
|
+
html: true,
|
|
32
|
+
attachments: true
|
|
33
|
+
};
|
|
34
|
+
async connect(uri) {
|
|
35
|
+
if (!this.nodemailer) {
|
|
36
|
+
try {
|
|
37
|
+
this.nodemailer = await import("nodemailer");
|
|
38
|
+
} catch {
|
|
39
|
+
throw new Error(
|
|
40
|
+
"nodemailer is required for the default SMTP connector. Install it with: npm install nodemailer"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const { host, port, secure, user, pass } = parseSmtpUri(uri);
|
|
45
|
+
const transportOptions = {
|
|
46
|
+
host,
|
|
47
|
+
port,
|
|
48
|
+
secure,
|
|
49
|
+
connectionTimeout: this.options.timeout ?? 3e4,
|
|
50
|
+
greetingTimeout: this.options.timeout ?? 3e4,
|
|
51
|
+
socketTimeout: this.options.timeout ?? 6e4,
|
|
52
|
+
debug: this.options.debug ?? false,
|
|
53
|
+
logger: this.options.debug ?? false
|
|
54
|
+
};
|
|
55
|
+
if (this.options.pool) {
|
|
56
|
+
transportOptions["pool"] = true;
|
|
57
|
+
transportOptions["maxConnections"] = this.options.maxConnections ?? 5;
|
|
58
|
+
}
|
|
59
|
+
if (this.options.tls) {
|
|
60
|
+
transportOptions["tls"] = {
|
|
61
|
+
minVersion: this.options.tls.minVersion,
|
|
62
|
+
rejectUnauthorized: this.options.tls.rejectUnauthorized ?? true
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (this.options.oauth2) {
|
|
66
|
+
transportOptions["auth"] = {
|
|
67
|
+
type: "OAuth2",
|
|
68
|
+
user,
|
|
69
|
+
clientId: this.options.oauth2.clientId,
|
|
70
|
+
clientSecret: this.options.oauth2.clientSecret,
|
|
71
|
+
refreshToken: this.options.oauth2.refreshToken,
|
|
72
|
+
accessToken: this.options.oauth2.accessToken
|
|
73
|
+
};
|
|
74
|
+
} else if (user && pass) {
|
|
75
|
+
transportOptions["auth"] = {
|
|
76
|
+
user,
|
|
77
|
+
pass
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
this.transporter = this.nodemailer.createTransport(transportOptions);
|
|
81
|
+
return {
|
|
82
|
+
connected: true,
|
|
83
|
+
host,
|
|
84
|
+
user,
|
|
85
|
+
secure
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
async request(_session, ctx) {
|
|
89
|
+
if (!this.transporter) {
|
|
90
|
+
return {
|
|
91
|
+
status: 500,
|
|
92
|
+
statusText: "Internal Server Error",
|
|
93
|
+
headers: {},
|
|
94
|
+
data: { error: "Not connected to SMTP server" },
|
|
95
|
+
ok: false
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const message = ctx.body;
|
|
100
|
+
if (!message || !message.to || !message.subject) {
|
|
101
|
+
return {
|
|
102
|
+
status: 400,
|
|
103
|
+
statusText: "Bad Request",
|
|
104
|
+
headers: {},
|
|
105
|
+
data: { error: "Invalid email message: to and subject are required" },
|
|
106
|
+
ok: false
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const mailOptions = {
|
|
110
|
+
from: this.formatAddress(message.from),
|
|
111
|
+
to: this.formatAddresses(message.to),
|
|
112
|
+
subject: message.subject
|
|
113
|
+
};
|
|
114
|
+
if (message["cc"]) mailOptions["cc"] = this.formatAddresses(message["cc"]);
|
|
115
|
+
if (message["bcc"]) mailOptions["bcc"] = this.formatAddresses(message["bcc"]);
|
|
116
|
+
if (message["replyTo"]) mailOptions["replyTo"] = this.formatAddress(message["replyTo"]);
|
|
117
|
+
if (message["text"]) mailOptions["text"] = message["text"];
|
|
118
|
+
if (message["html"]) mailOptions["html"] = message["html"];
|
|
119
|
+
if (message["headers"]) mailOptions["headers"] = message["headers"];
|
|
120
|
+
if (message["priority"]) {
|
|
121
|
+
const priorityMap = {
|
|
122
|
+
high: { priority: "high", headers: { "X-Priority": "1" } },
|
|
123
|
+
normal: { priority: "normal", headers: { "X-Priority": "3" } },
|
|
124
|
+
low: { priority: "low", headers: { "X-Priority": "5" } }
|
|
125
|
+
};
|
|
126
|
+
const p = priorityMap[message["priority"]];
|
|
127
|
+
mailOptions["priority"] = p.priority;
|
|
128
|
+
mailOptions["headers"] = { ...mailOptions["headers"], ...p.headers };
|
|
129
|
+
}
|
|
130
|
+
if (message["attachments"] && message["attachments"].length > 0) {
|
|
131
|
+
mailOptions["attachments"] = message["attachments"].map((att) => ({
|
|
132
|
+
filename: att.filename,
|
|
133
|
+
content: att.content,
|
|
134
|
+
contentType: att.contentType,
|
|
135
|
+
contentDisposition: att.disposition,
|
|
136
|
+
cid: att.cid
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
const result = await this.transporter.sendMail(mailOptions);
|
|
140
|
+
const sendResult = {
|
|
141
|
+
accepted: result.accepted,
|
|
142
|
+
rejected: result.rejected,
|
|
143
|
+
messageId: result.messageId,
|
|
144
|
+
response: result.response
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
status: 200,
|
|
148
|
+
statusText: "OK",
|
|
149
|
+
headers: {},
|
|
150
|
+
data: sendResult,
|
|
151
|
+
ok: true
|
|
152
|
+
};
|
|
153
|
+
} catch (error) {
|
|
154
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
155
|
+
return {
|
|
156
|
+
status: 500,
|
|
157
|
+
statusText: "Internal Server Error",
|
|
158
|
+
headers: {},
|
|
159
|
+
data: { error: message },
|
|
160
|
+
ok: false
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async verify(_session) {
|
|
165
|
+
if (!this.transporter) return false;
|
|
166
|
+
try {
|
|
167
|
+
await this.transporter.verify();
|
|
168
|
+
return true;
|
|
169
|
+
} catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
disconnect() {
|
|
174
|
+
if (this.transporter) {
|
|
175
|
+
this.transporter.close();
|
|
176
|
+
this.transporter = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
formatAddress(addr) {
|
|
180
|
+
if (typeof addr === "string") return addr;
|
|
181
|
+
if (addr.name) return `"${addr.name}" <${addr.address}>`;
|
|
182
|
+
return addr.address;
|
|
183
|
+
}
|
|
184
|
+
formatAddresses(addrs) {
|
|
185
|
+
if (Array.isArray(addrs)) {
|
|
186
|
+
return addrs.map((a) => this.formatAddress(a));
|
|
187
|
+
}
|
|
188
|
+
return this.formatAddress(addrs);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
function createDefaultSmtpConnector(options) {
|
|
192
|
+
return new NodemailerConnector(options);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export {
|
|
196
|
+
NodemailerConnector,
|
|
197
|
+
createDefaultSmtpConnector
|
|
198
|
+
};
|
|
199
|
+
//# sourceMappingURL=chunk-UQESMDZP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connectors/nodemailer.ts"],"sourcesContent":["/**\n * Default SMTP connector using nodemailer\n *\n * This connector provides SMTP functionality using the popular nodemailer library.\n * It's the default connector used when no custom connector is provided.\n *\n * @example\n * ```ts\n * import { smtp } from '@unireq/smtp';\n *\n * // Uses NodemailerConnector by default\n * const { transport } = smtp('smtp://user:pass@smtp.gmail.com:587');\n * ```\n */\n\nimport type { RequestContext, Response } from '@unireq/core';\nimport type {\n EmailMessage,\n SendResult,\n SMTPCapabilities,\n SMTPConnector,\n SMTPConnectorOptions,\n SMTPSession,\n} from '../connector.js';\n\n/**\n * Nodemailer transporter type (minimal interface)\n */\ninterface NodemailerTransporter {\n sendMail(options: unknown): Promise<{\n accepted: string[];\n rejected: string[];\n messageId: string;\n response: string;\n }>;\n verify(): Promise<true>;\n close(): void;\n}\n\n/**\n * Nodemailer module type\n */\ninterface NodemailerModule {\n createTransport(options: unknown): NodemailerTransporter;\n}\n\n/**\n * OAuth2 credentials for Gmail and other providers\n */\nexport interface OAuth2Credentials {\n /** OAuth2 client ID */\n readonly clientId: string;\n /** OAuth2 client secret */\n readonly clientSecret: string;\n /** OAuth2 refresh token */\n readonly refreshToken: string;\n /** OAuth2 access token (optional, will be refreshed automatically) */\n readonly accessToken?: string;\n}\n\n/**\n * Extended options for NodemailerConnector\n */\nexport interface NodemailerConnectorOptions extends SMTPConnectorOptions {\n /** OAuth2 credentials for authentication */\n readonly oauth2?: OAuth2Credentials;\n}\n\n/**\n * Parse SMTP URI into connection options\n */\nfunction parseSmtpUri(uri: string): {\n host: string;\n port: number;\n secure: boolean;\n user: string;\n pass: string;\n} {\n const url = new URL(uri);\n\n // Determine port and security based on protocol\n let port = Number(url.port);\n let secure = false;\n\n if (url.protocol === 'smtps:') {\n secure = true;\n port = port || 465;\n } else {\n // smtp:// - use STARTTLS on port 587\n port = port || 587;\n }\n\n return {\n host: url.hostname,\n port,\n secure,\n user: decodeURIComponent(url.username),\n pass: decodeURIComponent(url.password),\n };\n}\n\n/**\n * Default SMTP connector using nodemailer\n */\nexport class NodemailerConnector implements SMTPConnector {\n private transporter: NodemailerTransporter | null = null;\n private nodemailer: NodemailerModule | null = null;\n\n readonly capabilities: SMTPCapabilities = {\n smtp: true,\n smtps: true,\n starttls: true,\n oauth2: true,\n html: true,\n attachments: true,\n };\n\n constructor(private readonly options: NodemailerConnectorOptions = {}) {}\n\n async connect(uri: string): Promise<SMTPSession> {\n // Lazy load nodemailer\n if (!this.nodemailer) {\n try {\n this.nodemailer = (await import('nodemailer')) as unknown as NodemailerModule;\n } catch {\n throw new Error(\n 'nodemailer is required for the default SMTP connector. ' + 'Install it with: npm install nodemailer',\n );\n }\n }\n\n const { host, port, secure, user, pass } = parseSmtpUri(uri);\n\n // Build transport options\n const transportOptions: Record<string, unknown> = {\n host,\n port,\n secure,\n connectionTimeout: this.options.timeout ?? 30000,\n greetingTimeout: this.options.timeout ?? 30000,\n socketTimeout: this.options.timeout ?? 60000,\n debug: this.options.debug ?? false,\n logger: this.options.debug ?? false,\n };\n\n // Add pooling if enabled\n if (this.options.pool) {\n transportOptions['pool'] = true;\n transportOptions['maxConnections'] = this.options.maxConnections ?? 5;\n }\n\n // Add TLS options\n if (this.options.tls) {\n transportOptions['tls'] = {\n minVersion: this.options.tls.minVersion,\n rejectUnauthorized: this.options.tls.rejectUnauthorized ?? true,\n };\n }\n\n // Add authentication\n if (this.options.oauth2) {\n // OAuth2 authentication\n transportOptions['auth'] = {\n type: 'OAuth2',\n user,\n clientId: this.options.oauth2.clientId,\n clientSecret: this.options.oauth2.clientSecret,\n refreshToken: this.options.oauth2.refreshToken,\n accessToken: this.options.oauth2.accessToken,\n };\n } else if (user && pass) {\n // Password authentication (App Password for Gmail)\n transportOptions['auth'] = {\n user,\n pass,\n };\n }\n\n this.transporter = this.nodemailer.createTransport(transportOptions);\n\n return {\n connected: true,\n host,\n user,\n secure,\n };\n }\n\n async request(_session: SMTPSession, ctx: RequestContext): Promise<Response> {\n if (!this.transporter) {\n return {\n status: 500,\n statusText: 'Internal Server Error',\n headers: {},\n data: { error: 'Not connected to SMTP server' },\n ok: false,\n };\n }\n\n try {\n const message = ctx.body as EmailMessage;\n\n if (!message || !message.to || !message.subject) {\n return {\n status: 400,\n statusText: 'Bad Request',\n headers: {},\n data: { error: 'Invalid email message: to and subject are required' },\n ok: false,\n };\n }\n\n // Build nodemailer options\n const mailOptions: Record<string, unknown> = {\n from: this.formatAddress(message.from),\n to: this.formatAddresses(message.to),\n subject: message.subject,\n };\n\n if (message['cc']) mailOptions['cc'] = this.formatAddresses(message['cc']);\n if (message['bcc']) mailOptions['bcc'] = this.formatAddresses(message['bcc']);\n if (message['replyTo']) mailOptions['replyTo'] = this.formatAddress(message['replyTo']);\n if (message['text']) mailOptions['text'] = message['text'];\n if (message['html']) mailOptions['html'] = message['html'];\n if (message['headers']) mailOptions['headers'] = message['headers'];\n\n // Handle priority\n if (message['priority']) {\n const priorityMap = {\n high: { priority: 'high', headers: { 'X-Priority': '1' } },\n normal: { priority: 'normal', headers: { 'X-Priority': '3' } },\n low: { priority: 'low', headers: { 'X-Priority': '5' } },\n };\n const p = priorityMap[message['priority']];\n mailOptions['priority'] = p.priority;\n mailOptions['headers'] = { ...(mailOptions['headers'] as object), ...p.headers };\n }\n\n // Handle attachments\n if (message['attachments'] && message['attachments'].length > 0) {\n mailOptions['attachments'] = message['attachments'].map((att) => ({\n filename: att.filename,\n content: att.content,\n contentType: att.contentType,\n contentDisposition: att.disposition,\n cid: att.cid,\n }));\n }\n\n const result = await this.transporter.sendMail(mailOptions);\n\n const sendResult: SendResult = {\n accepted: result.accepted,\n rejected: result.rejected,\n messageId: result.messageId,\n response: result.response,\n };\n\n return {\n status: 200,\n statusText: 'OK',\n headers: {},\n data: sendResult,\n ok: true,\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n status: 500,\n statusText: 'Internal Server Error',\n headers: {},\n data: { error: message },\n ok: false,\n };\n }\n }\n\n async verify(_session: SMTPSession): Promise<boolean> {\n if (!this.transporter) return false;\n try {\n await this.transporter.verify();\n return true;\n } catch {\n return false;\n }\n }\n\n disconnect(): void {\n if (this.transporter) {\n this.transporter.close();\n this.transporter = null;\n }\n }\n\n private formatAddress(addr: string | { name?: string; address: string }): string {\n if (typeof addr === 'string') return addr;\n if (addr.name) return `\"${addr.name}\" <${addr.address}>`;\n return addr.address;\n }\n\n private formatAddresses(\n addrs: string | { name?: string; address: string } | (string | { name?: string; address: string })[],\n ): string | string[] {\n if (Array.isArray(addrs)) {\n return addrs.map((a) => this.formatAddress(a));\n }\n return this.formatAddress(addrs);\n }\n}\n\n/**\n * Creates a default nodemailer connector\n */\nexport function createDefaultSmtpConnector(options?: NodemailerConnectorOptions): SMTPConnector {\n return new NodemailerConnector(options);\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { Connector, RequestContext, Response, TransportWithCapabilities } from '@unireq/core';
|
|
2
|
+
export { Connector, RequestContext, Response, TransportWithCapabilities, policy } from '@unireq/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SMTP Connector interface for BYOC (Bring Your Own Connector) pattern
|
|
6
|
+
*
|
|
7
|
+
* This interface extends the core Connector with SMTP-specific capabilities.
|
|
8
|
+
* Implement this interface to create custom SMTP connectors.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { smtp, type SMTPConnector } from '@unireq/smtp';
|
|
13
|
+
*
|
|
14
|
+
* class MySmtpConnector implements SMTPConnector {
|
|
15
|
+
* // ... implementation
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* const { transport } = smtp('smtp://mail.server.com', new MySmtpConnector());
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* SMTP session representing an active connection
|
|
24
|
+
*/
|
|
25
|
+
interface SMTPSession {
|
|
26
|
+
/** Whether the session is currently connected */
|
|
27
|
+
readonly connected: boolean;
|
|
28
|
+
/** SMTP server hostname */
|
|
29
|
+
readonly host: string;
|
|
30
|
+
/** Authenticated username */
|
|
31
|
+
readonly user: string;
|
|
32
|
+
/** Whether the connection uses TLS */
|
|
33
|
+
readonly secure: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* SMTP connector capabilities
|
|
37
|
+
*/
|
|
38
|
+
interface SMTPCapabilities {
|
|
39
|
+
/** Supports SMTP protocol */
|
|
40
|
+
readonly smtp: boolean;
|
|
41
|
+
/** Supports SMTPS (implicit TLS) */
|
|
42
|
+
readonly smtps: boolean;
|
|
43
|
+
/** Supports STARTTLS upgrade */
|
|
44
|
+
readonly starttls: boolean;
|
|
45
|
+
/** Supports OAuth2 authentication */
|
|
46
|
+
readonly oauth2: boolean;
|
|
47
|
+
/** Supports HTML emails */
|
|
48
|
+
readonly html: boolean;
|
|
49
|
+
/** Supports attachments */
|
|
50
|
+
readonly attachments: boolean;
|
|
51
|
+
/** Index signature for TransportCapabilities compatibility */
|
|
52
|
+
readonly [key: string]: boolean | undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* SMTP connector options for connection configuration
|
|
56
|
+
*/
|
|
57
|
+
interface SMTPConnectorOptions {
|
|
58
|
+
/** Connection timeout in milliseconds */
|
|
59
|
+
readonly timeout?: number;
|
|
60
|
+
/** TLS options for secure connections */
|
|
61
|
+
readonly tls?: {
|
|
62
|
+
/** Minimum TLS version */
|
|
63
|
+
readonly minVersion?: string;
|
|
64
|
+
/** Reject unauthorized certificates */
|
|
65
|
+
readonly rejectUnauthorized?: boolean;
|
|
66
|
+
};
|
|
67
|
+
/** Enable debug logging */
|
|
68
|
+
readonly debug?: boolean;
|
|
69
|
+
/** Pool connections for better performance */
|
|
70
|
+
readonly pool?: boolean;
|
|
71
|
+
/** Maximum number of connections in pool */
|
|
72
|
+
readonly maxConnections?: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Email address representation
|
|
76
|
+
*/
|
|
77
|
+
interface EmailAddress {
|
|
78
|
+
/** Display name (optional) */
|
|
79
|
+
readonly name?: string;
|
|
80
|
+
/** Email address */
|
|
81
|
+
readonly address: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Email attachment
|
|
85
|
+
*/
|
|
86
|
+
interface EmailAttachment {
|
|
87
|
+
/** Filename */
|
|
88
|
+
readonly filename: string;
|
|
89
|
+
/** Content as Buffer, string, or readable stream */
|
|
90
|
+
readonly content: Buffer | string | NodeJS.ReadableStream;
|
|
91
|
+
/** MIME type (optional, auto-detected if omitted) */
|
|
92
|
+
readonly contentType?: string;
|
|
93
|
+
/** Content disposition: 'attachment' or 'inline' */
|
|
94
|
+
readonly disposition?: 'attachment' | 'inline';
|
|
95
|
+
/** Content-ID for inline attachments (for HTML embedding) */
|
|
96
|
+
readonly cid?: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Email message to send
|
|
100
|
+
*/
|
|
101
|
+
interface EmailMessage {
|
|
102
|
+
/** Sender address */
|
|
103
|
+
readonly from: string | EmailAddress;
|
|
104
|
+
/** Recipient(s) */
|
|
105
|
+
readonly to: string | EmailAddress | (string | EmailAddress)[];
|
|
106
|
+
/** CC recipient(s) */
|
|
107
|
+
readonly cc?: string | EmailAddress | (string | EmailAddress)[];
|
|
108
|
+
/** BCC recipient(s) */
|
|
109
|
+
readonly bcc?: string | EmailAddress | (string | EmailAddress)[];
|
|
110
|
+
/** Reply-To address */
|
|
111
|
+
readonly replyTo?: string | EmailAddress;
|
|
112
|
+
/** Email subject */
|
|
113
|
+
readonly subject: string;
|
|
114
|
+
/** Plain text body */
|
|
115
|
+
readonly text?: string;
|
|
116
|
+
/** HTML body */
|
|
117
|
+
readonly html?: string;
|
|
118
|
+
/** Attachments */
|
|
119
|
+
readonly attachments?: EmailAttachment[];
|
|
120
|
+
/** Custom headers */
|
|
121
|
+
readonly headers?: Record<string, string>;
|
|
122
|
+
/** Message priority: 'high', 'normal', 'low' */
|
|
123
|
+
readonly priority?: 'high' | 'normal' | 'low';
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Result of sending an email
|
|
127
|
+
*/
|
|
128
|
+
interface SendResult {
|
|
129
|
+
/** Whether the email was accepted */
|
|
130
|
+
readonly accepted: string[];
|
|
131
|
+
/** Whether the email was rejected */
|
|
132
|
+
readonly rejected: string[];
|
|
133
|
+
/** Message ID assigned by the server */
|
|
134
|
+
readonly messageId: string;
|
|
135
|
+
/** Server response */
|
|
136
|
+
readonly response: string;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* SMTP Connector interface
|
|
140
|
+
*
|
|
141
|
+
* Extends the core Connector interface with SMTP-specific capabilities.
|
|
142
|
+
* The connector is responsible for:
|
|
143
|
+
* - Establishing and managing SMTP connections
|
|
144
|
+
* - Translating EmailMessage to SMTP commands
|
|
145
|
+
* - Returning standardized Response objects
|
|
146
|
+
*/
|
|
147
|
+
interface SMTPConnector extends Connector<SMTPSession> {
|
|
148
|
+
/**
|
|
149
|
+
* Connect to an SMTP server
|
|
150
|
+
* @param uri - SMTP URI (e.g., 'smtp://user:pass@host:port' or 'smtps://...')
|
|
151
|
+
* @returns SMTP session
|
|
152
|
+
*/
|
|
153
|
+
connect(uri: string): Promise<SMTPSession>;
|
|
154
|
+
/**
|
|
155
|
+
* Send an email
|
|
156
|
+
* @param session - Active SMTP session
|
|
157
|
+
* @param ctx - Request context with email message in body
|
|
158
|
+
* @returns Response with SendResult
|
|
159
|
+
*/
|
|
160
|
+
request(session: SMTPSession, ctx: RequestContext): Promise<Response>;
|
|
161
|
+
/**
|
|
162
|
+
* Disconnect from SMTP server
|
|
163
|
+
* @param session - Active SMTP session
|
|
164
|
+
*/
|
|
165
|
+
disconnect(session: SMTPSession): Promise<void> | void;
|
|
166
|
+
/**
|
|
167
|
+
* Verify connection is still alive
|
|
168
|
+
* @param session - Active SMTP session
|
|
169
|
+
*/
|
|
170
|
+
verify?(session: SMTPSession): Promise<boolean>;
|
|
171
|
+
/**
|
|
172
|
+
* Connector capabilities
|
|
173
|
+
*/
|
|
174
|
+
readonly capabilities: SMTPCapabilities;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Default SMTP connector using nodemailer
|
|
179
|
+
*
|
|
180
|
+
* This connector provides SMTP functionality using the popular nodemailer library.
|
|
181
|
+
* It's the default connector used when no custom connector is provided.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* import { smtp } from '@unireq/smtp';
|
|
186
|
+
*
|
|
187
|
+
* // Uses NodemailerConnector by default
|
|
188
|
+
* const { transport } = smtp('smtp://user:pass@smtp.gmail.com:587');
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* OAuth2 credentials for Gmail and other providers
|
|
194
|
+
*/
|
|
195
|
+
interface OAuth2Credentials {
|
|
196
|
+
/** OAuth2 client ID */
|
|
197
|
+
readonly clientId: string;
|
|
198
|
+
/** OAuth2 client secret */
|
|
199
|
+
readonly clientSecret: string;
|
|
200
|
+
/** OAuth2 refresh token */
|
|
201
|
+
readonly refreshToken: string;
|
|
202
|
+
/** OAuth2 access token (optional, will be refreshed automatically) */
|
|
203
|
+
readonly accessToken?: string;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Extended options for NodemailerConnector
|
|
207
|
+
*/
|
|
208
|
+
interface NodemailerConnectorOptions extends SMTPConnectorOptions {
|
|
209
|
+
/** OAuth2 credentials for authentication */
|
|
210
|
+
readonly oauth2?: OAuth2Credentials;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Default SMTP connector using nodemailer
|
|
214
|
+
*/
|
|
215
|
+
declare class NodemailerConnector implements SMTPConnector {
|
|
216
|
+
private readonly options;
|
|
217
|
+
private transporter;
|
|
218
|
+
private nodemailer;
|
|
219
|
+
readonly capabilities: SMTPCapabilities;
|
|
220
|
+
constructor(options?: NodemailerConnectorOptions);
|
|
221
|
+
connect(uri: string): Promise<SMTPSession>;
|
|
222
|
+
request(_session: SMTPSession, ctx: RequestContext): Promise<Response>;
|
|
223
|
+
verify(_session: SMTPSession): Promise<boolean>;
|
|
224
|
+
disconnect(): void;
|
|
225
|
+
private formatAddress;
|
|
226
|
+
private formatAddresses;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Creates a default nodemailer connector
|
|
230
|
+
*/
|
|
231
|
+
declare function createDefaultSmtpConnector(options?: NodemailerConnectorOptions): SMTPConnector;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* SMTP Transport using BYOC (Bring Your Own Connector) pattern
|
|
235
|
+
*
|
|
236
|
+
* The smtp() function creates an SMTP transport that can use:
|
|
237
|
+
* - The default NodemailerConnector (requires nodemailer package)
|
|
238
|
+
* - A custom connector implementing SMTPConnector interface
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```ts
|
|
242
|
+
* import { smtp } from '@unireq/smtp';
|
|
243
|
+
* import { client } from '@unireq/core';
|
|
244
|
+
*
|
|
245
|
+
* // Using default connector (requires: npm install nodemailer)
|
|
246
|
+
* const { transport } = smtp('smtp://user:pass@smtp.gmail.com:587');
|
|
247
|
+
* const mail = client(transport);
|
|
248
|
+
*
|
|
249
|
+
* // Send an email
|
|
250
|
+
* await mail.post('/', {
|
|
251
|
+
* from: 'me@gmail.com',
|
|
252
|
+
* to: 'you@example.com',
|
|
253
|
+
* subject: 'Hello!',
|
|
254
|
+
* text: 'This is a test email.',
|
|
255
|
+
* });
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* SMTP transport options
|
|
261
|
+
*/
|
|
262
|
+
interface SMTPTransportOptions extends SMTPConnectorOptions {
|
|
263
|
+
/** OAuth2 credentials for authentication */
|
|
264
|
+
readonly oauth2?: {
|
|
265
|
+
readonly clientId: string;
|
|
266
|
+
readonly clientSecret: string;
|
|
267
|
+
readonly refreshToken: string;
|
|
268
|
+
readonly accessToken?: string;
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Creates an SMTP transport
|
|
273
|
+
*
|
|
274
|
+
* Supports BYOC (Bring Your Own Connector) pattern:
|
|
275
|
+
* - Without connector: Uses default NodemailerConnector (requires nodemailer)
|
|
276
|
+
* - With connector: Uses the provided SMTPConnector implementation
|
|
277
|
+
*
|
|
278
|
+
* @param uri - SMTP URI (e.g., 'smtp://user:pass@server.com:587' or 'smtps://...')
|
|
279
|
+
* @param connectorOrOptions - Custom connector or transport options
|
|
280
|
+
* @returns Transport with SMTP capabilities
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* // Simple usage with App Password (Gmail)
|
|
285
|
+
* const { transport } = smtp('smtp://user@gmail.com:app-password@smtp.gmail.com:587');
|
|
286
|
+
*
|
|
287
|
+
* // With SMTPS (implicit TLS, port 465)
|
|
288
|
+
* const { transport } = smtp('smtps://user:pass@smtp.gmail.com');
|
|
289
|
+
*
|
|
290
|
+
* // With OAuth2
|
|
291
|
+
* const { transport } = smtp('smtp://user@gmail.com@smtp.gmail.com:587', {
|
|
292
|
+
* oauth2: {
|
|
293
|
+
* clientId: 'your-client-id',
|
|
294
|
+
* clientSecret: 'your-client-secret',
|
|
295
|
+
* refreshToken: 'your-refresh-token',
|
|
296
|
+
* },
|
|
297
|
+
* });
|
|
298
|
+
*
|
|
299
|
+
* // With custom connector (BYOC)
|
|
300
|
+
* const { transport } = smtp('smtp://server.com', myConnector);
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
declare function smtp(uri?: string, connectorOrOptions?: SMTPConnector | SMTPTransportOptions): TransportWithCapabilities;
|
|
304
|
+
|
|
305
|
+
export { type EmailAddress, type EmailAttachment, type EmailMessage, NodemailerConnector, type NodemailerConnectorOptions, type OAuth2Credentials, type SMTPCapabilities, type SMTPConnector, type SMTPConnectorOptions, type SMTPSession, type SMTPTransportOptions, type SendResult, createDefaultSmtpConnector, smtp };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NodemailerConnector,
|
|
3
|
+
createDefaultSmtpConnector
|
|
4
|
+
} from "./chunk-UQESMDZP.js";
|
|
5
|
+
|
|
6
|
+
// src/index.ts
|
|
7
|
+
import { policy as policy2 } from "@unireq/core";
|
|
8
|
+
|
|
9
|
+
// src/transport.ts
|
|
10
|
+
import { policy } from "@unireq/core";
|
|
11
|
+
async function getDefaultConnector(options) {
|
|
12
|
+
try {
|
|
13
|
+
const { createDefaultSmtpConnector: createDefaultSmtpConnector2 } = await import("./nodemailer-KYPPEHYG.js");
|
|
14
|
+
return createDefaultSmtpConnector2(options);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Default SMTP connector not available. Install nodemailer or provide a custom connector.
|
|
19
|
+
Install: npm install nodemailer
|
|
20
|
+
Or use BYOC: smtp('smtp://server.com', myConnector)
|
|
21
|
+
Cause: ${cause}`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function smtp(uri, connectorOrOptions) {
|
|
26
|
+
let session = null;
|
|
27
|
+
let actualConnector = null;
|
|
28
|
+
const isConnector = (arg) => arg !== null && typeof arg === "object" && "connect" in arg && "request" in arg && "disconnect" in arg;
|
|
29
|
+
const providedConnector = isConnector(connectorOrOptions) ? connectorOrOptions : null;
|
|
30
|
+
const options = !isConnector(connectorOrOptions) ? connectorOrOptions : void 0;
|
|
31
|
+
const transport = policy(
|
|
32
|
+
async (ctx) => {
|
|
33
|
+
if (!actualConnector) {
|
|
34
|
+
actualConnector = providedConnector ?? await getDefaultConnector(options);
|
|
35
|
+
}
|
|
36
|
+
if (!session && uri) {
|
|
37
|
+
session = await actualConnector.connect(uri);
|
|
38
|
+
}
|
|
39
|
+
return actualConnector.request(session, ctx);
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "smtp",
|
|
43
|
+
kind: "transport",
|
|
44
|
+
options: uri ? { uri } : void 0
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
const defaultCapabilities = {
|
|
48
|
+
smtp: true,
|
|
49
|
+
smtps: true,
|
|
50
|
+
starttls: true,
|
|
51
|
+
oauth2: true,
|
|
52
|
+
html: true,
|
|
53
|
+
attachments: true
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
transport,
|
|
57
|
+
capabilities: providedConnector?.capabilities ?? defaultCapabilities
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
NodemailerConnector,
|
|
62
|
+
createDefaultSmtpConnector,
|
|
63
|
+
policy2 as policy,
|
|
64
|
+
smtp
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/transport.ts"],"sourcesContent":["/**\n * @unireq/smtp - SMTP transport with BYOC (Bring Your Own Connector) pattern\n *\n * This package provides an SMTP transport that can use:\n * - The default NodemailerConnector (requires nodemailer package as peer dependency)\n * - A custom connector implementing SMTPConnector interface (BYOC)\n *\n * @example Default usage (requires: npm install nodemailer)\n * ```ts\n * import { smtp } from '@unireq/smtp';\n * import { client } from '@unireq/core';\n *\n * // Gmail with App Password\n * const { transport } = smtp('smtp://user@gmail.com:your-app-password@smtp.gmail.com:587');\n * const mail = client(transport);\n *\n * // Send an email\n * await mail.post('/', {\n * from: 'user@gmail.com',\n * to: 'recipient@example.com',\n * subject: 'Hello from Unireq!',\n * text: 'This is a test email.',\n * html: '<h1>Hello!</h1><p>This is a test email.</p>',\n * });\n * ```\n *\n * @example With OAuth2 (Gmail)\n * ```ts\n * import { smtp } from '@unireq/smtp';\n *\n * const { transport } = smtp('smtp://user@gmail.com@smtp.gmail.com:587', {\n * oauth2: {\n * clientId: 'your-client-id.apps.googleusercontent.com',\n * clientSecret: 'your-client-secret',\n * refreshToken: 'your-refresh-token',\n * },\n * });\n * ```\n *\n * @example BYOC (Bring Your Own Connector)\n * ```ts\n * import { smtp, type SMTPConnector } from '@unireq/smtp';\n *\n * class MySmtpConnector implements SMTPConnector {\n * // Custom implementation...\n * }\n *\n * const { transport } = smtp('smtp://mail.server.com', new MySmtpConnector());\n * ```\n *\n * @packageDocumentation\n */\n\n// Re-export core types for convenience\nexport type { Connector, RequestContext, Response, TransportWithCapabilities } from '@unireq/core';\nexport { policy } from '@unireq/core';\n// Connector interface for BYOC\nexport type {\n EmailAddress,\n EmailAttachment,\n EmailMessage,\n SendResult,\n SMTPCapabilities,\n SMTPConnector,\n SMTPConnectorOptions,\n SMTPSession,\n} from './connector.js';\n// Default connector (requires nodemailer peer dependency)\nexport {\n createDefaultSmtpConnector,\n NodemailerConnector,\n type NodemailerConnectorOptions,\n type OAuth2Credentials,\n} from './connectors/nodemailer.js';\n// Main transport function\nexport { type SMTPTransportOptions, smtp } from './transport.js';\n","/**\n * SMTP Transport using BYOC (Bring Your Own Connector) pattern\n *\n * The smtp() function creates an SMTP transport that can use:\n * - The default NodemailerConnector (requires nodemailer package)\n * - A custom connector implementing SMTPConnector interface\n *\n * @example\n * ```ts\n * import { smtp } from '@unireq/smtp';\n * import { client } from '@unireq/core';\n *\n * // Using default connector (requires: npm install nodemailer)\n * const { transport } = smtp('smtp://user:pass@smtp.gmail.com:587');\n * const mail = client(transport);\n *\n * // Send an email\n * await mail.post('/', {\n * from: 'me@gmail.com',\n * to: 'you@example.com',\n * subject: 'Hello!',\n * text: 'This is a test email.',\n * });\n * ```\n */\n\nimport type { RequestContext, Response, TransportWithCapabilities } from '@unireq/core';\nimport { policy } from '@unireq/core';\nimport type { SMTPConnector, SMTPConnectorOptions, SMTPSession } from './connector.js';\n\n/**\n * Lazily loads the default SMTP connector\n * Throws a helpful error if nodemailer is not installed\n */\nasync function getDefaultConnector(options?: SMTPConnectorOptions): Promise<SMTPConnector> {\n try {\n const { createDefaultSmtpConnector } = await import('./connectors/nodemailer.js');\n return createDefaultSmtpConnector(options);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n throw new Error(\n `Default SMTP connector not available. Install nodemailer or provide a custom connector.\\n` +\n `Install: npm install nodemailer\\n` +\n `Or use BYOC: smtp('smtp://server.com', myConnector)\\n` +\n `Cause: ${cause}`,\n );\n }\n}\n\n/**\n * SMTP transport options\n */\nexport interface SMTPTransportOptions extends SMTPConnectorOptions {\n /** OAuth2 credentials for authentication */\n readonly oauth2?: {\n readonly clientId: string;\n readonly clientSecret: string;\n readonly refreshToken: string;\n readonly accessToken?: string;\n };\n}\n\n/**\n * Creates an SMTP transport\n *\n * Supports BYOC (Bring Your Own Connector) pattern:\n * - Without connector: Uses default NodemailerConnector (requires nodemailer)\n * - With connector: Uses the provided SMTPConnector implementation\n *\n * @param uri - SMTP URI (e.g., 'smtp://user:pass@server.com:587' or 'smtps://...')\n * @param connectorOrOptions - Custom connector or transport options\n * @returns Transport with SMTP capabilities\n *\n * @example\n * ```ts\n * // Simple usage with App Password (Gmail)\n * const { transport } = smtp('smtp://user@gmail.com:app-password@smtp.gmail.com:587');\n *\n * // With SMTPS (implicit TLS, port 465)\n * const { transport } = smtp('smtps://user:pass@smtp.gmail.com');\n *\n * // With OAuth2\n * const { transport } = smtp('smtp://user@gmail.com@smtp.gmail.com:587', {\n * oauth2: {\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * refreshToken: 'your-refresh-token',\n * },\n * });\n *\n * // With custom connector (BYOC)\n * const { transport } = smtp('smtp://server.com', myConnector);\n * ```\n */\nexport function smtp(\n uri?: string,\n connectorOrOptions?: SMTPConnector | SMTPTransportOptions,\n): TransportWithCapabilities {\n let session: SMTPSession | null = null;\n let actualConnector: SMTPConnector | null = null;\n\n // Determine if second argument is a connector or options\n const isConnector = (arg: unknown): arg is SMTPConnector =>\n arg !== null && typeof arg === 'object' && 'connect' in arg && 'request' in arg && 'disconnect' in arg;\n\n const providedConnector = isConnector(connectorOrOptions) ? connectorOrOptions : null;\n const options = !isConnector(connectorOrOptions) ? connectorOrOptions : undefined;\n\n const transport = policy(\n async (ctx: RequestContext): Promise<Response> => {\n // Lazy load connector\n if (!actualConnector) {\n actualConnector = providedConnector ?? (await getDefaultConnector(options));\n }\n\n // Connect if not connected and URI provided\n if (!session && uri) {\n session = await actualConnector.connect(uri);\n }\n\n // biome-ignore lint/style/noNonNullAssertion: session is guaranteed by connect() or existing connection above\n return actualConnector.request(session!, ctx);\n },\n {\n name: 'smtp',\n kind: 'transport',\n options: uri ? { uri } : undefined,\n },\n );\n\n // Get capabilities from connector if available, otherwise use defaults\n const defaultCapabilities = {\n smtp: true,\n smtps: true,\n starttls: true,\n oauth2: true,\n html: true,\n attachments: true,\n };\n\n return {\n transport,\n capabilities: providedConnector?.capabilities ?? defaultCapabilities,\n };\n}\n"],"mappings":";;;;;;AAuDA,SAAS,UAAAA,eAAc;;;AC5BvB,SAAS,cAAc;AAOvB,eAAe,oBAAoB,SAAwD;AACzF,MAAI;AACF,UAAM,EAAE,4BAAAC,4BAA2B,IAAI,MAAM,OAAO,0BAA4B;AAChF,WAAOA,4BAA2B,OAAO;AAAA,EAC3C,SAAS,OAAO;AACd,UAAM,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACnE,UAAM,IAAI;AAAA,MACR;AAAA;AAAA;AAAA,SAGY,KAAK;AAAA,IACnB;AAAA,EACF;AACF;AA+CO,SAAS,KACd,KACA,oBAC2B;AAC3B,MAAI,UAA8B;AAClC,MAAI,kBAAwC;AAG5C,QAAM,cAAc,CAAC,QACnB,QAAQ,QAAQ,OAAO,QAAQ,YAAY,aAAa,OAAO,aAAa,OAAO,gBAAgB;AAErG,QAAM,oBAAoB,YAAY,kBAAkB,IAAI,qBAAqB;AACjF,QAAM,UAAU,CAAC,YAAY,kBAAkB,IAAI,qBAAqB;AAExE,QAAM,YAAY;AAAA,IAChB,OAAO,QAA2C;AAEhD,UAAI,CAAC,iBAAiB;AACpB,0BAAkB,qBAAsB,MAAM,oBAAoB,OAAO;AAAA,MAC3E;AAGA,UAAI,CAAC,WAAW,KAAK;AACnB,kBAAU,MAAM,gBAAgB,QAAQ,GAAG;AAAA,MAC7C;AAGA,aAAO,gBAAgB,QAAQ,SAAU,GAAG;AAAA,IAC9C;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS,MAAM,EAAE,IAAI,IAAI;AAAA,IAC3B;AAAA,EACF;AAGA,QAAM,sBAAsB;AAAA,IAC1B,MAAM;AAAA,IACN,OAAO;AAAA,IACP,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAEA,SAAO;AAAA,IACL;AAAA,IACA,cAAc,mBAAmB,gBAAgB;AAAA,EACnD;AACF;","names":["policy","createDefaultSmtpConnector"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unireq/smtp",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "SMTP transport for unireq using nodemailer",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"author": "Olivier Orabona",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@unireq/core": "0.0.1"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"nodemailer": "^7.0.12"
|
|
24
|
+
},
|
|
25
|
+
"peerDependenciesMeta": {
|
|
26
|
+
"nodemailer": {
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/nodemailer": "^7.0.4",
|
|
32
|
+
"typescript": "^5.9.3",
|
|
33
|
+
"tsup": "^8.5.1"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/oorabona/unireq",
|
|
41
|
+
"directory": "packages/smtp"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/oorabona/unireq/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/oorabona/unireq/tree/main/packages/smtp",
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"type-check": "tsc --noEmit",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
52
|
+
}
|
|
53
|
+
}
|