@igniter-js/mail 0.1.0 → 0.1.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/AGENTS.md +54 -41
- package/CHANGELOG.md +7 -8
- package/README.md +55 -44
- package/dist/adapter-BhnIsrlh.d.mts +60 -0
- package/dist/adapter-BhnIsrlh.d.ts +60 -0
- package/dist/adapters/index.d.mts +229 -0
- package/dist/adapters/index.d.ts +229 -0
- package/dist/adapters/index.js +371 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/index.mjs +361 -0
- package/dist/adapters/index.mjs.map +1 -0
- package/dist/index.d.mts +73 -267
- package/dist/index.d.ts +73 -267
- package/dist/index.js +612 -594
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +609 -580
- package/dist/index.mjs.map +1 -1
- package/dist/shim.d.mts +42 -0
- package/dist/shim.d.ts +42 -0
- package/dist/shim.js +83 -0
- package/dist/shim.js.map +1 -0
- package/dist/shim.mjs +72 -0
- package/dist/shim.mjs.map +1 -0
- package/dist/telemetry/index.d.mts +59 -0
- package/dist/telemetry/index.d.ts +59 -0
- package/dist/telemetry/index.js +71 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/index.mjs +69 -0
- package/dist/telemetry/index.mjs.map +1 -0
- package/package.json +27 -2
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import nodemailer from 'nodemailer';
|
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { render } from '@react-email/components';
|
|
6
6
|
|
|
7
|
-
// src/errors/
|
|
7
|
+
// src/errors/mail.error.ts
|
|
8
8
|
var IgniterMailError = class _IgniterMailError extends IgniterError {
|
|
9
9
|
constructor(payload) {
|
|
10
10
|
super({
|
|
@@ -27,560 +27,375 @@ var IgniterMailError = class _IgniterMailError extends IgniterError {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
// src/adapters/postmark.adapter.ts
|
|
30
|
-
var
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
var PostmarkMailAdapter = class _PostmarkMailAdapter {
|
|
31
|
+
/**
|
|
32
|
+
* Creates a new adapter instance.
|
|
33
|
+
*
|
|
34
|
+
* @param credentials - Provider credentials (secret/from).
|
|
35
|
+
* @throws {IgniterMailError} Does not throw on creation; errors surface on send.
|
|
36
|
+
*/
|
|
37
|
+
constructor(credentials = {}) {
|
|
38
|
+
this.credentials = credentials;
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Creates a new adapter instance.
|
|
42
|
+
*
|
|
43
|
+
* @param credentials - Provider credentials (secret/from).
|
|
44
|
+
* @returns A Postmark adapter instance.
|
|
45
|
+
* @throws {IgniterMailError} Does not throw on creation; errors surface on send.
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const adapter = PostmarkMailAdapter.create({ secret: 'token', from: 'no-reply@acme.com' })
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
static create(credentials) {
|
|
52
|
+
return new _PostmarkMailAdapter(credentials);
|
|
44
53
|
}
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Sends an email using Postmark (HTTP API).
|
|
56
|
+
*
|
|
57
|
+
* @param params - Normalized email parameters.
|
|
58
|
+
* @returns A promise that resolves when the email is accepted.
|
|
59
|
+
* @throws {IgniterMailError} When configuration is invalid or Postmark rejects the request.
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* await adapter.send({ to: 'user@example.com', subject: 'Hello', html: '<p>Hi</p>', text: 'Hi' })
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
async send(params) {
|
|
66
|
+
if (!this.credentials.secret) {
|
|
48
67
|
throw new IgniterMailError({
|
|
49
68
|
code: "MAIL_ADAPTER_CONFIGURATION_INVALID",
|
|
50
69
|
message: "Postmark adapter secret is required"
|
|
51
70
|
});
|
|
52
71
|
}
|
|
53
|
-
if (!this.from) {
|
|
72
|
+
if (!this.credentials.from) {
|
|
54
73
|
throw new IgniterMailError({
|
|
55
74
|
code: "MAIL_ADAPTER_CONFIGURATION_INVALID",
|
|
56
75
|
message: "Postmark adapter from is required"
|
|
57
76
|
});
|
|
58
77
|
}
|
|
59
|
-
const token = this.secret;
|
|
60
|
-
const from = this.from;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
metadata: {
|
|
85
|
-
status: response.status,
|
|
86
|
-
body
|
|
87
|
-
}
|
|
88
|
-
});
|
|
78
|
+
const token = this.credentials.secret;
|
|
79
|
+
const from = this.credentials.from;
|
|
80
|
+
const response = await fetch("https://api.postmarkapp.com/email", {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: {
|
|
83
|
+
Accept: "application/json",
|
|
84
|
+
"Content-Type": "application/json",
|
|
85
|
+
"X-Postmark-Server-Token": token
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
From: from,
|
|
89
|
+
To: params.to,
|
|
90
|
+
Subject: params.subject,
|
|
91
|
+
HtmlBody: params.html,
|
|
92
|
+
TextBody: params.text
|
|
93
|
+
})
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
const body = await response.text().catch(() => "");
|
|
97
|
+
throw new IgniterMailError({
|
|
98
|
+
code: "MAIL_PROVIDER_SEND_FAILED",
|
|
99
|
+
message: "Postmark send failed",
|
|
100
|
+
metadata: {
|
|
101
|
+
status: response.status,
|
|
102
|
+
body
|
|
89
103
|
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
92
106
|
}
|
|
93
107
|
};
|
|
94
|
-
var
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this.
|
|
103
|
-
return this;
|
|
108
|
+
var ResendMailAdapter = class _ResendMailAdapter {
|
|
109
|
+
/**
|
|
110
|
+
* Creates an adapter with credentials.
|
|
111
|
+
*
|
|
112
|
+
* @param credentials - Adapter credentials including API secret and default from.
|
|
113
|
+
* @throws {IgniterMailError} Does not throw on creation; errors surface on send.
|
|
114
|
+
*/
|
|
115
|
+
constructor(credentials = {}) {
|
|
116
|
+
this.credentials = credentials;
|
|
104
117
|
}
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Creates a new adapter instance.
|
|
120
|
+
*
|
|
121
|
+
* @param credentials - Adapter credentials including API secret and default from.
|
|
122
|
+
* @returns A configured Resend adapter.
|
|
123
|
+
* @throws {IgniterMailError} Does not throw on creation; errors surface on send.
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* const adapter = ResendMailAdapter.create({ secret: 'token', from: 'no-reply@acme.com' })
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
static create(credentials) {
|
|
130
|
+
return new _ResendMailAdapter(credentials);
|
|
109
131
|
}
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Sends an email using Resend.
|
|
134
|
+
*
|
|
135
|
+
* @param params - Email payload to send.
|
|
136
|
+
* @returns Resolves when the email is accepted by Resend.
|
|
137
|
+
* @throws {IgniterMailError} When credentials are missing or Resend rejects the request.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* await adapter.send({ to: 'user@example.com', subject: 'Welcome', html: '<p>Hi</p>', text: 'Hi' })
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
async send(params) {
|
|
145
|
+
if (!this.credentials.secret) {
|
|
113
146
|
throw new IgniterMailError({
|
|
114
147
|
code: "MAIL_ADAPTER_CONFIGURATION_INVALID",
|
|
115
148
|
message: "Resend adapter secret is required"
|
|
116
149
|
});
|
|
117
150
|
}
|
|
118
|
-
if (!this.from) {
|
|
151
|
+
if (!this.credentials.from) {
|
|
119
152
|
throw new IgniterMailError({
|
|
120
153
|
code: "MAIL_ADAPTER_CONFIGURATION_INVALID",
|
|
121
154
|
message: "Resend adapter from is required"
|
|
122
155
|
});
|
|
123
156
|
}
|
|
124
|
-
const resend = new Resend(this.secret);
|
|
125
|
-
const from = this.from;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
to,
|
|
135
|
-
from,
|
|
136
|
-
subject,
|
|
137
|
-
html,
|
|
138
|
-
text,
|
|
139
|
-
scheduledAt: scheduledAt?.toISOString()
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
};
|
|
157
|
+
const resend = new Resend(this.credentials.secret);
|
|
158
|
+
const from = this.credentials.from;
|
|
159
|
+
await resend.emails.create({
|
|
160
|
+
to: params.to,
|
|
161
|
+
from,
|
|
162
|
+
subject: params.subject,
|
|
163
|
+
html: params.html,
|
|
164
|
+
text: params.text,
|
|
165
|
+
scheduledAt: params.scheduledAt?.toISOString()
|
|
166
|
+
});
|
|
143
167
|
}
|
|
144
168
|
};
|
|
145
|
-
var resendAdapter = (options) => ResendMailAdapterBuilder.create().withSecret(options.secret).withFrom(options.from).build();
|
|
146
169
|
|
|
147
170
|
// src/adapters/sendgrid.adapter.ts
|
|
148
|
-
var
|
|
149
|
-
/**
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
171
|
+
var SendGridMailAdapter = class _SendGridMailAdapter {
|
|
172
|
+
/**
|
|
173
|
+
* Creates an adapter with credentials.
|
|
174
|
+
*
|
|
175
|
+
* @param credentials - Adapter credentials including API secret and default from.
|
|
176
|
+
* @throws {IgniterMailError} Does not throw on creation; errors surface on send.
|
|
177
|
+
*/
|
|
178
|
+
constructor(credentials = {}) {
|
|
179
|
+
this.credentials = credentials;
|
|
157
180
|
}
|
|
158
|
-
/**
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Creates a new adapter instance.
|
|
183
|
+
*
|
|
184
|
+
* @param credentials - Adapter credentials including API secret and default from.
|
|
185
|
+
* @returns A configured SendGrid adapter.
|
|
186
|
+
* @throws {IgniterMailError} Does not throw on creation; errors surface on send.
|
|
187
|
+
* @example
|
|
188
|
+
* ```ts
|
|
189
|
+
* const adapter = SendGridMailAdapter.create({ secret: 'token', from: 'no-reply@acme.com' })
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
static create(credentials) {
|
|
193
|
+
return new _SendGridMailAdapter(credentials);
|
|
162
194
|
}
|
|
163
|
-
/**
|
|
164
|
-
|
|
165
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Sends an email using SendGrid (HTTP API).
|
|
197
|
+
*
|
|
198
|
+
* @param params - Email payload to send.
|
|
199
|
+
* @returns Resolves when the email is accepted by SendGrid.
|
|
200
|
+
* @throws {IgniterMailError} When credentials are missing or the API fails.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```ts
|
|
204
|
+
* await adapter.send({ to: 'user@example.com', subject: 'Hi', html: '<p>Hi</p>', text: 'Hi' })
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
async send(params) {
|
|
208
|
+
if (!this.credentials.secret) {
|
|
166
209
|
throw new IgniterMailError({
|
|
167
210
|
code: "MAIL_ADAPTER_CONFIGURATION_INVALID",
|
|
168
211
|
message: "SendGrid adapter secret is required"
|
|
169
212
|
});
|
|
170
213
|
}
|
|
171
|
-
if (!this.from) {
|
|
214
|
+
if (!this.credentials.from) {
|
|
172
215
|
throw new IgniterMailError({
|
|
173
216
|
code: "MAIL_ADAPTER_CONFIGURATION_INVALID",
|
|
174
217
|
message: "SendGrid adapter from is required"
|
|
175
218
|
});
|
|
176
219
|
}
|
|
177
|
-
const apiKey = this.secret;
|
|
178
|
-
const from = this.from;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
body
|
|
208
|
-
}
|
|
209
|
-
});
|
|
220
|
+
const apiKey = this.credentials.secret;
|
|
221
|
+
const from = this.credentials.from;
|
|
222
|
+
const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
|
|
223
|
+
method: "POST",
|
|
224
|
+
headers: {
|
|
225
|
+
Authorization: `Bearer ${apiKey}`,
|
|
226
|
+
"Content-Type": "application/json"
|
|
227
|
+
},
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
personalizations: [
|
|
230
|
+
{
|
|
231
|
+
to: [{ email: params.to }]
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
from: { email: from },
|
|
235
|
+
subject: params.subject,
|
|
236
|
+
content: [
|
|
237
|
+
{ type: "text/plain", value: params.text },
|
|
238
|
+
{ type: "text/html", value: params.html }
|
|
239
|
+
]
|
|
240
|
+
})
|
|
241
|
+
});
|
|
242
|
+
if (!response.ok) {
|
|
243
|
+
const body = await response.text().catch(() => "");
|
|
244
|
+
throw new IgniterMailError({
|
|
245
|
+
code: "MAIL_PROVIDER_SEND_FAILED",
|
|
246
|
+
message: "SendGrid send failed",
|
|
247
|
+
metadata: {
|
|
248
|
+
status: response.status,
|
|
249
|
+
body
|
|
210
250
|
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
213
253
|
}
|
|
214
254
|
};
|
|
215
|
-
var
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
this.
|
|
224
|
-
return this;
|
|
255
|
+
var SmtpMailAdapter = class _SmtpMailAdapter {
|
|
256
|
+
/**
|
|
257
|
+
* Creates an adapter with credentials.
|
|
258
|
+
*
|
|
259
|
+
* @param credentials - Adapter credentials including SMTP URL and default from.
|
|
260
|
+
* @throws {IgniterMailError} Does not throw on creation; errors surface on send.
|
|
261
|
+
*/
|
|
262
|
+
constructor(credentials = {}) {
|
|
263
|
+
this.credentials = credentials;
|
|
225
264
|
}
|
|
226
|
-
/**
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Creates a new adapter instance.
|
|
267
|
+
*
|
|
268
|
+
* @param credentials - Adapter credentials including SMTP URL and default from.
|
|
269
|
+
* @returns A configured SMTP adapter.
|
|
270
|
+
* @throws {IgniterMailError} Does not throw on creation; errors surface on send.
|
|
271
|
+
* @example
|
|
272
|
+
* ```ts
|
|
273
|
+
* const adapter = SmtpMailAdapter.create({ secret: 'smtps://user:pass@host:465', from: 'no-reply@acme.com' })
|
|
274
|
+
* ```
|
|
275
|
+
*/
|
|
276
|
+
static create(credentials) {
|
|
277
|
+
return new _SmtpMailAdapter(credentials);
|
|
230
278
|
}
|
|
231
|
-
/**
|
|
232
|
-
|
|
233
|
-
|
|
279
|
+
/**
|
|
280
|
+
* Sends an email using Nodemailer over SMTP.
|
|
281
|
+
*
|
|
282
|
+
* @param params - Email payload to send.
|
|
283
|
+
* @returns Resolves when the email is sent.
|
|
284
|
+
* @throws {IgniterMailError} When credentials are missing or the SMTP send fails.
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```ts
|
|
288
|
+
* await adapter.send({ to: 'user@example.com', subject: 'Hi', html: '<p>Hi</p>', text: 'Hi' })
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
async send(params) {
|
|
292
|
+
if (!this.credentials.secret) {
|
|
234
293
|
throw new IgniterMailError({
|
|
235
294
|
code: "MAIL_ADAPTER_CONFIGURATION_INVALID",
|
|
236
295
|
message: "SMTP adapter secret is required"
|
|
237
296
|
});
|
|
238
297
|
}
|
|
239
|
-
if (!this.from) {
|
|
298
|
+
if (!this.credentials.from) {
|
|
240
299
|
throw new IgniterMailError({
|
|
241
300
|
code: "MAIL_ADAPTER_CONFIGURATION_INVALID",
|
|
242
301
|
message: "SMTP adapter from is required"
|
|
243
302
|
});
|
|
244
303
|
}
|
|
245
|
-
const smtpUrl = this.secret;
|
|
246
|
-
const from = this.from;
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
greetingTimeout: 5e3,
|
|
254
|
-
socketTimeout: 1e4
|
|
255
|
-
});
|
|
256
|
-
};
|
|
257
|
-
return {
|
|
258
|
-
/** Sends an email using Nodemailer over SMTP. */
|
|
259
|
-
send: async ({ to, subject, html, text }) => {
|
|
260
|
-
const transport = createTransporter();
|
|
261
|
-
const mailOptions = {
|
|
262
|
-
from,
|
|
263
|
-
to,
|
|
264
|
-
subject,
|
|
265
|
-
html,
|
|
266
|
-
text
|
|
267
|
-
};
|
|
268
|
-
try {
|
|
269
|
-
await transport.sendMail(mailOptions);
|
|
270
|
-
transport.close();
|
|
271
|
-
} catch (error) {
|
|
272
|
-
transport.close();
|
|
273
|
-
throw error;
|
|
274
|
-
}
|
|
304
|
+
const smtpUrl = this.credentials.secret;
|
|
305
|
+
const from = this.credentials.from;
|
|
306
|
+
const transport = nodemailer.createTransport(smtpUrl, {
|
|
307
|
+
connectionTimeout: 1e4,
|
|
308
|
+
greetingTimeout: 5e3,
|
|
309
|
+
socketTimeout: 1e4,
|
|
310
|
+
tls: {
|
|
311
|
+
rejectUnauthorized: false
|
|
275
312
|
}
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const logger = options.logger ?? console;
|
|
285
|
-
const silent = options.silent ?? false;
|
|
286
|
-
return {
|
|
287
|
-
sent,
|
|
288
|
-
reset: () => {
|
|
289
|
-
sent.length = 0;
|
|
290
|
-
},
|
|
291
|
-
last: () => sent.at(-1),
|
|
292
|
-
send: async (params) => {
|
|
293
|
-
sent.push({ ...params, at: /* @__PURE__ */ new Date() });
|
|
294
|
-
if (!silent) {
|
|
295
|
-
logger.info(
|
|
296
|
-
`[TestMailAdapter] to=${params.to} subject=${params.subject} html=${params.html.length}B text=${params.text.length}B`
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// src/adapters/webhook.adapter.ts
|
|
304
|
-
var WebhookMailAdapterBuilder = class _WebhookMailAdapterBuilder {
|
|
305
|
-
/** Creates a new builder instance. */
|
|
306
|
-
static create() {
|
|
307
|
-
return new _WebhookMailAdapterBuilder();
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Sets the webhook URL.
|
|
311
|
-
*
|
|
312
|
-
* Note: when using `IgniterMailBuilder.withAdapter('webhook', secret)`, the `secret`
|
|
313
|
-
* is treated as the webhook URL.
|
|
314
|
-
*/
|
|
315
|
-
withUrl(url) {
|
|
316
|
-
this.url = url;
|
|
317
|
-
return this;
|
|
318
|
-
}
|
|
319
|
-
/** Sets the default FROM address. */
|
|
320
|
-
withFrom(from) {
|
|
321
|
-
this.from = from;
|
|
322
|
-
return this;
|
|
323
|
-
}
|
|
324
|
-
/** Builds the adapter instance. */
|
|
325
|
-
build() {
|
|
326
|
-
if (!this.url) {
|
|
327
|
-
throw new IgniterMailError({
|
|
328
|
-
code: "MAIL_ADAPTER_CONFIGURATION_INVALID",
|
|
329
|
-
message: "Webhook adapter url is required"
|
|
313
|
+
});
|
|
314
|
+
try {
|
|
315
|
+
await transport.sendMail({
|
|
316
|
+
from,
|
|
317
|
+
to: params.to,
|
|
318
|
+
subject: params.subject,
|
|
319
|
+
html: params.html,
|
|
320
|
+
text: params.text
|
|
330
321
|
});
|
|
331
|
-
}
|
|
332
|
-
if (!this.from) {
|
|
322
|
+
} catch (error) {
|
|
333
323
|
throw new IgniterMailError({
|
|
334
|
-
code: "
|
|
335
|
-
message: "
|
|
324
|
+
code: "MAIL_PROVIDER_SEND_FAILED",
|
|
325
|
+
message: "SMTP send failed",
|
|
326
|
+
metadata: {
|
|
327
|
+
originalError: error
|
|
328
|
+
}
|
|
336
329
|
});
|
|
330
|
+
} finally {
|
|
331
|
+
transport.close();
|
|
337
332
|
}
|
|
338
|
-
const url = this.url;
|
|
339
|
-
const from = this.from;
|
|
340
|
-
return {
|
|
341
|
-
/** Sends an email by POST-ing to the configured webhook URL. */
|
|
342
|
-
send: async ({ to, subject, html, text, scheduledAt }) => {
|
|
343
|
-
const response = await fetch(url, {
|
|
344
|
-
method: "POST",
|
|
345
|
-
headers: {
|
|
346
|
-
"Content-Type": "application/json"
|
|
347
|
-
},
|
|
348
|
-
body: JSON.stringify({
|
|
349
|
-
to,
|
|
350
|
-
from,
|
|
351
|
-
subject,
|
|
352
|
-
html,
|
|
353
|
-
text,
|
|
354
|
-
scheduledAt: scheduledAt?.toISOString()
|
|
355
|
-
})
|
|
356
|
-
});
|
|
357
|
-
if (!response.ok) {
|
|
358
|
-
const body = await response.text().catch(() => "");
|
|
359
|
-
throw new IgniterMailError({
|
|
360
|
-
code: "MAIL_PROVIDER_SEND_FAILED",
|
|
361
|
-
message: "Webhook send failed",
|
|
362
|
-
metadata: {
|
|
363
|
-
status: response.status,
|
|
364
|
-
body
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
};
|
|
370
333
|
}
|
|
371
334
|
};
|
|
372
|
-
var webhookAdapter = (options) => WebhookMailAdapterBuilder.create().withUrl(options.secret).withFrom(options.from).build();
|
|
373
335
|
|
|
374
|
-
// src/
|
|
375
|
-
var
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
withLogger(logger) {
|
|
393
|
-
this.logger = logger;
|
|
394
|
-
return this;
|
|
395
|
-
}
|
|
336
|
+
// src/types/telemetry.ts
|
|
337
|
+
var IGNITER_MAIL_TELEMETRY_EVENTS = {
|
|
338
|
+
/** Emitted when mail send operation starts */
|
|
339
|
+
SEND_STARTED: "igniter.mail.send.started",
|
|
340
|
+
/** Emitted when mail send operation succeeds */
|
|
341
|
+
SEND_SUCCESS: "igniter.mail.send.success",
|
|
342
|
+
/** Emitted when mail send operation fails */
|
|
343
|
+
SEND_ERROR: "igniter.mail.send.error",
|
|
344
|
+
/** Emitted when mail schedule operation starts */
|
|
345
|
+
SCHEDULE_STARTED: "igniter.mail.schedule.started",
|
|
346
|
+
/** Emitted when mail schedule operation succeeds */
|
|
347
|
+
SCHEDULE_SUCCESS: "igniter.mail.schedule.success",
|
|
348
|
+
/** Emitted when mail schedule operation fails */
|
|
349
|
+
SCHEDULE_ERROR: "igniter.mail.schedule.error"
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// src/utils/schema.ts
|
|
353
|
+
var IgniterMailSchema = class {
|
|
396
354
|
/**
|
|
397
|
-
*
|
|
355
|
+
* Validates an unknown input using `StandardSchemaV1` when the schema provides `~standard.validate`.
|
|
398
356
|
*
|
|
399
|
-
* If
|
|
400
|
-
*/
|
|
401
|
-
withQueue(adapter, options) {
|
|
402
|
-
this.queue = { adapter, options };
|
|
403
|
-
return this;
|
|
404
|
-
}
|
|
405
|
-
withAdapter(adapterOrProvider, secret) {
|
|
406
|
-
if (typeof adapterOrProvider === "string") {
|
|
407
|
-
if (!secret) {
|
|
408
|
-
throw new IgniterMailError({
|
|
409
|
-
code: "MAIL_PROVIDER_ADAPTER_SECRET_REQUIRED",
|
|
410
|
-
message: "MAIL_PROVIDER_ADAPTER_SECRET_REQUIRED",
|
|
411
|
-
logger: this.logger
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
this.adapter = {
|
|
415
|
-
kind: "provider",
|
|
416
|
-
provider: adapterOrProvider,
|
|
417
|
-
secret
|
|
418
|
-
};
|
|
419
|
-
return this;
|
|
420
|
-
}
|
|
421
|
-
this.adapter = {
|
|
422
|
-
kind: "adapter",
|
|
423
|
-
adapter: adapterOrProvider
|
|
424
|
-
};
|
|
425
|
-
return this;
|
|
426
|
-
}
|
|
427
|
-
/**
|
|
428
|
-
* Registers a template.
|
|
429
|
-
*/
|
|
430
|
-
addTemplate(key, template) {
|
|
431
|
-
this.templates[key] = template;
|
|
432
|
-
return this;
|
|
433
|
-
}
|
|
434
|
-
/** Hook invoked before sending. */
|
|
435
|
-
withOnSendStarted(onSendStarted) {
|
|
436
|
-
this.onSendStarted = onSendStarted;
|
|
437
|
-
return this;
|
|
438
|
-
}
|
|
439
|
-
/** Hook invoked on error. */
|
|
440
|
-
withOnSendError(onSendError) {
|
|
441
|
-
this.onSendError = onSendError;
|
|
442
|
-
return this;
|
|
443
|
-
}
|
|
444
|
-
/** Hook invoked on success. */
|
|
445
|
-
withOnSendSuccess(onSendSuccess) {
|
|
446
|
-
this.onSendSuccess = onSendSuccess;
|
|
447
|
-
return this;
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Builds the {@link IgniterMail} instance.
|
|
357
|
+
* If the schema does not provide a validator, this method returns the input as-is.
|
|
451
358
|
*/
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
message: "MAIL_PROVIDER_FROM_REQUIRED",
|
|
457
|
-
logger: this.logger
|
|
458
|
-
});
|
|
359
|
+
static async validateInput(schema, input) {
|
|
360
|
+
const standard = schema?.["~standard"];
|
|
361
|
+
if (!standard?.validate) {
|
|
362
|
+
return input;
|
|
459
363
|
}
|
|
460
|
-
|
|
364
|
+
const result = await standard.validate(input);
|
|
365
|
+
if (result?.issues?.length) {
|
|
461
366
|
throw new IgniterMailError({
|
|
462
|
-
code: "
|
|
463
|
-
message: "
|
|
464
|
-
|
|
367
|
+
code: "MAIL_PROVIDER_TEMPLATE_DATA_INVALID",
|
|
368
|
+
message: "Invalid mail template payload",
|
|
369
|
+
statusCode: 400,
|
|
370
|
+
details: result.issues
|
|
465
371
|
});
|
|
466
372
|
}
|
|
467
|
-
|
|
468
|
-
switch (this.adapter.provider) {
|
|
469
|
-
case "resend":
|
|
470
|
-
return ResendMailAdapterBuilder.create().withSecret(this.adapter.secret).withFrom(this.from).build();
|
|
471
|
-
case "smtp":
|
|
472
|
-
return SmtpMailAdapterBuilder.create().withSecret(this.adapter.secret).withFrom(this.from).build();
|
|
473
|
-
case "postmark":
|
|
474
|
-
return PostmarkMailAdapterBuilder.create().withSecret(this.adapter.secret).withFrom(this.from).build();
|
|
475
|
-
case "sendgrid":
|
|
476
|
-
return SendGridMailAdapterBuilder.create().withSecret(this.adapter.secret).withFrom(this.from).build();
|
|
477
|
-
case "webhook":
|
|
478
|
-
return WebhookMailAdapterBuilder.create().withUrl(this.adapter.secret).withFrom(this.from).build();
|
|
479
|
-
default:
|
|
480
|
-
throw new IgniterMailError({
|
|
481
|
-
code: "MAIL_PROVIDER_ADAPTER_NOT_FOUND",
|
|
482
|
-
message: `MAIL_PROVIDER_ADAPTER_NOT_FOUND: ${this.adapter.provider}`,
|
|
483
|
-
logger: this.logger,
|
|
484
|
-
metadata: {
|
|
485
|
-
provider: this.adapter.provider
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
})();
|
|
490
|
-
return this.factory({
|
|
491
|
-
from: this.from,
|
|
492
|
-
adapter: resolvedAdapter,
|
|
493
|
-
templates: this.templates,
|
|
494
|
-
onSendStarted: this.onSendStarted,
|
|
495
|
-
onSendError: this.onSendError,
|
|
496
|
-
onSendSuccess: this.onSendSuccess,
|
|
497
|
-
logger: this.logger,
|
|
498
|
-
queue: this.queue ? {
|
|
499
|
-
adapter: this.queue.adapter,
|
|
500
|
-
id: `${this.queue.options?.namespace ?? "mail"}.${this.queue.options?.task ?? "send"}`,
|
|
501
|
-
options: this.queue.options
|
|
502
|
-
} : void 0
|
|
503
|
-
});
|
|
373
|
+
return result?.value ?? input;
|
|
504
374
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
/** Creates a new builder instance. */
|
|
510
|
-
static create() {
|
|
511
|
-
return new _MailTemplateBuilder();
|
|
512
|
-
}
|
|
513
|
-
/** Sets the default subject for the template. */
|
|
514
|
-
withSubject(subject) {
|
|
515
|
-
this.subject = subject;
|
|
516
|
-
return this;
|
|
517
|
-
}
|
|
518
|
-
/** Attaches the schema used to validate and infer payload types. */
|
|
519
|
-
withSchema(schema) {
|
|
520
|
-
this.schema = schema;
|
|
521
|
-
return this;
|
|
522
|
-
}
|
|
523
|
-
/** Sets the React Email render function for the template. */
|
|
524
|
-
withRender(render2) {
|
|
525
|
-
this.render = render2;
|
|
526
|
-
return this;
|
|
527
|
-
}
|
|
528
|
-
/** Builds the template definition. */
|
|
529
|
-
build() {
|
|
530
|
-
if (!this.subject) {
|
|
531
|
-
throw new IgniterMailError({
|
|
532
|
-
code: "MAIL_TEMPLATE_CONFIGURATION_INVALID",
|
|
533
|
-
message: "Mail template subject is required"
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
if (!this.schema) {
|
|
537
|
-
throw new IgniterMailError({
|
|
538
|
-
code: "MAIL_TEMPLATE_CONFIGURATION_INVALID",
|
|
539
|
-
message: "Mail template schema is required"
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
if (!this.render) {
|
|
543
|
-
throw new IgniterMailError({
|
|
544
|
-
code: "MAIL_TEMPLATE_CONFIGURATION_INVALID",
|
|
545
|
-
message: "Mail template render is required"
|
|
546
|
-
});
|
|
547
|
-
}
|
|
375
|
+
/**
|
|
376
|
+
* Creates a passthrough StandardSchema validator.
|
|
377
|
+
*/
|
|
378
|
+
static createPassthroughSchema() {
|
|
548
379
|
return {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
380
|
+
"~standard": {
|
|
381
|
+
vendor: "@igniter-js/mail",
|
|
382
|
+
version: 1,
|
|
383
|
+
validate: async (value) => ({ value })
|
|
384
|
+
}
|
|
552
385
|
};
|
|
553
386
|
}
|
|
554
387
|
};
|
|
555
388
|
|
|
556
|
-
// src/
|
|
557
|
-
|
|
558
|
-
const standard = schema?.["~standard"];
|
|
559
|
-
if (!standard?.validate) {
|
|
560
|
-
return input;
|
|
561
|
-
}
|
|
562
|
-
const result = await standard.validate(input);
|
|
563
|
-
if (result?.issues?.length) {
|
|
564
|
-
throw new IgniterMailError({
|
|
565
|
-
code: "MAIL_PROVIDER_TEMPLATE_DATA_INVALID",
|
|
566
|
-
message: "Invalid mail template payload",
|
|
567
|
-
statusCode: 400,
|
|
568
|
-
details: result.issues
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
return result?.value ?? input;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// src/core/igniter-mail.tsx
|
|
575
|
-
var _IgniterMail = class _IgniterMail {
|
|
389
|
+
// src/core/manager.tsx
|
|
390
|
+
var IgniterMailManagerCore = class {
|
|
576
391
|
constructor(options) {
|
|
577
392
|
this.queueJobRegistered = false;
|
|
578
393
|
/**
|
|
579
394
|
* Type inference helper.
|
|
580
395
|
* Access via `typeof mail.$Infer` (type-level only).
|
|
581
396
|
*/
|
|
582
|
-
this.$Infer =
|
|
583
|
-
const { adapter, templates, logger, queue, ...rest } = options;
|
|
397
|
+
this.$Infer = {};
|
|
398
|
+
const { adapter, templates, logger, telemetry, queue, ...rest } = options;
|
|
584
399
|
if (!adapter) {
|
|
585
400
|
throw new IgniterMailError({
|
|
586
401
|
code: "MAIL_PROVIDER_ADAPTER_REQUIRED",
|
|
@@ -598,6 +413,7 @@ var _IgniterMail = class _IgniterMail {
|
|
|
598
413
|
this.adapter = adapter;
|
|
599
414
|
this.templates = templates;
|
|
600
415
|
this.logger = logger;
|
|
416
|
+
this.telemetry = telemetry;
|
|
601
417
|
this.queue = queue;
|
|
602
418
|
this.options = rest;
|
|
603
419
|
}
|
|
@@ -611,30 +427,20 @@ var _IgniterMail = class _IgniterMail {
|
|
|
611
427
|
}
|
|
612
428
|
this.queueJobRegistering = (async () => {
|
|
613
429
|
const queueOptions = queue.options;
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
"
|
|
617
|
-
vendor: "@igniter-js/mail",
|
|
618
|
-
version: 1,
|
|
619
|
-
validate: async (value) => ({ value })
|
|
620
|
-
}
|
|
621
|
-
};
|
|
622
|
-
const definition = queue.adapter.register({
|
|
623
|
-
name,
|
|
430
|
+
const passthroughSchema = IgniterMailSchema.createPassthroughSchema();
|
|
431
|
+
queue.adapter.register({
|
|
432
|
+
name: queueOptions?.job ?? "send",
|
|
624
433
|
input: passthroughSchema,
|
|
625
|
-
handler: async ({ input }) => {
|
|
626
|
-
await this.send(input);
|
|
627
|
-
},
|
|
628
|
-
queue: queueOptions?.queue,
|
|
629
434
|
attempts: queueOptions?.attempts,
|
|
630
435
|
priority: queueOptions?.priority,
|
|
631
436
|
removeOnComplete: queueOptions?.removeOnComplete,
|
|
632
437
|
removeOnFail: queueOptions?.removeOnFail,
|
|
633
438
|
metadata: queueOptions?.metadata,
|
|
634
|
-
limiter: queueOptions?.limiter
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
439
|
+
limiter: queueOptions?.limiter,
|
|
440
|
+
queue: { name: queueOptions?.queue ?? "mail" },
|
|
441
|
+
handler: async ({ input }) => {
|
|
442
|
+
await this.send(input);
|
|
443
|
+
}
|
|
638
444
|
});
|
|
639
445
|
this.queueJobRegistered = true;
|
|
640
446
|
})();
|
|
@@ -646,7 +452,7 @@ var _IgniterMail = class _IgniterMail {
|
|
|
646
452
|
}
|
|
647
453
|
async validateTemplateData(template, data) {
|
|
648
454
|
try {
|
|
649
|
-
return await
|
|
455
|
+
return await IgniterMailSchema.validateInput(
|
|
650
456
|
template.schema,
|
|
651
457
|
data
|
|
652
458
|
);
|
|
@@ -665,11 +471,20 @@ var _IgniterMail = class _IgniterMail {
|
|
|
665
471
|
* Sends an email immediately.
|
|
666
472
|
*/
|
|
667
473
|
async send(params) {
|
|
474
|
+
const startTime = Date.now();
|
|
668
475
|
try {
|
|
669
476
|
this.logger?.debug("IgniterMail.send started", {
|
|
670
477
|
to: params.to,
|
|
671
478
|
template: String(params.template)
|
|
672
479
|
});
|
|
480
|
+
this.telemetry?.emit(IGNITER_MAIL_TELEMETRY_EVENTS.SEND_STARTED, {
|
|
481
|
+
level: "debug",
|
|
482
|
+
attributes: {
|
|
483
|
+
"mail.to": params.to,
|
|
484
|
+
"mail.template": String(params.template),
|
|
485
|
+
"mail.subject": params.subject
|
|
486
|
+
}
|
|
487
|
+
});
|
|
673
488
|
await this.onSendStarted(params);
|
|
674
489
|
const template = this.templates[params.template];
|
|
675
490
|
if (!template) {
|
|
@@ -698,9 +513,20 @@ var _IgniterMail = class _IgniterMail {
|
|
|
698
513
|
text
|
|
699
514
|
});
|
|
700
515
|
await this.onSendSuccess(params);
|
|
516
|
+
const durationMs = Date.now() - startTime;
|
|
517
|
+
this.telemetry?.emit(IGNITER_MAIL_TELEMETRY_EVENTS.SEND_SUCCESS, {
|
|
518
|
+
level: "info",
|
|
519
|
+
attributes: {
|
|
520
|
+
"mail.to": params.to,
|
|
521
|
+
"mail.template": String(params.template),
|
|
522
|
+
"mail.subject": params.subject || template.subject,
|
|
523
|
+
"mail.duration_ms": durationMs
|
|
524
|
+
}
|
|
525
|
+
});
|
|
701
526
|
this.logger?.info("IgniterMail.send success", {
|
|
702
527
|
to: params.to,
|
|
703
|
-
template: String(params.template)
|
|
528
|
+
template: String(params.template),
|
|
529
|
+
durationMs
|
|
704
530
|
});
|
|
705
531
|
} catch (error) {
|
|
706
532
|
const normalizedError = IgniterMailError.is(error) ? error : new IgniterMailError({
|
|
@@ -713,6 +539,18 @@ var _IgniterMail = class _IgniterMail {
|
|
|
713
539
|
template: String(params.template)
|
|
714
540
|
}
|
|
715
541
|
});
|
|
542
|
+
const durationMs = Date.now() - startTime;
|
|
543
|
+
this.telemetry?.emit(IGNITER_MAIL_TELEMETRY_EVENTS.SEND_ERROR, {
|
|
544
|
+
level: "error",
|
|
545
|
+
attributes: {
|
|
546
|
+
"mail.to": params.to,
|
|
547
|
+
"mail.template": String(params.template),
|
|
548
|
+
"mail.subject": params.subject,
|
|
549
|
+
"mail.error.code": normalizedError.code,
|
|
550
|
+
"mail.error.message": normalizedError.message,
|
|
551
|
+
"mail.duration_ms": durationMs
|
|
552
|
+
}
|
|
553
|
+
});
|
|
716
554
|
this.logger?.error("IgniterMail.send failed", normalizedError);
|
|
717
555
|
await this.onSendError(params, normalizedError);
|
|
718
556
|
throw normalizedError;
|
|
@@ -721,10 +559,10 @@ var _IgniterMail = class _IgniterMail {
|
|
|
721
559
|
/**
|
|
722
560
|
* Schedules an email for a future date.
|
|
723
561
|
*
|
|
724
|
-
*
|
|
725
|
-
* Otherwise, it uses a best-effort `setTimeout`.
|
|
562
|
+
* Requires a queue adapter; otherwise it throws.
|
|
726
563
|
*/
|
|
727
564
|
async schedule(params, date) {
|
|
565
|
+
const startTime = Date.now();
|
|
728
566
|
if (date.getTime() <= Date.now()) {
|
|
729
567
|
throw new IgniterMailError({
|
|
730
568
|
code: "MAIL_PROVIDER_SCHEDULE_DATE_INVALID",
|
|
@@ -732,24 +570,60 @@ var _IgniterMail = class _IgniterMail {
|
|
|
732
570
|
logger: this.logger
|
|
733
571
|
});
|
|
734
572
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
573
|
+
const delay = Math.max(0, date.getTime() - Date.now());
|
|
574
|
+
this.logger?.debug("IgniterMail.schedule started", {
|
|
575
|
+
to: params.to,
|
|
576
|
+
template: String(params.template),
|
|
577
|
+
scheduledAt: date.toISOString(),
|
|
578
|
+
delayMs: delay
|
|
579
|
+
});
|
|
580
|
+
this.telemetry?.emit(IGNITER_MAIL_TELEMETRY_EVENTS.SCHEDULE_STARTED, {
|
|
581
|
+
level: "debug",
|
|
582
|
+
attributes: {
|
|
583
|
+
"mail.to": params.to,
|
|
584
|
+
"mail.template": String(params.template),
|
|
585
|
+
"mail.scheduled_at": date.toISOString(),
|
|
586
|
+
"mail.delay_ms": delay
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
if (!this.queue) {
|
|
590
|
+
throw new IgniterMailError({
|
|
591
|
+
code: "MAIL_PROVIDER_SCHEDULE_QUEUE_NOT_CONFIGURED",
|
|
592
|
+
message: "MAIL_PROVIDER_SCHEDULE_QUEUE_NOT_CONFIGURED",
|
|
593
|
+
logger: this.logger
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
await this.ensureQueueJobRegistered();
|
|
598
|
+
const queue = this.queue.options?.queue ?? "mail";
|
|
599
|
+
const job = this.queue.options?.job ?? "send";
|
|
600
|
+
const id = `${queue}.${job}`;
|
|
601
|
+
this.logger?.info("IgniterMail.schedule enqueued", {
|
|
602
|
+
to: params.to,
|
|
603
|
+
template: String(params.template),
|
|
604
|
+
delay,
|
|
605
|
+
durationMs: Date.now() - startTime
|
|
606
|
+
});
|
|
607
|
+
await this.queue.adapter.invoke({
|
|
608
|
+
id,
|
|
609
|
+
input: params,
|
|
610
|
+
delay
|
|
611
|
+
});
|
|
612
|
+
this.telemetry?.emit(IGNITER_MAIL_TELEMETRY_EVENTS.SCHEDULE_SUCCESS, {
|
|
613
|
+
level: "info",
|
|
614
|
+
attributes: {
|
|
615
|
+
"mail.to": params.to,
|
|
616
|
+
"mail.template": String(params.template),
|
|
617
|
+
"mail.scheduled_at": date.toISOString(),
|
|
618
|
+
"mail.delay_ms": delay,
|
|
619
|
+
"mail.queue_id": this.queue.options?.queue ?? "mail"
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
return;
|
|
623
|
+
} catch (error) {
|
|
624
|
+
let normalizedError = error;
|
|
625
|
+
if (!IgniterMailError.is(error)) {
|
|
626
|
+
normalizedError = new IgniterMailError({
|
|
753
627
|
code: "MAIL_PROVIDER_SCHEDULE_FAILED",
|
|
754
628
|
message: "MAIL_PROVIDER_SCHEDULE_FAILED",
|
|
755
629
|
cause: error,
|
|
@@ -757,19 +631,23 @@ var _IgniterMail = class _IgniterMail {
|
|
|
757
631
|
metadata: {
|
|
758
632
|
to: params.to,
|
|
759
633
|
template: String(params.template),
|
|
760
|
-
|
|
634
|
+
scheduledAt: date.toISOString()
|
|
761
635
|
}
|
|
762
636
|
});
|
|
763
|
-
this.logger?.error("IgniterMail.schedule failed", normalizedError);
|
|
764
|
-
throw normalizedError;
|
|
765
637
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
638
|
+
this.telemetry?.emit(IGNITER_MAIL_TELEMETRY_EVENTS.SCHEDULE_ERROR, {
|
|
639
|
+
level: "error",
|
|
640
|
+
attributes: {
|
|
641
|
+
"mail.to": params.to,
|
|
642
|
+
"mail.template": String(params.template),
|
|
643
|
+
"mail.scheduled_at": date.toISOString(),
|
|
644
|
+
"mail.error.code": normalizedError.code,
|
|
645
|
+
"mail.error.message": normalizedError.message
|
|
646
|
+
}
|
|
771
647
|
});
|
|
772
|
-
|
|
648
|
+
this.logger?.error("IgniterMail.schedule failed", normalizedError);
|
|
649
|
+
throw normalizedError;
|
|
650
|
+
}
|
|
773
651
|
}
|
|
774
652
|
async onSendStarted(params) {
|
|
775
653
|
await this.options.onSendStarted?.(params);
|
|
@@ -781,76 +659,227 @@ var _IgniterMail = class _IgniterMail {
|
|
|
781
659
|
await this.options.onSendSuccess?.(params);
|
|
782
660
|
}
|
|
783
661
|
};
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
662
|
+
|
|
663
|
+
// src/builders/main.builder.ts
|
|
664
|
+
var IgniterMailBuilder = class _IgniterMailBuilder {
|
|
665
|
+
constructor(options) {
|
|
666
|
+
this.templates = {};
|
|
667
|
+
this.from = options.from;
|
|
668
|
+
this.adapter = options.adapter;
|
|
669
|
+
this.templates = options.templates;
|
|
670
|
+
this.logger = options.logger;
|
|
671
|
+
this.telemetry = options.telemetry;
|
|
672
|
+
this.onSendStartedHandler = options.onSendStarted;
|
|
673
|
+
this.onSendErrorHandler = options.onSendError;
|
|
674
|
+
this.onSendSuccessHandler = options.onSendSuccess;
|
|
675
|
+
if (options.queue) {
|
|
676
|
+
this.queue = {
|
|
677
|
+
adapter: options.queue.adapter,
|
|
678
|
+
options: options.queue.options
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Creates a new builder.
|
|
684
|
+
*/
|
|
685
|
+
static create() {
|
|
686
|
+
return new _IgniterMailBuilder({ from: "", adapter: {}, templates: {} });
|
|
687
|
+
}
|
|
688
|
+
/** Sets the default FROM address. */
|
|
689
|
+
withFrom(from) {
|
|
690
|
+
this.from = from;
|
|
691
|
+
return this;
|
|
692
|
+
}
|
|
693
|
+
/** Attaches a logger instance. */
|
|
694
|
+
withLogger(logger) {
|
|
695
|
+
this.logger = logger;
|
|
696
|
+
return this;
|
|
697
|
+
}
|
|
698
|
+
/** Attaches a telemetry instance for observability. */
|
|
699
|
+
withTelemetry(telemetry) {
|
|
700
|
+
this.telemetry = telemetry;
|
|
701
|
+
return this;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Enables queue delivery.
|
|
705
|
+
*
|
|
706
|
+
* When configured, `IgniterMail.schedule()` will enqueue jobs using the queue adapter.
|
|
707
|
+
*/
|
|
708
|
+
withQueue(adapter, options) {
|
|
709
|
+
this.queue = { adapter, options };
|
|
710
|
+
return this;
|
|
711
|
+
}
|
|
712
|
+
withAdapter(adapterOrProvider, secret) {
|
|
713
|
+
if (typeof adapterOrProvider === "string") {
|
|
714
|
+
if (!secret) {
|
|
715
|
+
throw new IgniterMailError({
|
|
716
|
+
code: "MAIL_PROVIDER_ADAPTER_SECRET_REQUIRED",
|
|
717
|
+
message: "MAIL_PROVIDER_ADAPTER_SECRET_REQUIRED",
|
|
718
|
+
logger: this.logger
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
switch (adapterOrProvider) {
|
|
722
|
+
case "resend":
|
|
723
|
+
this.adapter = ResendMailAdapter.create({
|
|
724
|
+
secret,
|
|
725
|
+
from: this.from
|
|
726
|
+
});
|
|
727
|
+
return this;
|
|
728
|
+
case "smtp":
|
|
729
|
+
this.adapter = SmtpMailAdapter.create({
|
|
730
|
+
secret,
|
|
731
|
+
from: this.from
|
|
732
|
+
});
|
|
733
|
+
return this;
|
|
734
|
+
case "postmark":
|
|
735
|
+
this.adapter = PostmarkMailAdapter.create({
|
|
736
|
+
secret,
|
|
737
|
+
from: this.from
|
|
738
|
+
});
|
|
739
|
+
return this;
|
|
740
|
+
case "sendgrid":
|
|
741
|
+
this.adapter = SendGridMailAdapter.create({
|
|
742
|
+
secret,
|
|
743
|
+
from: this.from
|
|
744
|
+
});
|
|
745
|
+
return this;
|
|
746
|
+
default:
|
|
747
|
+
throw new IgniterMailError({
|
|
748
|
+
code: "MAIL_PROVIDER_ADAPTER_NOT_FOUND",
|
|
749
|
+
message: `MAIL_PROVIDER_ADAPTER_NOT_FOUND: ${adapterOrProvider}`,
|
|
750
|
+
logger: this.logger,
|
|
751
|
+
metadata: {
|
|
752
|
+
provider: adapterOrProvider
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
this.adapter = adapterOrProvider;
|
|
758
|
+
return this;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Registers a template.
|
|
762
|
+
*/
|
|
763
|
+
addTemplate(key, template) {
|
|
764
|
+
return new _IgniterMailBuilder({
|
|
765
|
+
from: this.from,
|
|
766
|
+
adapter: this.adapter,
|
|
767
|
+
templates: {
|
|
768
|
+
...this.templates,
|
|
769
|
+
[key]: template
|
|
770
|
+
},
|
|
771
|
+
logger: this.logger,
|
|
772
|
+
telemetry: this.telemetry,
|
|
773
|
+
onSendStarted: this.onSendStartedHandler,
|
|
774
|
+
onSendError: this.onSendErrorHandler,
|
|
775
|
+
onSendSuccess: this.onSendSuccessHandler,
|
|
776
|
+
queue: this.queue ? {
|
|
777
|
+
adapter: this.queue.adapter,
|
|
778
|
+
options: this.queue.options
|
|
779
|
+
} : void 0
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
/** Hook invoked before sending. */
|
|
783
|
+
onSendStarted(handler) {
|
|
784
|
+
this.onSendStartedHandler = handler;
|
|
785
|
+
return this;
|
|
786
|
+
}
|
|
787
|
+
/** Hook invoked on error. */
|
|
788
|
+
onSendError(handler) {
|
|
789
|
+
this.onSendErrorHandler = handler;
|
|
790
|
+
return this;
|
|
791
|
+
}
|
|
792
|
+
/** Hook invoked on success. */
|
|
793
|
+
onSendSuccess(handler) {
|
|
794
|
+
this.onSendSuccessHandler = handler;
|
|
795
|
+
return this;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Builds the {@link IgniterMail} instance.
|
|
799
|
+
*/
|
|
800
|
+
build() {
|
|
801
|
+
if (!this.from) {
|
|
802
|
+
throw new IgniterMailError({
|
|
803
|
+
code: "MAIL_PROVIDER_FROM_REQUIRED",
|
|
804
|
+
message: "MAIL_PROVIDER_FROM_REQUIRED",
|
|
805
|
+
logger: this.logger
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
if (!this.adapter) {
|
|
809
|
+
throw new IgniterMailError({
|
|
810
|
+
code: "MAIL_PROVIDER_ADAPTER_REQUIRED",
|
|
811
|
+
message: "MAIL_PROVIDER_ADAPTER_REQUIRED",
|
|
812
|
+
logger: this.logger
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
return new IgniterMailManagerCore({
|
|
816
|
+
from: this.from,
|
|
817
|
+
adapter: this.adapter,
|
|
818
|
+
templates: this.templates,
|
|
819
|
+
onSendStarted: this.onSendStartedHandler,
|
|
820
|
+
onSendError: this.onSendErrorHandler,
|
|
821
|
+
onSendSuccess: this.onSendSuccessHandler,
|
|
822
|
+
telemetry: this.telemetry,
|
|
823
|
+
logger: this.logger,
|
|
824
|
+
queue: this.queue ? {
|
|
825
|
+
adapter: this.queue.adapter,
|
|
826
|
+
options: this.queue.options
|
|
827
|
+
} : void 0
|
|
811
828
|
});
|
|
812
|
-
return _IgniterMail.instance;
|
|
813
829
|
}
|
|
814
|
-
_IgniterMail.instance = new _IgniterMail(
|
|
815
|
-
options
|
|
816
|
-
);
|
|
817
|
-
return _IgniterMail.instance;
|
|
818
830
|
};
|
|
819
|
-
var IgniterMail =
|
|
831
|
+
var IgniterMail = IgniterMailBuilder;
|
|
820
832
|
|
|
821
|
-
// src/
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
833
|
+
// src/builders/template.builder.ts
|
|
834
|
+
var IgniterMailTemplateBuilder = class _IgniterMailTemplateBuilder {
|
|
835
|
+
/** Creates a new builder instance. */
|
|
836
|
+
static create() {
|
|
837
|
+
return new _IgniterMailTemplateBuilder();
|
|
838
|
+
}
|
|
839
|
+
/** Sets the default subject for the template. */
|
|
840
|
+
withSubject(subject) {
|
|
841
|
+
this.subject = subject;
|
|
842
|
+
return this;
|
|
843
|
+
}
|
|
844
|
+
/** Attaches the schema used to validate and infer payload types. */
|
|
845
|
+
withSchema(schema) {
|
|
846
|
+
this.schema = schema;
|
|
847
|
+
return this;
|
|
848
|
+
}
|
|
849
|
+
/** Sets the React Email render function for the template. */
|
|
850
|
+
withRender(render2) {
|
|
851
|
+
this.render = render2;
|
|
852
|
+
return this;
|
|
853
|
+
}
|
|
854
|
+
/** Builds the template definition. */
|
|
855
|
+
build() {
|
|
856
|
+
if (!this.subject) {
|
|
857
|
+
throw new IgniterMailError({
|
|
858
|
+
code: "MAIL_TEMPLATE_CONFIGURATION_INVALID",
|
|
859
|
+
message: "Mail template subject is required"
|
|
860
|
+
});
|
|
828
861
|
}
|
|
829
|
-
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
// src/utils/get-adapter.ts
|
|
833
|
-
var getAdapter = (adapter) => {
|
|
834
|
-
switch (adapter) {
|
|
835
|
-
case "resend":
|
|
836
|
-
return resendAdapter;
|
|
837
|
-
case "smtp":
|
|
838
|
-
return smtpAdapter;
|
|
839
|
-
case "postmark":
|
|
840
|
-
return postmarkAdapter;
|
|
841
|
-
case "sendgrid":
|
|
842
|
-
return sendgridAdapter;
|
|
843
|
-
case "webhook":
|
|
844
|
-
return webhookAdapter;
|
|
845
|
-
default:
|
|
862
|
+
if (!this.schema) {
|
|
846
863
|
throw new IgniterMailError({
|
|
847
|
-
code: "
|
|
848
|
-
message:
|
|
849
|
-
metadata: { adapter }
|
|
864
|
+
code: "MAIL_TEMPLATE_CONFIGURATION_INVALID",
|
|
865
|
+
message: "Mail template schema is required"
|
|
850
866
|
});
|
|
867
|
+
}
|
|
868
|
+
if (!this.render) {
|
|
869
|
+
throw new IgniterMailError({
|
|
870
|
+
code: "MAIL_TEMPLATE_CONFIGURATION_INVALID",
|
|
871
|
+
message: "Mail template render is required"
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
return {
|
|
875
|
+
subject: this.subject,
|
|
876
|
+
schema: this.schema,
|
|
877
|
+
render: this.render
|
|
878
|
+
};
|
|
851
879
|
}
|
|
852
880
|
};
|
|
881
|
+
var IgniterMailTemplate = IgniterMailTemplateBuilder;
|
|
853
882
|
|
|
854
|
-
export { IgniterMail, IgniterMailBuilder, IgniterMailError,
|
|
883
|
+
export { IgniterMail, IgniterMailBuilder, IgniterMailError, IgniterMailManagerCore, IgniterMailSchema, IgniterMailTemplate, IgniterMailTemplateBuilder };
|
|
855
884
|
//# sourceMappingURL=index.mjs.map
|
|
856
885
|
//# sourceMappingURL=index.mjs.map
|