sently 0.3.3 → 0.4.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.md +93 -0
  3. package/dist/adapters/bun.d.ts +35 -0
  4. package/dist/{src/adapters → adapters}/bun.js +1 -1
  5. package/dist/adapters/cf.d.ts +55 -0
  6. package/dist/{src/adapters → adapters}/cf.js +1 -1
  7. package/dist/adapters/deno.d.ts +48 -0
  8. package/dist/{src/adapters → adapters}/deno.js +1 -1
  9. package/dist/adapters/node.d.ts +35 -0
  10. package/dist/{src/adapters → adapters}/node.js +1 -1
  11. package/dist/auth/oauth2.d.ts +34 -0
  12. package/dist/{src/auth → auth}/oauth2.js +3 -3
  13. package/dist/{chunk-hdqpvsm8.js → chunk-bvxkmq94.js} +12 -3
  14. package/dist/{chunk-hdqpvsm8.js.map → chunk-bvxkmq94.js.map} +3 -3
  15. package/dist/{chunk-dhbe64fc.js → chunk-j6qw8ms6.js} +1 -1
  16. package/dist/{chunk-qb05tsqn.js → chunk-tjsgb3qb.js} +2 -221
  17. package/dist/chunk-tjsgb3qb.js.map +11 -0
  18. package/dist/chunk-z3eq2t1d.js +244 -0
  19. package/dist/chunk-z3eq2t1d.js.map +10 -0
  20. package/dist/core/address.d.ts +21 -0
  21. package/dist/core/base64.d.ts +27 -0
  22. package/dist/core/cram-md5.d.ts +17 -0
  23. package/dist/core/dkim.d.ts +22 -0
  24. package/dist/core/mime.d.ts +13 -0
  25. package/dist/core/plugin.d.ts +23 -0
  26. package/dist/core/sigv4.d.ts +57 -0
  27. package/dist/core/smtp.d.ts +90 -0
  28. package/dist/core/types.d.ts +291 -0
  29. package/dist/detect.d.ts +15 -0
  30. package/dist/index.d.ts +37 -0
  31. package/dist/index.js +29 -0
  32. package/dist/{src/index.js.map → index.js.map} +1 -1
  33. package/dist/plugins/template.d.ts +61 -0
  34. package/dist/plugins/template.js +29 -0
  35. package/dist/plugins/template.js.map +10 -0
  36. package/dist/pool/connection.d.ts +25 -0
  37. package/dist/pool/pool.d.ts +59 -0
  38. package/dist/{src/pool → pool}/pool.js +26 -14
  39. package/dist/pool/pool.js.map +11 -0
  40. package/dist/transports/brevo.d.ts +20 -0
  41. package/dist/{src/transports → transports}/brevo.js +32 -4
  42. package/dist/transports/brevo.js.map +10 -0
  43. package/dist/transports/mailgun.d.ts +22 -0
  44. package/dist/{src/transports → transports}/mailgun.js +29 -4
  45. package/dist/transports/mailgun.js.map +10 -0
  46. package/dist/transports/postmark.d.ts +24 -0
  47. package/dist/{src/transports → transports}/postmark.js +33 -4
  48. package/dist/transports/postmark.js.map +10 -0
  49. package/dist/transports/preview.d.ts +15 -0
  50. package/dist/transports/preview.js +73 -0
  51. package/dist/transports/preview.js.map +10 -0
  52. package/dist/transports/resend.d.ts +26 -0
  53. package/dist/{src/transports → transports}/resend.js +28 -4
  54. package/dist/transports/resend.js.map +10 -0
  55. package/dist/transports/resolve-attachments.d.ts +12 -0
  56. package/dist/transports/retry.d.ts +21 -0
  57. package/dist/transports/retry.js +79 -0
  58. package/dist/transports/retry.js.map +10 -0
  59. package/dist/transports/sendgrid.d.ts +24 -0
  60. package/dist/{src/transports → transports}/sendgrid.js +33 -4
  61. package/dist/transports/sendgrid.js.map +10 -0
  62. package/dist/transports/ses.d.ts +25 -0
  63. package/dist/{src/transports → transports}/ses.js +45 -6
  64. package/dist/{src/transports → transports}/ses.js.map +3 -3
  65. package/dist/transports/smtp.d.ts +52 -0
  66. package/dist/transports/smtp.js +27 -0
  67. package/dist/{src/transports → transports}/smtp.js.map +1 -1
  68. package/package.json +25 -4
  69. package/dist/chunk-qb05tsqn.js.map +0 -12
  70. package/dist/src/index.js +0 -18
  71. package/dist/src/pool/pool.js.map +0 -11
  72. package/dist/src/transports/brevo.js.map +0 -10
  73. package/dist/src/transports/mailgun.js.map +0 -10
  74. package/dist/src/transports/postmark.js.map +0 -10
  75. package/dist/src/transports/resend.js.map +0 -10
  76. package/dist/src/transports/sendgrid.js.map +0 -10
  77. package/dist/src/transports/smtp.js +0 -25
  78. /package/dist/{src/adapters → adapters}/bun.js.map +0 -0
  79. /package/dist/{src/adapters → adapters}/cf.js.map +0 -0
  80. /package/dist/{src/adapters → adapters}/deno.js.map +0 -0
  81. /package/dist/{src/adapters → adapters}/node.js.map +0 -0
  82. /package/dist/{src/auth → auth}/oauth2.js.map +0 -0
  83. /package/dist/{chunk-dhbe64fc.js.map → chunk-j6qw8ms6.js.map} +0 -0
