@maestro-js/mail 1.0.0-alpha.0

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/dist/index.js ADDED
@@ -0,0 +1,554 @@
1
+ // src/index.ts
2
+ import "iso-fns2";
3
+
4
+ // src/mail-types.ts
5
+ function getEmails(address) {
6
+ const list = Array.isArray(address) ? address : [address];
7
+ return list.map((a) => typeof a === "string" ? a.trim() : a.email.trim());
8
+ }
9
+
10
+ // src/send-grid-mail-driver.ts
11
+ import assert from "assert";
12
+ function sendGridMailDriver(sendGrid) {
13
+ async function send(envelope) {
14
+ const allEmails = [
15
+ ...getEmails(envelope.to),
16
+ ...envelope.cc ? getEmails(envelope.cc) : [],
17
+ ...envelope.bcc ? getEmails(envelope.bcc) : []
18
+ ];
19
+ const from = envelope.from;
20
+ assert.ok(from, "missing from address");
21
+ const result = await new Promise(
22
+ (resolve, reject) => sendGrid.send(
23
+ {
24
+ to: envelope.to,
25
+ cc: envelope.cc,
26
+ bcc: envelope.bcc,
27
+ from,
28
+ subject: envelope.subject ?? void 0,
29
+ ...envelope.content,
30
+ attachments: envelope.attachments,
31
+ category: envelope.category ?? void 0,
32
+ customArgs: { ...envelope.extras ?? {}, __extraKeys__: JSON.stringify(Object.keys(envelope.extras ?? {})) }
33
+ },
34
+ false,
35
+ (err, result2) => {
36
+ if (err) return reject(err);
37
+ resolve(result2[0]);
38
+ }
39
+ )
40
+ );
41
+ return {
42
+ success: result.statusCode < 400,
43
+ messages: allEmails.map((email) => ({ email, id: result.headers["x-message-id"] })),
44
+ driverName: "sendgrid"
45
+ };
46
+ }
47
+ return {
48
+ send,
49
+ toMailEvent(sgEvent) {
50
+ const sgEventType = sgEvent.event;
51
+ if (sgEventType === "processed" || sgEventType === "dropped" || sgEventType === "group_unsubscribe" || sgEventType === "group_resubscribe") {
52
+ return null;
53
+ } else {
54
+ const messageId = sgEvent.sg_message_id.split(".")[0];
55
+ assert.ok(messageId, "missing messageId");
56
+ const extraKeys = JSON.parse(sgEvent["__extraKeys__"] ?? "[]");
57
+ const formatted = {
58
+ eventId: sgEvent.sg_event_id,
59
+ event: sgEventType === "delivered" ? "delivery" : sgEventType === "spamreport" ? "spamComplaint" : sgEventType,
60
+ email: sgEvent.email,
61
+ messageId,
62
+ timestamp: sgEvent.timestamp * 1e3,
63
+ category: Array.isArray(sgEvent.category) ? sgEvent.category[0] ?? null : null,
64
+ extras: Object.fromEntries(extraKeys.map((key) => [key, sgEvent[key]])),
65
+ driverName: "sendgrid"
66
+ };
67
+ return formatted;
68
+ }
69
+ }
70
+ };
71
+ }
72
+
73
+ // src/mail-trap-mail-driver.ts
74
+ import { Helpers } from "@maestro-js/helpers";
75
+ import assert2 from "assert";
76
+ function mailTrapMailDriver(mailTrap) {
77
+ async function send(envelope) {
78
+ function convertAddress(address) {
79
+ return Array.isArray(address) ? address.map((a) => typeof a === "string" ? { email: a } : a) : [typeof address === "string" ? { email: address } : address];
80
+ }
81
+ assert2.ok(envelope.from, "missing from address");
82
+ const result = await Helpers.retry(
83
+ () => mailTrap.send({
84
+ to: convertAddress(envelope.to),
85
+ cc: envelope.cc ? convertAddress(envelope.cc) : [],
86
+ bcc: envelope.bcc ? convertAddress(envelope.bcc) : [],
87
+ from: typeof envelope.from === "string" ? { email: envelope.from } : envelope.from,
88
+ subject: envelope.subject ?? "",
89
+ ...envelope.content,
90
+ attachments: envelope.attachments,
91
+ category: envelope.category ?? void 0,
92
+ custom_variables: Object.fromEntries(Object.entries(envelope.extras ?? {}).filter((e) => e[1] != null))
93
+ }),
94
+ {
95
+ maxAttempts: 10,
96
+ delay: 1e4,
97
+ shouldRetry: (e) => {
98
+ console.log("Trouble with a mailtrap call! Retrying...", e);
99
+ return true;
100
+ }
101
+ }
102
+ );
103
+ const emails = [
104
+ ...getEmails(envelope.to),
105
+ ...envelope.cc ? getEmails(envelope.cc) : [],
106
+ ...envelope.bcc ? getEmails(envelope.bcc) : []
107
+ ];
108
+ return {
109
+ success: result.success,
110
+ messages: result.message_ids.map((id, i) => {
111
+ assert2.ok(emails[i], `could not match messageId ${id} to an email address`);
112
+ return { id, email: emails[i] };
113
+ }),
114
+ driverName: "mailtrap"
115
+ };
116
+ }
117
+ return {
118
+ send,
119
+ toMailEvent(mtEvent) {
120
+ const formatted = {
121
+ eventId: mtEvent.event_id,
122
+ event: mtEvent.event === "soft_bounce" ? "softBounce" : mtEvent.event === "spam_complaint" ? "spamComplaint" : mtEvent.event,
123
+ email: mtEvent.email,
124
+ messageId: mtEvent.message_id,
125
+ timestamp: mtEvent.timestamp * 1e3,
126
+ category: mtEvent.category ?? null,
127
+ extras: mtEvent.custom_variables ?? {},
128
+ driverName: "mailtrap"
129
+ };
130
+ return formatted;
131
+ }
132
+ };
133
+ }
134
+
135
+ // src/ses-mail-driver.ts
136
+ import { Helpers as Helpers2 } from "@maestro-js/helpers";
137
+ import assert3 from "assert";
138
+ import MailComposer from "nodemailer/lib/mail-composer/index.js";
139
+ var sesToMaestroEventMapping = {
140
+ Bounce: "bounce",
141
+ Complaint: "spamComplaint",
142
+ Delivery: "delivery",
143
+ Reject: "reject",
144
+ Open: "open",
145
+ Click: "click",
146
+ Subscription: "unsubscribe"
147
+ };
148
+ function sesMailDriver(ses, configurationSetName) {
149
+ async function send(envelope) {
150
+ assert3.ok(envelope.from, "missing from address");
151
+ function getsComposerAddr(addresses) {
152
+ const list = Array.isArray(addresses) ? addresses : [addresses];
153
+ return list.map((a) => typeof a === "string" ? a : a.name ? { name: a.name, address: a.email } : a.email);
154
+ }
155
+ const toListComposer = getsComposerAddr(envelope.to);
156
+ const ccListComposer = envelope.cc ? getsComposerAddr(envelope.cc) : [];
157
+ const bccListComposer = envelope.bcc ? getsComposerAddr(envelope.bcc) : [];
158
+ const replyToComposer = envelope.headers?.replyTo ? getsComposerAddr(envelope.headers?.replyTo) : void 0;
159
+ const composer = new MailComposer({
160
+ from: typeof envelope.from === "string" ? envelope.from : envelope.from.name ? `${envelope.from.name} <${envelope.from.email}>` : envelope.from.email,
161
+ to: toListComposer,
162
+ cc: ccListComposer,
163
+ bcc: bccListComposer,
164
+ subject: envelope.subject ?? "",
165
+ text: "text" in envelope.content ? envelope.content.text : void 0,
166
+ html: "html" in envelope.content ? envelope.content.html : void 0,
167
+ replyTo: replyToComposer,
168
+ headers: {
169
+ ...envelope.headers?.listUnsubscribe && { "List-Unsubscribe": envelope.headers.listUnsubscribe },
170
+ ...envelope.headers?.listUnsubscribePost && { "List-Unsubscribe-Post": envelope.headers.listUnsubscribePost }
171
+ },
172
+ attachments: envelope.attachments?.map((att) => {
173
+ const disposition = att.disposition === "inline" ? "inline" : "attachment";
174
+ const output = {
175
+ filename: att.filename,
176
+ content: Buffer.from(att.content, "base64"),
177
+ contentDisposition: disposition
178
+ };
179
+ if (att.type) {
180
+ output.contentType = att.type;
181
+ }
182
+ if (att.contentId) {
183
+ output.cid = att.contentId;
184
+ }
185
+ return output;
186
+ })
187
+ });
188
+ const toList = getEmails(envelope.to);
189
+ const ccList = envelope.cc ? getEmails(envelope.cc) : [];
190
+ const bccList = envelope.bcc ? getEmails(envelope.bcc) : [];
191
+ const allAddresses = [...toList, ...ccList, ...bccList];
192
+ const messageTags = envelope.extras ? Object.entries(envelope.extras).map(([Name, Value]) => ({
193
+ Name,
194
+ Value: String(Value)
195
+ })) : [];
196
+ const rawBuffer = await composer.compile().build();
197
+ const sendPromises = allAddresses.map(async (recipient) => {
198
+ try {
199
+ const cmd = {
200
+ input: {
201
+ RawMessage: { Data: rawBuffer },
202
+ Destinations: [recipient],
203
+ ConfigurationSetName: configurationSetName,
204
+ Tags: messageTags.length ? messageTags : void 0
205
+ }
206
+ };
207
+ const result = await Helpers2.retry(() => ses.send(cmd), {
208
+ maxAttempts: 5,
209
+ delay: 3e3,
210
+ shouldRetry: () => true
211
+ });
212
+ return { id: result.MessageId, email: recipient };
213
+ } catch (err) {
214
+ throw err;
215
+ }
216
+ });
217
+ const messages = await Promise.allSettled(sendPromises);
218
+ if (messages.every((m) => m.status === "rejected")) {
219
+ throw messages[0]?.reason;
220
+ } else {
221
+ return {
222
+ success: true,
223
+ driverName: "ses",
224
+ messages: messages.filter((m) => m.status === "fulfilled").map((m) => m.value)
225
+ };
226
+ }
227
+ }
228
+ function toMailEvent(raw) {
229
+ const { mail, eventType, eventId } = raw;
230
+ const maestroEvent = sesToMaestroEventMapping[eventType];
231
+ if (!maestroEvent) {
232
+ console.debug(`Skipping unhandled SES event: ${eventType}`);
233
+ return null;
234
+ }
235
+ const timeStampString = eventType && raw?.[eventType.toLowerCase()]?.timestamp || (/* @__PURE__ */ new Date()).toISOString();
236
+ const timeStamp = new Date(timeStampString).getTime();
237
+ const mailTags = raw.mail.tags;
238
+ const extras = mailTags ? Object.fromEntries(
239
+ Object.entries(mailTags).filter(([, vs]) => vs.length > 0).map(([k, vs]) => [k, vs[0]])
240
+ ) : {};
241
+ return {
242
+ driverName: "ses",
243
+ eventId,
244
+ messageId: mail.messageId,
245
+ timestamp: timeStamp,
246
+ category: null,
247
+ email: raw.bounce?.bouncedRecipients?.[0]?.emailAddress ?? raw.complaint?.complainedRecipients?.[0]?.emailAddress ?? mail.destination[0],
248
+ event: maestroEvent,
249
+ extras
250
+ };
251
+ }
252
+ return { send, toMailEvent };
253
+ }
254
+
255
+ // src/ics-attachment.ts
256
+ import { dateTimeFns, durationFns, instantFns, zonedDateTimeFns } from "iso-fns2";
257
+ import { createEvent } from "ics";
258
+ function icsAttachment(options) {
259
+ const { value: start, inputType: startInputType, outputType: startOutputType } = parseIsoToDateArray(options.start);
260
+ const endingOptions = durationFns.isValid(options.end) ? { duration: parseIsoToDurationObject(options.end) } : (() => {
261
+ const result = parseIsoToDateArray(options.end);
262
+ return {
263
+ end: result.value,
264
+ endInputType: result.inputType,
265
+ endOutputType: result.outputType
266
+ };
267
+ })();
268
+ const icsMessage = createEvent({
269
+ ...options,
270
+ start,
271
+ startInputType,
272
+ startOutputType,
273
+ ...endingOptions,
274
+ created: options.created ? instantToDateArray(options.created) : void 0,
275
+ lastModified: options.lastModified ? instantToDateArray(options.lastModified) : void 0,
276
+ alarms: options.alarms ? options.alarms.map((a) => ({
277
+ ...a,
278
+ duration: a.duration ? parseIsoToDurationObject(a.duration) : void 0,
279
+ trigger: a.trigger ? parseIsoToDurationObject(a.trigger) : void 0
280
+ })) : void 0
281
+ });
282
+ if (!icsMessage.value || icsMessage.error) {
283
+ throw icsMessage.error ?? new Error("Unknown error generating ics attachment");
284
+ }
285
+ const encodedMessage = Buffer.from(icsMessage.value).toString("base64");
286
+ return {
287
+ content: encodedMessage,
288
+ filename: options.filename ?? "event.ics",
289
+ type: "application/ics",
290
+ disposition: "attachment"
291
+ };
292
+ }
293
+ function instantToDateArray(iso) {
294
+ const value = instantFns.chain(iso).toZonedDateTime("UTC").toDateTime().getFields().value();
295
+ return [value.year, value.month, value.day, value.hour, value.minute];
296
+ }
297
+ function parseIsoToDateArray(iso) {
298
+ if (instantFns.isValid(iso)) {
299
+ const value = instantToDateArray(iso);
300
+ return { value, inputType: "utc", outputType: "utc" };
301
+ } else if (zonedDateTimeFns.isValid(iso)) {
302
+ const value = zonedDateTimeFns.chain(iso).toInstant().toZonedDateTime("UTC").toDateTime().getFields().value();
303
+ return { value: [value.year, value.month, value.day, value.hour, value.minute], inputType: "utc", outputType: "utc" };
304
+ } else {
305
+ const value = dateTimeFns.getFields(iso);
306
+ return { value: [value.year, value.month, value.day, value.hour, value.minute], inputType: "local", outputType: "local" };
307
+ }
308
+ }
309
+ function parseIsoToDurationObject(iso) {
310
+ const value = durationFns.getFields(durationFns.abs(iso));
311
+ return {
312
+ weeks: value.weeks,
313
+ days: value.days,
314
+ hours: value.hours,
315
+ minutes: value.minutes,
316
+ seconds: value.seconds,
317
+ before: durationFns.getSign(iso) === -1
318
+ };
319
+ }
320
+
321
+ // src/index.ts
322
+ import assert5 from "assert";
323
+ import { Log as Log2 } from "@maestro-js/log";
324
+
325
+ // src/stub-mail-driver.ts
326
+ import assert4 from "assert";
327
+ import { randomUUID } from "crypto";
328
+ import "@maestro-js/log";
329
+ function stubMailDriver({ logger } = {}) {
330
+ async function send(envelope) {
331
+ assert4.ok(envelope.from, "missing from address");
332
+ const emails = [
333
+ ...getEmails(envelope.to),
334
+ ...envelope.cc ? getEmails(envelope.cc) : [],
335
+ ...envelope.bcc ? getEmails(envelope.bcc) : []
336
+ ];
337
+ logger?.info(envelope);
338
+ return {
339
+ success: true,
340
+ messages: emails.map((email) => ({ email, id: randomUUID() })),
341
+ driverName: "stub"
342
+ };
343
+ }
344
+ return {
345
+ send
346
+ };
347
+ }
348
+
349
+ // src/index.ts
350
+ import { ServiceRegistry } from "@maestro-js/service-registry";
351
+ var EMPTY_ENVELOPE = /* @__PURE__ */ Symbol("EMPTY_ENVELOPE");
352
+ function create(config) {
353
+ const queueService = config.queueService;
354
+ const mailQueue = queueService.create({
355
+ name: ["Maestro.Mail", config.name].filter(Boolean).join("."),
356
+ async workFn(envelope) {
357
+ return send(envelope);
358
+ }
359
+ });
360
+ const mailableQueue = queueService.create({
361
+ name: ["Maestro.Mailable", config.name].filter(Boolean).join("."),
362
+ async workFn(props) {
363
+ const mailable2 = mailableRegistry.get(props.mailableName);
364
+ if (!mailable2) {
365
+ throw new Error(`No mailable found with name ${props.mailableName}`);
366
+ }
367
+ const envelope = await mailable2({ ...props.renderArgs });
368
+ if (envelope === EMPTY_ENVELOPE) {
369
+ return;
370
+ }
371
+ return send(envelope);
372
+ }
373
+ });
374
+ function logMail({
375
+ envelope,
376
+ messages,
377
+ driverName,
378
+ mailable: mailable2
379
+ }) {
380
+ function convertAddress(address) {
381
+ if (!address) {
382
+ return [];
383
+ }
384
+ return Array.isArray(address) ? address.map((a) => typeof a === "string" ? { email: a } : a) : [typeof address === "string" ? { email: address } : address];
385
+ }
386
+ const allEmails = [...convertAddress(envelope.to), ...convertAddress(envelope.cc), ...convertAddress(envelope.bcc)];
387
+ messages.forEach((message) => {
388
+ const to = allEmails.find((e) => e.email === message.email);
389
+ config.logger.info({
390
+ type: "envelope",
391
+ to: to || { email: message.email },
392
+ from: typeof envelope.from === "string" ? { email: envelope.from } : envelope.from,
393
+ attachments: envelope.attachments ?? [],
394
+ subject: envelope.subject,
395
+ mailable: mailable2,
396
+ content: envelope.content,
397
+ messageId: message.id,
398
+ driverName,
399
+ headers: envelope.headers ?? {},
400
+ category: envelope.category ?? null,
401
+ extras: Object.fromEntries(Object.entries(envelope.extras ?? {}).filter((e) => e[1] != null))
402
+ });
403
+ });
404
+ }
405
+ async function send(envelope) {
406
+ const formattedEnvelope = formatEnvelope(envelope);
407
+ try {
408
+ const { driverName, messages, success } = await config.driver.send(formattedEnvelope);
409
+ if (success) {
410
+ logMail({ envelope: formattedEnvelope, messages, driverName, mailable: null });
411
+ }
412
+ return { messages, success };
413
+ } catch (error) {
414
+ if (config.onMessageSendFailure) {
415
+ config.onMessageSendFailure(formattedEnvelope, error);
416
+ }
417
+ throw error;
418
+ }
419
+ }
420
+ function queue(envelope) {
421
+ mailQueue.addMessage(envelope, { scheduledDate: null });
422
+ }
423
+ function later(envelope, scheduledDate) {
424
+ mailQueue.addMessage(envelope, { scheduledDate });
425
+ }
426
+ async function sendMailable(props) {
427
+ const mailable2 = mailableRegistry.get(props.mailableName);
428
+ if (!mailable2) {
429
+ throw new Error(`No mailable found with name ${props.mailableName}`);
430
+ }
431
+ const result = await mailable2({ ...props.renderArgs });
432
+ if (result === EMPTY_ENVELOPE) {
433
+ return;
434
+ }
435
+ const envelope = formatEnvelope(result);
436
+ try {
437
+ const { messages, success, driverName } = await config.driver.send(envelope);
438
+ if (success) {
439
+ logMail({ envelope, messages, driverName, mailable: props.mailableName });
440
+ }
441
+ } catch (error) {
442
+ if (config.onMessageSendFailure) {
443
+ config.onMessageSendFailure(envelope, error);
444
+ }
445
+ throw error;
446
+ }
447
+ }
448
+ async function queueMailable(props, options) {
449
+ await mailableQueue.addMessage(props, options);
450
+ }
451
+ function formatEnvelope(envelope) {
452
+ function formatAddress(mailAddress) {
453
+ return typeof mailAddress === "string" ? mailAddress.trim().toLowerCase() : { ...mailAddress, email: mailAddress.email.trim().toLowerCase() };
454
+ }
455
+ const from = envelope.from ?? config.globalFrom;
456
+ assert5.ok(from);
457
+ const formattedTo = Array.isArray(envelope.to) ? envelope.to.map((to) => formatAddress(to)) : [formatAddress(envelope.to)];
458
+ const formattedToUniq = [...new Set(formattedTo.map((f) => typeof f === "string" ? f : f.email))].map(
459
+ (email) => formattedTo.find((t) => typeof t === "string" ? t === email : t.email === email)
460
+ );
461
+ if (formattedToUniq.length !== formattedTo.length) {
462
+ Log2.warn({
463
+ message: "Addresses were not unique in the request. Duplicate email addresses have been removed",
464
+ original: formattedTo,
465
+ modified: formattedToUniq
466
+ });
467
+ }
468
+ return {
469
+ ...envelope,
470
+ to: formattedToUniq,
471
+ cc: envelope.cc ? Array.isArray(envelope.cc) ? envelope.cc.map(formatAddress) : [formatAddress(envelope.cc)] : void 0,
472
+ bcc: envelope.bcc ? Array.isArray(envelope.bcc) ? envelope.bcc.map(formatAddress) : [formatAddress(envelope.bcc)] : void 0,
473
+ from: formatAddress(from)
474
+ };
475
+ }
476
+ return { send, queue, later, sendMailable, queueMailable };
477
+ }
478
+ var mailableRegistry = /* @__PURE__ */ new Map();
479
+ function mailable(name, fn) {
480
+ mailableRegistry.set(name, fn);
481
+ return {
482
+ /**
483
+ * Prepares the mailable with the renderArgs so that the mailable
484
+ * can be sent.
485
+ */
486
+ render(renderArgs) {
487
+ function getFns(mailService) {
488
+ async function content() {
489
+ const result = await fn(renderArgs);
490
+ if (result !== EMPTY_ENVELOPE) {
491
+ return result.content;
492
+ } else {
493
+ return { text: "" };
494
+ }
495
+ }
496
+ async function send() {
497
+ await mailService.sendMailable({ mailableName: name, renderArgs });
498
+ }
499
+ async function queue() {
500
+ await mailService.queueMailable({ mailableName: name, renderArgs }, { scheduledDate: null });
501
+ }
502
+ async function later(scheduledDate) {
503
+ await mailService.queueMailable({ mailableName: name, renderArgs }, { scheduledDate });
504
+ }
505
+ return { content, send, queue, later };
506
+ }
507
+ const defaultFns = getFns(registry.resolve("default"));
508
+ function provider2(key) {
509
+ return getFns(registry.resolve(key));
510
+ }
511
+ return {
512
+ content: defaultFns.content,
513
+ send: defaultFns.send,
514
+ queue: defaultFns.queue,
515
+ later: defaultFns.later,
516
+ provider: provider2
517
+ };
518
+ }
519
+ };
520
+ }
521
+ var drivers = {
522
+ sendGrid: sendGridMailDriver,
523
+ mailTrap: mailTrapMailDriver,
524
+ ses: sesMailDriver,
525
+ stub: stubMailDriver
526
+ };
527
+ var attachments = {
528
+ ics: icsAttachment
529
+ };
530
+ var registry = ServiceRegistry.createRegistry(ServiceRegistry.proxyFunctionsForObject);
531
+ var Provider = {
532
+ create,
533
+ register: registry.register
534
+ };
535
+ function provider(key) {
536
+ const service = registry.resolve(key);
537
+ return {
538
+ send: service.send,
539
+ later: service.later,
540
+ queue: service.queue
541
+ };
542
+ }
543
+ var Mail = {
544
+ EMPTY_ENVELOPE,
545
+ mailable,
546
+ Provider,
547
+ ...provider("default"),
548
+ provider,
549
+ drivers,
550
+ attachments
551
+ };
552
+ export {
553
+ Mail
554
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@maestro-js/mail",
3
+ "description": "Driver-based email sending with queued delivery, scheduled sending, and mailable templates for the @maestro-js/mail package. Use when working with @maestro-js/mail. Covers creating mail services with SendGrid/SES/MailTrap/Stub drivers, sending emails immediately or via queue, scheduling future delivery, building reusable mailable templates, processing webhook events, generating ICS calendar attachments, and testing with the stub driver.",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "default": "./dist/index.js"
9
+ }
10
+ },
11
+ "dependencies": {
12
+ "ics": "^3.8.1",
13
+ "iso-fns2": "npm:iso-fns@2.0.0-alpha.26",
14
+ "nodemailer": "^7.0.5",
15
+ "@maestro-js/helpers": "1.0.0-alpha.0",
16
+ "@maestro-js/log": "1.0.0-alpha.0",
17
+ "@maestro-js/service-registry": "1.0.0-alpha.0"
18
+ },
19
+ "devDependencies": {
20
+ "@react-email/render": "0.0.7",
21
+ "@types/node": "^22.19.11",
22
+ "@types/nodemailer": "^6.4.17",
23
+ "@types/react": "^19.0.0",
24
+ "@maestro-js/queue": "1.0.0-alpha.0"
25
+ },
26
+ "version": "1.0.0-alpha.0",
27
+ "publishConfig": {
28
+ "access": "restricted"
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "license": "UNLICENSED",
34
+ "engines": {
35
+ "node": ">=22.18.0"
36
+ },
37
+ "repository": "https://github.com/Marcato-Partners/maestro-js",
38
+ "scripts": {
39
+ "build": "tsup --config ../../tsup.config.ts",
40
+ "typecheck": "tsc --noEmit",
41
+ "test": "echo 'No tests yet'",
42
+ "format": "prettier --write src/",
43
+ "lint": "prettier --check src/"
44
+ }
45
+ }