@@ -0,0 +1,22 @@
1
+ import type { DKIMConfig } from "./types.js";
2
+ /** Result of DKIM signing — the header line to prepend. */
3
+ export interface DKIMSignResult {
4
+ /** Complete DKIM-Signature header line (without trailing CRLF). */
5
+ header: string;
6
+ }
7
+ /**
8
+ * Canonicalize headers using the relaxed algorithm (RFC 6376 §3.4.2).
9
+ */
10
+ export declare function canonicalizeHeadersRelaxed(headers: string, fieldNames: string[]): string;
11
+ /**
12
+ * Canonicalize body using the relaxed algorithm (RFC 6376 §3.4.4).
13
+ */
14
+ export declare function canonicalizeBodyRelaxed(body: string): string;
15
+ /**
16
+ * Import a PEM-encoded private key into a Web Crypto CryptoKey.
17
+ */
18
+ export declare function importPrivateKey(pem: string, algorithm: "rsa-sha256" | "ed25519-sha256"): Promise<CryptoKey>;
19
+ /**
20
+ * Sign a raw MIME message with DKIM.
21
+ */
22
+ export declare function signDKIM(rawMessage: Uint8Array, config: DKIMConfig): Promise<DKIMSignResult>;
@@ -0,0 +1,13 @@
1
+ import type { DKIMConfig, Envelope, MailOptions } from "./types.js";
2
+ /** Result of building a complete MIME message. */
3
+ export interface MIMEBuildResult {
4
+ raw: Uint8Array;
5
+ envelope: Envelope;
6
+ messageId: string;
7
+ size: number;
8
+ }
9
+ /**
10
+ * Build a complete MIME message as a Uint8Array ready for SMTP DATA.
11
+ * When `dkim` is provided, signs the message and prepends the DKIM-Signature header.
12
+ */
13
+ export declare function buildMIME(options: MailOptions, dkim?: DKIMConfig): Promise<MIMEBuildResult>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @module
3
+ * Plugin pipeline for sently.
4
+ * Plugins transform MailOptions before message construction.
5
+ * They run sequentially — each receives the previous plugin's output.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { runPlugins } from "sently/core/plugin";
10
+ * const result = await runPlugins(options, [pluginA, pluginB]);
11
+ * ```
12
+ */
13
+ import type { MailOptions, MailPlugin } from "./types.js";
14
+ /**
15
+ * Run a list of plugins sequentially over MailOptions.
16
+ * If plugins is empty or undefined, returns options unchanged.
17
+ * Each plugin may be sync or async.
18
+ *
19
+ * @param options - the original mail options
20
+ * @param plugins - ordered list of plugins to apply
21
+ * @returns transformed mail options after all plugins have run
22
+ */
23
+ export declare function runPlugins(options: MailOptions, plugins: MailPlugin[] | undefined): Promise<MailOptions>;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @module
3
+ * AWS Signature Version 4 signing using Web Crypto (HMAC-SHA256).
4
+ * Works on Node.js, Bun, Deno, and Cloudflare Workers.
5
+ * No external dependencies.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { signRequest } from "sently/core/sigv4";
10
+ * const signed = await signRequest({
11
+ * method: "POST",
12
+ * url: "https://email.us-east-1.amazonaws.com/v2/email/outbound-emails",
13
+ * headers: { "content-type": "application/json" },
14
+ * body: '{"..."}',
15
+ * credentials: { accessKeyId, secretAccessKey, region: "us-east-1", service: "ses" },
16
+ * });
17
+ * ```
18
+ */
19
+ /** AWS credentials and signing scope for SigV4. */
20
+ export interface SigV4Credentials {
21
+ accessKeyId: string;
22
+ secretAccessKey: string;
23
+ region: string;
24
+ service: string;
25
+ sessionToken?: string;
26
+ }
27
+ /** HTTP request to sign with AWS Signature Version 4. */
28
+ export interface SigV4Request {
29
+ method: string;
30
+ url: string;
31
+ headers: Record<string, string>;
32
+ body: string;
33
+ credentials: SigV4Credentials;
34
+ /** Override datetime for testing. Full 'YYYYMMDDTHHMMSSZ' when provided. */
35
+ _date?: string;
36
+ }
37
+ /** Signed request headers including Authorization. */
38
+ export interface SigV4Result {
39
+ /** All headers including Authorization, x-amz-date, and x-amz-security-token */
40
+ headers: Record<string, string>;
41
+ }
42
+ /**
43
+ * Compute SHA-256 hash of a string using Web Crypto.
44
+ * Returns lowercase hex string.
45
+ * @internal
46
+ */
47
+ export declare function sha256Hex(data: string): Promise<string>;
48
+ /**
49
+ * Compute HMAC-SHA256 using Web Crypto.
50
+ * @internal
51
+ */
52
+ export declare function hmacSHA256(key: Uint8Array | string, data: string): Promise<Uint8Array>;
53
+ /**
54
+ * Sign an HTTP request with AWS Signature Version 4.
55
+ * Returns the complete set of headers to include in the request.
56
+ */
57
+ export declare function signRequest(request: SigV4Request): Promise<SigV4Result>;
@@ -0,0 +1,90 @@
1
+ export { computeCRAMMD5 } from "./cram-md5.js";
2
+ /** SMTP command to send to the server. */
3
+ export type SMTPCommand = {
4
+ type: "EHLO";
5
+ domain: string;
6
+ } | {
7
+ type: "STARTTLS";
8
+ } | {
9
+ type: "AUTH_LOGIN";
10
+ user: string;
11
+ pass: string;
12
+ } | {
13
+ type: "AUTH_PLAIN";
14
+ user: string;
15
+ pass: string;
16
+ } | {
17
+ type: "AUTH_CRAM_MD5_INIT";
18
+ } | {
19
+ type: "AUTH_CRAM_MD5_RESPONSE";
20
+ response: string;
21
+ } | {
22
+ type: "AUTH_XOAUTH2";
23
+ xoauth2String: string;
24
+ } | {
25
+ type: "MAIL_FROM";
26
+ address: string;
27
+ } | {
28
+ type: "RCPT_TO";
29
+ address: string;
30
+ } | {
31
+ type: "DATA";
32
+ } | {
33
+ type: "DATA_BODY";
34
+ content: Uint8Array;
35
+ } | {
36
+ type: "QUIT";
37
+ } | {
38
+ type: "RSET";
39
+ } | {
40
+ type: "NOOP";
41
+ };
42
+ /** Parsed SMTP server response. */
43
+ export interface SMTPResponse {
44
+ code: number;
45
+ message: string;
46
+ isSuccess: boolean;
47
+ isReady: boolean;
48
+ isError: boolean;
49
+ }
50
+ /** SMTP protocol error with server response details. */
51
+ export declare class SMTPError extends Error {
52
+ readonly code: number;
53
+ readonly command: string;
54
+ readonly response: string;
55
+ /** Creates an SMTP protocol error. */
56
+ constructor(message: string, code: number, command: string, response: string);
57
+ }
58
+ /**
59
+ * Encode an SMTPCommand into a Uint8Array for sending over the socket.
60
+ */
61
+ export declare function encodeCommand(cmd: SMTPCommand): Uint8Array;
62
+ /**
63
+ * Parse raw bytes from the server into an SMTPResponse.
64
+ */
65
+ export declare function parseResponse(data: Uint8Array): SMTPResponse;
66
+ /**
67
+ * Accumulate byte chunks until a complete SMTP response is received.
68
+ */
69
+ export declare function accumulateResponse(chunks: Uint8Array[]): Uint8Array | null;
70
+ /**
71
+ * Select the best AUTH method from EHLO capability lines.
72
+ * Priority: XOAUTH2 > CRAM-MD5 > LOGIN > PLAIN.
73
+ */
74
+ export declare function selectAuthMethod(capabilities: string[]): "LOGIN" | "PLAIN" | "CRAM-MD5" | "OAUTH2";
75
+ /**
76
+ * Parse an EHLO multi-line response and extract capability keywords.
77
+ */
78
+ export declare function parseEHLO(response: SMTPResponse): string[];
79
+ /**
80
+ * Assert that an SMTPResponse code is within the expected set.
81
+ */
82
+ export declare function assertResponse(response: SMTPResponse, expectedCodes: number[], command: string): void;
83
+ /** Encode AUTH LOGIN password step (second base64 chunk). */
84
+ export declare function encodeAuthLoginPass(pass: string): Uint8Array;
85
+ /** Encode AUTH LOGIN user step when sent separately after 334. */
86
+ export declare function encodeAuthLoginUser(user: string): Uint8Array;
87
+ /** Encode CRAM-MD5 response after challenge. */
88
+ export declare function encodeAuthCramResponse(response: string): Uint8Array;
89
+ /** Encode raw SMTP line with CRLF. */
90
+ export declare function encodeLine(line: string): Uint8Array;
@@ -0,0 +1,291 @@
1
+ /** A single email address with optional display name. */
2
+ export interface Address {
3
+ name?: string;
4
+ address: string;
5
+ }
6
+ /** Flexible address input accepted by mail APIs. */
7
+ export type AddressInput = string | Address | (string | Address)[];
8
+ /** Email attachment (in-memory or file path on supported runtimes). */
9
+ export interface Attachment {
10
+ filename: string;
11
+ content?: Uint8Array | string;
12
+ path?: string;
13
+ contentType?: string;
14
+ encoding?: "base64" | "7bit" | "8bit" | "binary" | "quoted-printable";
15
+ contentId?: string;
16
+ inline?: boolean;
17
+ headers?: Record<string, string>;
18
+ }
19
+ /** Options for composing and sending an email message. */
20
+ export interface MailOptions {
21
+ from: AddressInput;
22
+ to: AddressInput;
23
+ cc?: AddressInput;
24
+ bcc?: AddressInput;
25
+ replyTo?: AddressInput;
26
+ subject: string;
27
+ text?: string;
28
+ html?: string;
29
+ attachments?: Attachment[];
30
+ headers?: Record<string, string>;
31
+ messageId?: string;
32
+ date?: Date;
33
+ priority?: "high" | "normal" | "low";
34
+ encoding?: "utf-8" | "ascii";
35
+ /** Template name registered with templatePlugin */
36
+ template?: string;
37
+ /** Template variables passed to the rendering engine */
38
+ data?: Record<string, unknown>;
39
+ }
40
+ /** Result returned after a message is accepted for delivery. */
41
+ export interface SendResult {
42
+ messageId: string;
43
+ accepted: string[];
44
+ rejected: string[];
45
+ response: string;
46
+ envelope: Envelope;
47
+ }
48
+ /** SMTP envelope addresses (MAIL FROM / RCPT TO). */
49
+ export interface Envelope {
50
+ from: string;
51
+ to: string[];
52
+ }
53
+ /** Runtime-specific TCP/TLS socket abstraction for SMTP. */
54
+ export interface SocketAdapter {
55
+ connect(host: string, port: number): Promise<void>;
56
+ startTLS(options?: TLSOptions): Promise<void>;
57
+ write(data: Uint8Array): Promise<void>;
58
+ read(): AsyncIterable<Uint8Array>;
59
+ close(): Promise<void>;
60
+ readonly secure: boolean;
61
+ readonly connected: boolean;
62
+ }
63
+ /** TLS connection options for STARTTLS and direct TLS. */
64
+ export interface TLSOptions {
65
+ rejectUnauthorized?: boolean;
66
+ servername?: string;
67
+ /** Minimum TLS version. Useful for legacy SMTP servers still on TLS 1.1. */
68
+ minVersion?: "TLSv1" | "TLSv1.1" | "TLSv1.2" | "TLSv1.3";
69
+ }
70
+ /** Result returned by transport and mailer verify() calls. */
71
+ export interface VerifyResult {
72
+ ok: boolean;
73
+ provider: string;
74
+ /** Human-readable status message from the provider */
75
+ message?: string;
76
+ /** Raw provider response (provider-specific) */
77
+ raw?: unknown;
78
+ }
79
+ /** Pluggable mail delivery backend (SMTP, HTTP API, etc.). */
80
+ export interface Transport {
81
+ send(options: MailOptions): Promise<SendResult>;
82
+ verify?(): Promise<VerifyResult>;
83
+ close?(): Promise<void>;
84
+ }
85
+ /** DKIM signing configuration for outbound messages. */
86
+ export interface DKIMConfig {
87
+ /** Your domain name. e.g. "example.com" */
88
+ domainName: string;
89
+ /** Key selector. e.g. "2024" → looks up 2024._domainkey.example.com */
90
+ keySelector: string;
91
+ /**
92
+ * DKIM private key in PEM format.
93
+ * Supports RSA (minimum 1024-bit, 2048 recommended) and Ed25519.
94
+ */
95
+ privateKey: string;
96
+ /** Algorithm. Default: "rsa-sha256". Use "ed25519-sha256" for Ed25519 keys. */
97
+ algorithm?: "rsa-sha256" | "ed25519-sha256";
98
+ /**
99
+ * Header fields to sign (colon-separated).
100
+ * Default follows RFC 6376 §5.4 recommendations.
101
+ */
102
+ headerFieldNames?: string;
103
+ /**
104
+ * Skip signing these header fields even if listed in headerFieldNames.
105
+ * Useful to exclude "message-id" and "date" for privacy.
106
+ */
107
+ skipFields?: string;
108
+ }
109
+ /** OAuth2 credentials for XOAUTH2 SMTP authentication. */
110
+ export interface OAuth2Config {
111
+ /** The authenticated user's email address */
112
+ user: string;
113
+ /** OAuth2 client ID */
114
+ clientId: string;
115
+ /** OAuth2 client secret */
116
+ clientSecret: string;
117
+ /** Refresh token for automatic access token renewal */
118
+ refreshToken: string;
119
+ /** Current access token (optional — will be fetched if absent) */
120
+ accessToken?: string;
121
+ /** Token endpoint URL. Default: Google's token endpoint */
122
+ tokenUrl?: string;
123
+ /**
124
+ * Custom token provider function.
125
+ * If provided, clientId / clientSecret / refreshToken are ignored.
126
+ * The function must return a valid access token string.
127
+ */
128
+ getToken?: () => Promise<string>;
129
+ }
130
+ /** Connection pool and rate limiting options for SMTP. */
131
+ export interface PoolConfig {
132
+ /** Use connection pooling. Default: false */
133
+ pool?: boolean;
134
+ /** Maximum number of simultaneous SMTP connections. Default: 5 */
135
+ maxConnections?: number;
136
+ /**
137
+ * Maximum number of messages per connection before it is recycled.
138
+ * Default: 100
139
+ */
140
+ maxMessages?: number;
141
+ /**
142
+ * Maximum send rate in messages per second across all connections.
143
+ * Default: unlimited
144
+ */
145
+ rateDelta?: number;
146
+ /**
147
+ * The time window in milliseconds for rate limiting.
148
+ * Default: 1000 (1 second)
149
+ */
150
+ rateLimit?: number;
151
+ }
152
+ /** Configuration for SMTP transport and relay connections. */
153
+ export interface SMTPConfig extends PoolConfig {
154
+ host: string;
155
+ port?: number;
156
+ secure?: boolean;
157
+ auth?: SMTPAuth;
158
+ tls?: TLSOptions;
159
+ connectionTimeout?: number;
160
+ greetingTimeout?: number;
161
+ socketTimeout?: number;
162
+ direct?: boolean;
163
+ adapter?: SocketAdapter;
164
+ dkim?: DKIMConfig;
165
+ /** Plugins run sequentially before message construction. */
166
+ plugins?: MailPlugin[];
167
+ }
168
+ /** SMTP authentication credentials and method hint. */
169
+ export interface SMTPAuth {
170
+ user: string;
171
+ pass?: string;
172
+ type?: "LOGIN" | "PLAIN" | "CRAM-MD5" | "OAUTH2";
173
+ oauth2?: OAuth2Config;
174
+ }
175
+ /** High-level mailer API wrapping a transport. */
176
+ export interface Mailer {
177
+ send(options: MailOptions): Promise<SendResult>;
178
+ sendBulk(messages: MailOptions[], options?: BulkSendOptions): Promise<BulkSendResult>;
179
+ verify(): Promise<VerifyResult>;
180
+ close(): Promise<void>;
181
+ }
182
+ /** Options for batch sending multiple messages. */
183
+ export interface BulkSendOptions {
184
+ /** Callback fired after each successful send */
185
+ onSuccess?: (message: MailOptions, index: number, result: SendResult) => void;
186
+ /** Callback fired after each failed send (does not throw) */
187
+ onError?: (message: MailOptions, index: number, error: unknown) => void;
188
+ /** Max concurrent sends. Defaults to pool maxConnections or 1 */
189
+ concurrency?: number;
190
+ }
191
+ /** Result of a batch send operation. */
192
+ export interface BulkSendResult {
193
+ /** Total messages attempted */
194
+ total: number;
195
+ /** Number of successful sends */
196
+ sent: number;
197
+ /** Number of failed sends */
198
+ failed: number;
199
+ /** Per-message results in input order */
200
+ results: Array<{
201
+ status: "sent";
202
+ result: SendResult;
203
+ } | {
204
+ status: "failed";
205
+ error: unknown;
206
+ }>;
207
+ }
208
+ /** Configuration for RetryTransport backoff and retry rules. */
209
+ export interface RetryConfig {
210
+ /** Maximum number of total attempts (including first). Default: 3 */
211
+ maxAttempts?: number;
212
+ /** Backoff strategy. Default: 'exponential' */
213
+ backoff?: "exponential" | "linear" | "fixed";
214
+ /** Base delay in ms. Default: 1000 */
215
+ baseDelay?: number;
216
+ /**
217
+ * HTTP status codes to retry on (for HTTP transports).
218
+ * Default: [429, 500, 502, 503, 504]
219
+ */
220
+ retryOn?: number[];
221
+ /** Optional callback called before each retry */
222
+ onRetry?: (attempt: number, error: unknown) => void;
223
+ }
224
+ /** Configuration for PreviewTransport disk output. */
225
+ export interface PreviewConfig {
226
+ /**
227
+ * Directory to write .eml files.
228
+ * Default: './.emails'
229
+ */
230
+ outDir?: string;
231
+ /**
232
+ * Open the email in the default browser after writing.
233
+ * Uses 'open' on macOS, 'xdg-open' on Linux, 'start' on Windows.
234
+ * Default: false
235
+ */
236
+ open?: boolean;
237
+ /**
238
+ * File format to write.
239
+ * 'eml' = raw MIME, 'html' = HTML body only (for quick preview)
240
+ * Default: 'eml'
241
+ */
242
+ format?: "eml" | "html";
243
+ }
244
+ /** Options for {@link createMailer} — custom transport or SMTP config. */
245
+ export type CreateMailerOptions = ({
246
+ transport: Transport;
247
+ plugins?: MailPlugin[];
248
+ } & Partial<SMTPConfig>) | SMTPConfig;
249
+ /**
250
+ * A mail plugin transforms MailOptions before the message is built.
251
+ * Plugins run sequentially. Each receives the output of the previous.
252
+ * Return a new MailOptions object — do not mutate the input.
253
+ *
254
+ * @example
255
+ * ```ts
256
+ * const addFooter = (options: MailOptions): MailOptions => ({
257
+ * ...options,
258
+ * html: options.html + '<p>Unsubscribe</p>',
259
+ * })
260
+ * ```
261
+ */
262
+ export type MailPlugin = ((options: MailOptions) => MailOptions) | ((options: MailOptions) => Promise<MailOptions>);
263
+ /** Mailgun HTTP API configuration. */
264
+ export interface MailgunConfig {
265
+ /** Mailgun API key (starts with "key-") */
266
+ apiKey: string;
267
+ /** Your Mailgun sending domain (e.g. "mg.example.com") */
268
+ domain: string;
269
+ /** API region. Default: 'us' (api.mailgun.net). Use 'eu' for api.eu.mailgun.net */
270
+ region?: "us" | "eu";
271
+ }
272
+ /** AWS SES v2 HTTP API configuration. */
273
+ export interface SESConfig {
274
+ /** AWS Access Key ID */
275
+ accessKeyId: string;
276
+ /** AWS Secret Access Key */
277
+ secretAccessKey: string;
278
+ /** AWS Region. Default: 'us-east-1' */
279
+ region?: string;
280
+ /** Optional session token for temporary credentials */
281
+ sessionToken?: string;
282
+ /** DKIM signing for raw MIME messages (attachment sends) */
283
+ dkim?: DKIMConfig;
284
+ }
285
+ /** Brevo (formerly Sendinblue) HTTP API configuration. */
286
+ export interface BrevoConfig {
287
+ /** Brevo (formerly Sendinblue) API key */
288
+ apiKey: string;
289
+ }
290
+ /** Detected JavaScript runtime environment. */
291
+ export type Runtime = "node" | "bun" | "deno" | "cf-workers" | "browser" | "unknown";
@@ -0,0 +1,15 @@
1
+ import type { CreateMailerOptions, Mailer, Runtime, SocketAdapter, TLSOptions } from "./core/types.js";
2
+ /** Detect the current JavaScript runtime. */
3
+ export declare function detectRuntime(): Runtime;
4
+ /**
5
+ * Dynamically import and instantiate the correct adapter for the current runtime.
6
+ */
7
+ export declare function createDefaultAdapter(options?: {
8
+ secure?: boolean;
9
+ connectionTimeout?: number;
10
+ tls?: TLSOptions;
11
+ }): Promise<SocketAdapter>;
12
+ /**
13
+ * Create a ready-to-use Mailer instance.
14
+ */
15
+ export declare function createMailer(options: CreateMailerOptions): Promise<Mailer>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @module
3
+ * Main sently entrypoint — runtime detection, mailer factory, and shared types.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { createMailer } from "sently";
8
+ *
9
+ * const mailer = await createMailer({
10
+ * host: "smtp.example.com",
11
+ * auth: { user: "you@example.com", pass: "secret" },
12
+ * });
13
+ *
14
+ * await mailer.send({
15
+ * from: "you@example.com",
16
+ * to: "recipient@example.com",
17
+ * subject: "Hello",
18
+ * text: "Plain text body",
19
+ * });
20
+ * ```
21
+ */
22
+ export { GOOGLE_TOKEN_URL, MICROSOFT_TOKEN_URL, OAuth2Client } from "./auth/oauth2.js";
23
+ export { SMTPError } from "./core/smtp.js";
24
+ export type { Address, AddressInput, Attachment, BrevoConfig, BulkSendOptions, BulkSendResult, CreateMailerOptions, DKIMConfig, Envelope, Mailer, MailgunConfig, MailOptions, MailPlugin, OAuth2Config, PoolConfig, PreviewConfig, RetryConfig, Runtime, SESConfig, SendResult, SMTPAuth, SMTPConfig, SocketAdapter, TLSOptions, Transport, VerifyResult, } from "./core/types.js";
25
+ export { createMailer, detectRuntime } from "./detect.js";
26
+ export type { TemplateEngine, TemplatePluginConfig } from "./plugins/template.js";
27
+ export { simpleEngine, templatePlugin } from "./plugins/template.js";
28
+ export { SMTPPool } from "./pool/pool.js";
29
+ export { BrevoError, BrevoTransport } from "./transports/brevo.js";
30
+ export { MailgunError, MailgunTransport } from "./transports/mailgun.js";
31
+ export { PostmarkError, PostmarkTransport } from "./transports/postmark.js";
32
+ export { PreviewTransport } from "./transports/preview.js";
33
+ export { ResendError, ResendTransport } from "./transports/resend.js";
34
+ export { RetryTransport } from "./transports/retry.js";
35
+ export { SendGridError, SendGridTransport } from "./transports/sendgrid.js";
36
+ export { SESError, SESTransport } from "./transports/ses.js";
37
+ export { SMTPTransport } from "./transports/smtp.js";
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ import"./chunk-v0bahtg2.js";
2
+ export {
3
+ templatePlugin,
4
+ simpleEngine,
5
+ detectRuntime,
6
+ createMailer,
7
+ SendGridTransport,
8
+ SendGridError,
9
+ SMTPTransport,
10
+ SMTPPool,
11
+ SMTPError,
12
+ SESTransport,
13
+ SESError,
14
+ RetryTransport,
15
+ ResendTransport,
16
+ ResendError,
17
+ PreviewTransport,
18
+ PostmarkTransport,
19
+ PostmarkError,
20
+ OAuth2Client,
21
+ MailgunTransport,
22
+ MailgunError,
23
+ MICROSOFT_TOKEN_URL,
24
+ GOOGLE_TOKEN_URL,
25
+ BrevoTransport,
26
+ BrevoError
27
+ };
28
+
29
+ //# debugId=5A145E20F9DF002A64756E2164756E21
@@ -4,6 +4,6 @@
4
4
  "sourcesContent": [
5
5
  ],
6
6
  "mappings": "",
7
- "debugId": "51809D75143CBC6864756E2164756E21",
7
+ "debugId": "5A145E20F9DF002A64756E2164756E21",
8
8
  "names": []
9
9
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @module
3
+ * Template plugin for sently.
4
+ * Renders HTML email templates using a pluggable engine.
5
+ * Built-in: simple {{variable}} interpolation (zero dependencies).
6
+ * Bring your own engine: pass any render function.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { templatePlugin, simpleEngine } from 'sently/plugins/template'
11
+ *
12
+ * const mailer = await createMailer({
13
+ * transport: new ResendTransport({ apiKey }),
14
+ * plugins: [
15
+ * templatePlugin({
16
+ * engine: simpleEngine,
17
+ * templates: {
18
+ * welcome: '<h1>Hello, {{name}}!</h1>',
19
+ * reset: '<p>Reset link: {{link}}</p>',
20
+ * },
21
+ * }),
22
+ * ],
23
+ * })
24
+ *
25
+ * await mailer.send({
26
+ * from: '...',
27
+ * to: '...',
28
+ * subject: 'Welcome!',
29
+ * template: 'welcome',
30
+ * data: { name: 'Ali' },
31
+ * })
32
+ * ```
33
+ */
34
+ import type { MailPlugin } from "../core/types.js";
35
+ /** Renders a template string with the given data object. */
36
+ export type TemplateEngine = (template: string, data: Record<string, unknown>) => string;
37
+ /** Configuration for the template plugin. */
38
+ export interface TemplatePluginConfig {
39
+ /**
40
+ * Rendering function. Use `simpleEngine` for zero-dep {{var}} interpolation,
41
+ * or pass any function: (template, data) => string.
42
+ */
43
+ engine: TemplateEngine;
44
+ /**
45
+ * Map of template names to HTML strings.
46
+ * Templates are loaded once at plugin creation time.
47
+ */
48
+ templates: Record<string, string>;
49
+ }
50
+ /**
51
+ * Built-in zero-dependency template engine.
52
+ * Replaces {{variable}} with the matching value from data.
53
+ * Unknown variables are replaced with an empty string.
54
+ */
55
+ export declare function simpleEngine(template: string, data: Record<string, unknown>): string;
56
+ /**
57
+ * Create a template plugin from the given config.
58
+ * The plugin reads options.template and options.data, renders the HTML,
59
+ * sets options.html, and removes template/data before passing to the next plugin.
60
+ */
61
+ export declare function templatePlugin(config: TemplatePluginConfig): MailPlugin;
@@ -0,0 +1,29 @@
1
+ import"../chunk-v0bahtg2.js";
2
+
3
+ // src/plugins/template.ts
4
+ function simpleEngine(template, data) {
5
+ return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
6
+ const val = data[key];
7
+ return val !== undefined && val !== null ? String(val) : "";
8
+ });
9
+ }
10
+ function templatePlugin(config) {
11
+ return (options) => {
12
+ if (!options.template) {
13
+ return options;
14
+ }
15
+ const tmpl = config.templates[options.template];
16
+ if (!tmpl) {
17
+ throw new Error(`sently: template "${options.template}" not found`);
18
+ }
19
+ const html = config.engine(tmpl, options.data ?? {});
20
+ const { template: _template, data: _data, ...rest } = options;
21
+ return { ...rest, html };
22
+ };
23
+ }
24
+ export {
25
+ templatePlugin,
26
+ simpleEngine
27
+ };
28
+
29
+ //# debugId=BF4AF728A3AA093064756E2164756E21