@adonisjs/mail
Version:
Mail provider for adonis framework and has support for all common mailing services to send emails
1,602 lines (1,594 loc) • 43.9 kB
JavaScript
import {
JSONTransport
} from "./chunk-F3J2XAU5.js";
import {
MemoryQueueMessenger
} from "./chunk-TDHID2NL.js";
import {
stubsRoot
} from "./chunk-ASVKPASJ.js";
import {
debug_default
} from "./chunk-ZF2M7BIF.js";
import {
__name
} from "./chunk-XE4OXN2W.js";
// src/message.ts
import { basename } from "node:path";
import { fileURLToPath } from "node:url";
import Macroable from "@poppinss/macroable";
import { AssertionError } from "node:assert";
import { cuid } from "@adonisjs/core/helpers";
import { RuntimeException } from "@poppinss/utils";
import ical from "ical-generator";
var Message = class _Message extends Macroable {
static {
__name(this, "Message");
}
static templateEngine;
/**
* Use the configured template engine to compute
* the email contents from templates
*/
static async computeContentsFor({ message, views }) {
const getTemplateEngine = /* @__PURE__ */ __name(() => {
if (!this.templateEngine) {
throw new RuntimeException("Cannot render email templates without a template engine");
}
return this.templateEngine;
}, "getTemplateEngine");
const viewHelpers = {
embedImage: /* @__PURE__ */ __name((filePath, options) => {
const cid = cuid();
message.attachments = message.attachments || [];
message.attachments.push({
path: filePath,
cid,
filename: basename(filePath),
...options
});
return `cid:${cid}`;
}, "embedImage"),
embedImageData: /* @__PURE__ */ __name((data, options) => {
const cid = cuid();
message.attachments = message.attachments || [];
message.attachments.push({
content: data,
cid,
...options
});
return `cid:${cid}`;
}, "embedImageData")
};
if (!message.html && views.html) {
debug_default("computing mail html contents %O", views.html);
message.html = await getTemplateEngine().render(views.html.template, viewHelpers, views.html.data);
}
if (!message.text && views.text) {
debug_default("computing mail text contents %O", views.text);
message.text = await getTemplateEngine().render(views.text.template, {}, views.text.data);
}
if (!message.watch && views.watch) {
debug_default("computing mail watch contents %O", views.watch);
message.watch = await getTemplateEngine().render(views.watch.template, viewHelpers, views.watch.data);
}
}
/**
* Nodemailer internally mutates the "attachments" object
* and removes the path property with the contents of
* the file.
*
* Therefore, we need an additional attachment array we can
* use to searching attachments and writing assertions
*/
#attachmentsForSearch = [];
/**
* Templates to use for rendering email body for
* HTML, plain text and watch
*/
contentViews = {};
/**
* Reference to the underlying node mailer message
*/
nodeMailerMessage = {};
/**
* Converts a recipient email and name to formatted
* string
*/
formatRecipient(recipient) {
if (!recipient) {
return void 0;
}
if (typeof recipient === "string") {
return recipient;
}
if (!recipient.name) {
return recipient.address;
}
return `${recipient.name} <${recipient.address}>`;
}
/**
* Check if a given recipient exists for the mentioned
* email and name.
*/
hasRecipient(property, address, name) {
const recipients = this.nodeMailerMessage[property];
if (!recipients) {
return false;
}
if (name) {
return !!recipients.find((recipient) => {
if (typeof recipient === "string") {
return false;
}
return recipient.address === address && recipient.name === name;
});
}
return !!recipients.find((recipient) => {
if (typeof recipient === "string") {
return recipient === address;
}
return recipient.address === address;
});
}
/**
* Assert the message is sent to the mentioned address
*/
assertRecipient(property, address, name) {
if (!this.hasRecipient(property, address, name)) {
const expected = this.formatRecipient({
address,
name: name || ""
});
const actual = this.nodeMailerMessage[property]?.map((recipient) => {
return this.formatRecipient(recipient);
}) || [];
throw new AssertionError({
message: `Expected message to be delivered to "${expected}"`,
expected: [
expected
],
actual,
operator: "includes"
});
}
}
/**
* Add recipient as `to`
*/
to(address, name) {
this.nodeMailerMessage.to = this.nodeMailerMessage.to || [];
this.nodeMailerMessage.to.push(name ? {
address,
name
} : address);
return this;
}
/**
* Check if message is sent to the mentioned address
*/
hasTo(address, name) {
return this.hasRecipient("to", address, name);
}
/**
* Assert the message is sent to the mentioned address
*/
assertTo(address, name) {
return this.assertRecipient("to", address, name);
}
/**
* Add `from` name and email
*/
from(address, name) {
this.nodeMailerMessage.from = name ? {
address,
name
} : address;
return this;
}
/**
* Check if message is sent from the mentioned address
*/
hasFrom(address, name) {
const fromAddress = this.nodeMailerMessage.from;
if (!fromAddress) {
return false;
}
if (name) {
if (typeof fromAddress === "string") {
return false;
}
return fromAddress.address === address && fromAddress.name === name;
}
if (typeof fromAddress === "string") {
return fromAddress === address;
}
return fromAddress.address === address;
}
/**
* Assert the message is sent from the mentioned address
*/
assertFrom(address, name) {
if (!this.hasFrom(address, name)) {
const expected = this.formatRecipient({
address,
name: name || ""
});
const actual = this.formatRecipient(this.nodeMailerMessage.from);
throw new AssertionError({
message: `Expected message to be sent from "${expected}"`,
expected,
actual
});
}
}
cc(addresses, name) {
this.nodeMailerMessage.cc = this.nodeMailerMessage.cc || [];
if (typeof addresses === "string") {
this.nodeMailerMessage.cc.push(name ? {
address: addresses,
name
} : addresses);
} else {
addresses.forEach((address) => {
this.nodeMailerMessage.cc.push(address);
});
}
return this;
}
/**
* Check if message is sent to the mentioned address
*/
hasCc(address, name) {
return this.hasRecipient("cc", address, name);
}
/**
* Assert the message is sent to the mentioned address
*/
assertCc(address, name) {
return this.assertRecipient("cc", address, name);
}
bcc(addresses, name) {
this.nodeMailerMessage.bcc = this.nodeMailerMessage.bcc || [];
if (typeof addresses === "string") {
this.nodeMailerMessage.bcc.push(name ? {
address: addresses,
name
} : addresses);
} else {
addresses.forEach((address) => {
this.nodeMailerMessage.bcc.push(address);
});
}
return this;
}
/**
* Check if message is sent to the mentioned address
*/
hasBcc(address, name) {
return this.hasRecipient("bcc", address, name);
}
/**
* Assert the message is sent to the mentioned address
*/
assertBcc(address, name) {
return this.assertRecipient("bcc", address, name);
}
/**
* Define custom message id
*/
messageId(messageId) {
this.nodeMailerMessage.messageId = messageId;
return this;
}
/**
* Define subject
*/
subject(message) {
this.nodeMailerMessage.subject = message;
return this;
}
/**
* Check if the message has the mentioned subject
*/
hasSubject(message) {
return !!this.nodeMailerMessage.subject && this.nodeMailerMessage.subject === message;
}
/**
* Assert the message has the mentioned subject
*/
assertSubject(message) {
if (!this.hasSubject(message)) {
throw new AssertionError({
message: `Expected message subject to be "${message}"`,
expected: message,
actual: this.nodeMailerMessage.subject
});
}
}
/**
* Define replyTo email and name
*/
replyTo(address, name) {
this.nodeMailerMessage.replyTo = this.nodeMailerMessage.replyTo || [];
this.nodeMailerMessage.replyTo.push(name ? {
address,
name
} : address);
return this;
}
/**
* Check if the mail has the mentioned reply to address
*/
hasReplyTo(address, name) {
return this.hasRecipient("replyTo", address, name);
}
/**
* Assert the mail has the mentioned reply to address
*/
assertReplyTo(address, name) {
if (!this.hasRecipient("replyTo", address, name)) {
const expected = this.formatRecipient({
address,
name: name || ""
});
const actual = this.nodeMailerMessage.replyTo?.map((recipient) => {
return this.formatRecipient(recipient);
}) || [];
throw new AssertionError({
message: `Expected reply-to addresses to include "${expected}"`,
expected: [
expected
],
actual,
operator: "includes"
});
}
}
/**
* Define inReplyTo message id
*/
inReplyTo(messageId) {
this.nodeMailerMessage.inReplyTo = messageId;
return this;
}
/**
* Define multiple message id's as references
*/
references(messagesIds) {
this.nodeMailerMessage.references = messagesIds;
return this;
}
/**
* Optionally define email envolpe
*/
envelope(envelope) {
this.nodeMailerMessage.envelope = envelope;
return this;
}
/**
* Define contents encoding
*/
encoding(encoding) {
this.nodeMailerMessage.encoding = encoding;
return this;
}
/**
* Define email prority
*/
priority(priority) {
this.nodeMailerMessage.priority = priority;
return this;
}
/**
* Compute email html from defined view
*/
htmlView(template, data) {
this.contentViews.html = {
template,
data
};
return this;
}
/**
* Compute email text from defined view
*/
textView(template, data) {
this.contentViews.text = {
template,
data
};
return this;
}
/**
* Compute apple watch html from defined view
*/
watchView(template, data) {
this.contentViews.watch = {
template,
data
};
return this;
}
/**
* Compute email html from raw text
*/
html(content) {
this.nodeMailerMessage.html = content;
return this;
}
/**
* Compute email text from raw text
*/
text(content) {
this.nodeMailerMessage.text = content;
return this;
}
/**
* Compute email watch html from raw text
*/
watch(content) {
this.nodeMailerMessage.watch = content;
return this;
}
/**
* Assert content of mail to include substring or match
* a given regular expression
*/
assertContent(property, substring) {
const contents = this.nodeMailerMessage[property];
if (!contents) {
throw new AssertionError({
message: `Expected message ${property} body to match substring, but it is undefined`
});
}
if (typeof substring === "string") {
if (!String(contents).includes(substring)) {
throw new AssertionError({
message: `Expected message ${property} body to include "${substring}"`
});
}
return;
}
if (!substring.test(String(contents))) {
throw new AssertionError({
message: `Expected message ${property} body to match "${substring}"`
});
}
}
/**
* Assert message plain text contents to include
* substring or match the given regular expression
*/
assertTextIncludes(substring) {
return this.assertContent("text", substring);
}
/**
* Assert message HTML contents to include substring
* or match the given regular expression
*/
assertHtmlIncludes(substring) {
return this.assertContent("html", substring);
}
/**
* Assert message watch contents to include substring
* or match the given regular expression
*/
assertWatchIncludes(substring) {
return this.assertContent("watch", substring);
}
/**
* Define one or attachments
*/
attach(file, options) {
const filePath = typeof file === "string" ? file : fileURLToPath(file);
this.nodeMailerMessage.attachments = this.nodeMailerMessage.attachments || [];
this.nodeMailerMessage.attachments.push({
path: filePath,
filename: basename(filePath),
...options
});
this.#attachmentsForSearch.push({
path: filePath,
filename: basename(filePath),
...options
});
return this;
}
hasAttachment(file, options) {
const attachments = this.#attachmentsForSearch;
if (typeof file === "function") {
return !!attachments.find(file);
}
const filePath = typeof file === "string" ? file : fileURLToPath(file);
return !!attachments.find((attachment) => {
const hasMatchingPath = attachment.path ? String(attachment.path).endsWith(filePath) : false;
if (!options) {
return hasMatchingPath;
}
if (options.filename && attachment.filename !== options.filename) {
return false;
}
if (options.cid && attachment.cid !== options.cid) {
return false;
}
return true;
});
}
assertAttachment(file, options) {
if (typeof file === "function") {
if (!this.hasAttachment(file)) {
throw new AssertionError({
message: `Expected assertion callback to find an attachment`
});
}
return;
}
if (!this.hasAttachment(file, options)) {
throw new AssertionError({
message: `Expected message attachments to include "${file}"`,
expected: [
{
path: file,
...options
}
],
actual: this.nodeMailerMessage.attachments,
operator: "includes"
});
}
}
/**
* Define attachment from raw data
*/
attachData(content, options) {
this.nodeMailerMessage.attachments = this.nodeMailerMessage.attachments || [];
this.nodeMailerMessage.attachments.push({
content,
...options
});
return this;
}
/**
* Embed attachment inside content using `cid`
*/
embed(file, cid, options) {
const filePath = typeof file === "string" ? file : fileURLToPath(file);
this.nodeMailerMessage.attachments = this.nodeMailerMessage.attachments || [];
this.nodeMailerMessage.attachments.push({
path: filePath,
cid,
filename: basename(filePath),
...options
});
this.#attachmentsForSearch.push({
path: filePath,
cid,
filename: basename(filePath),
...options
});
return this;
}
/**
* Embed attachment from raw data inside content using `cid`
*/
embedData(content, cid, options) {
this.nodeMailerMessage.attachments = this.nodeMailerMessage.attachments || [];
this.nodeMailerMessage.attachments.push({
content,
cid,
...options
});
return this;
}
/**
* Define custom headers for email
*/
header(key, value) {
if (!this.nodeMailerMessage.headers) {
this.nodeMailerMessage.headers = {};
}
if (!Array.isArray(this.nodeMailerMessage.headers)) {
this.nodeMailerMessage.headers[key] = value;
}
return this;
}
/**
* Check if a header has been defined and optionally
* check for values as well.
*/
hasHeader(key, value) {
const headers = this.nodeMailerMessage.headers;
if (!headers || Array.isArray(headers)) {
return false;
}
const headerValue = headers[key];
if (!headerValue) {
return false;
}
if (value) {
return !!(Array.isArray(value) ? value : [
value
]).every((one) => {
return typeof headerValue === "string" ? headerValue === one : Array.isArray(headerValue) ? headerValue.includes(one) : headerValue.value === one;
});
}
return true;
}
/**
* Assert a header has been defined and optionally
* check for values as well.
*/
assertHeader(key, value) {
if (!this.hasHeader(key, value)) {
const headers = this.nodeMailerMessage.headers;
const actual = headers && !Array.isArray(headers) ? headers[key] : void 0;
if (!value || !actual) {
throw new AssertionError({
message: `Expected message headers to include "${key}"`
});
}
throw new AssertionError({
message: `Expected message headers to include "${key}" with value "${value}"`,
actual,
expected: value
});
}
}
/**
* Define custom prepared headers for email
*/
preparedHeader(key, value) {
if (!this.nodeMailerMessage.headers) {
this.nodeMailerMessage.headers = {};
}
if (!Array.isArray(this.nodeMailerMessage.headers)) {
this.nodeMailerMessage.headers[key] = {
prepared: true,
value
};
}
return this;
}
/**
* Defines a `List-` prefix header on the email. Calling
* this method multiple times for the same key will
* override the old value.
*/
addListHeader(key, value) {
this.nodeMailerMessage.list = this.nodeMailerMessage.list || {};
this.nodeMailerMessage.list[key] = value;
return this;
}
/**
* Add `List-Help` header. Calling this method multiple
* times will override the existing value
*/
listHelp(value) {
return this.addListHeader("help", value);
}
/**
* Add `List-Unsubscribe` header. Calling this method multiple
* times will override the existing value
*/
listUnsubscribe(value) {
return this.addListHeader("unsubscribe", value);
}
/**
* Add `List-Subscribe` header. Calling this method multiple
* times will override the existing value
*/
listSubscribe(value) {
return this.addListHeader("subscribe", value);
}
/**
* Attach a calendar event and define contents as string
*/
icalEvent(contents, options) {
if (typeof contents === "function") {
const calendar = ical();
contents(calendar);
contents = calendar.toString();
}
this.nodeMailerMessage.icalEvent = {
content: contents,
...options
};
return this;
}
/**
* Attach a calendar event and load contents from a file
*/
icalEventFromFile(file, options) {
const filePath = typeof file === "string" ? file : fileURLToPath(file);
this.nodeMailerMessage.icalEvent = {
path: filePath,
...options
};
return this;
}
/**
* Attach a calendar event and load contents from a url
*/
icalEventFromUrl(url, options) {
this.nodeMailerMessage.icalEvent = {
href: url,
...options
};
return this;
}
/**
* Computes email contents by rendering the configured
* templates
*/
async computeContents() {
await _Message.computeContentsFor({
message: this.nodeMailerMessage,
views: this.contentViews
});
}
/**
* Object representation of the message
*/
toObject() {
return {
message: this.nodeMailerMessage,
views: {
...this.contentViews
}
};
}
/**
* JSON representation of the message
*/
toJSON() {
return this.toObject();
}
};
// src/base_mail.ts
var BaseMail = class {
static {
__name(this, "BaseMail");
}
/**
* A flag to avoid build email message for
* multiple times
*/
built = false;
/**
* Reference to the mail message object
*/
message = new Message();
/**
* Define the email subject
*/
subject;
/**
* Define the from address for the email
*/
from;
/**
* Define the replyTo email address
*/
replyTo;
/**
* Defines the subject on the message using the mail
* class subject property
*/
defineSubject() {
if (this.subject) {
this.message.subject(this.subject);
}
}
/**
* Defines the from on the message using the mail
* class from property
*/
defineSender() {
if (this.from) {
typeof this.from === "string" ? this.message.from(this.from) : this.message.from(this.from.address, this.from.name);
}
if (this.replyTo) {
typeof this.replyTo === "string" ? this.message.replyTo(this.replyTo) : this.message.replyTo(this.replyTo.address, this.replyTo.name);
}
}
/**
* Builds the mail message for sending it
*/
async build() {
if (this.built) {
return;
}
this.built = true;
this.defineSubject();
this.defineSender();
await this.prepare();
}
/**
* Builds the mail message with the email contents.
* This method will render the templates ahead of
* time
*/
async buildWithContents() {
if (this.built) {
return;
}
await this.build();
await this.message.computeContents();
}
/**
* Sends the mail
*/
async send(mailer, config) {
await this.build();
return mailer.sendCompiled(this.message.toObject(), config);
}
/**
* Sends the mail by using the background
* messenger
*/
async sendLater(mailer, config) {
await this.build();
return mailer.sendLaterCompiled(this.message.toObject(), config);
}
};
// src/mailer.ts
var Mailer = class {
static {
__name(this, "Mailer");
}
name;
transport;
config;
/**
* Reference to AdonisJS application emitter
*/
#emitter;
/**
* Messenger to use for queuing emails
*/
#messenger;
constructor(name, transport, emitter, config = {}) {
this.name = name;
this.transport = transport;
this.config = config;
this.#emitter = emitter;
this.#messenger = new MemoryQueueMessenger(this, this.#emitter);
}
/**
* Configure the messenger to use for sending email asynchronously
*/
setMessenger(messenger) {
this.#messenger = messenger;
return this;
}
/**
* Sends a compiled email using the underlying transport
*/
async sendCompiled(mail, sendConfig) {
if (!mail.message.from && this.config.from) {
mail.message.from = this.config.from;
}
if (!mail.message.replyTo && this.config.replyTo) {
mail.message.replyTo = [
this.config.replyTo
];
}
this.#emitter.emit("mail:sending", {
...mail,
mailerName: this.name
});
await Message.computeContentsFor(mail);
debug_default('sending email, subject "%s"', mail.message.subject);
const response = await this.transport.send(mail.message, sendConfig);
debug_default('email sent, message id "%s"', response.messageId);
this.#emitter.emit("mail:sent", {
...mail,
mailerName: this.name,
response
});
return response;
}
/**
* Queues a compiled email
*/
async sendLaterCompiled(compiledMessage, sendConfig) {
this.#emitter.emit("mail:queueing", {
...compiledMessage,
mailerName: this.name
});
debug_default("queueing email");
const metaData = await this.#messenger.queue(compiledMessage, sendConfig);
this.#emitter.emit("mail:queued", {
...compiledMessage,
metaData,
mailerName: this.name
});
}
/**
* Sends email
*/
async send(callbackOrMail, config) {
if (callbackOrMail instanceof BaseMail) {
return callbackOrMail.send(this, config);
}
const message = new Message();
if (this.config.from) {
typeof this.config.from === "string" ? message.from(this.config.from) : message.from(this.config.from.address, this.config.from.name);
}
await callbackOrMail(message);
const compiledMessage = message.toObject();
return this.sendCompiled(compiledMessage, config);
}
/**
* Send an email asynchronously using the mail messenger. The
* default messenger uses an in-memory queue, unless you have
* configured a custom messenger.
*/
async sendLater(callbackOrMail, config) {
if (callbackOrMail instanceof BaseMail) {
return callbackOrMail.sendLater(this, config);
}
const message = new Message();
await callbackOrMail(message);
const compiledMessage = message.toObject();
return this.sendLaterCompiled(compiledMessage, config);
}
/**
* Invokes `close` method on the transport
*/
async close() {
await this.transport.close?.();
}
};
// configure.ts
import string from "@poppinss/utils/string";
var ENV_VARIABLES = {
smtp: [
"SMTP_HOST",
"SMTP_PORT"
],
ses: [
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
"AWS_REGION"
],
mailgun: [
"MAILGUN_API_KEY",
"MAILGUN_DOMAIN"
],
sparkpost: [
"SPARKPOST_API_KEY"
],
resend: [
"RESEND_API_KEY"
],
brevo: [
"BREVO_API_KEY"
]
};
var KNOWN_TRANSPORTS = Object.keys(ENV_VARIABLES);
async function configure(command) {
let selectedTransports = command.parsedFlags.transports;
if (!selectedTransports) {
selectedTransports = await command.prompt.multiple("Select the mail services you want to use", KNOWN_TRANSPORTS, {
validate(values) {
return !values || !values.length ? "Please select one or more transports" : true;
}
});
}
const transports2 = typeof selectedTransports === "string" ? [
selectedTransports
] : selectedTransports;
const unknownTransport = transports2.find((transport) => !KNOWN_TRANSPORTS.includes(transport));
if (unknownTransport) {
command.exitCode = 1;
command.logger.logError(`Invalid transport "${unknownTransport}". Supported transports are: ${string.sentence(KNOWN_TRANSPORTS)}`);
return;
}
const codemods = await command.createCodemods();
await codemods.makeUsingStub(stubsRoot, "config/mail.stub", {
transports: transports2
});
await codemods.updateRcFile((rcFile) => {
rcFile.addProvider("@adonisjs/mail/mail_provider");
rcFile.addCommand("@adonisjs/mail/commands");
});
await codemods.defineEnvVariables(transports2.reduce((result, transport) => {
ENV_VARIABLES[transport].forEach((envVariable) => {
result[envVariable] = "";
});
return result;
}, {}));
await codemods.defineEnvValidations({
leadingComment: "Variables for configuring the mail package",
variables: transports2.reduce((result, transport) => {
ENV_VARIABLES[transport].forEach((envVariable) => {
result[envVariable] = "Env.schema.string()";
});
return result;
}, {})
});
}
__name(configure, "configure");
// src/fake_mailer.ts
import string2 from "@poppinss/utils/string";
import { AssertionError as AssertionError2 } from "node:assert";
var MailsCollection = class MailsCollection2 {
static {
__name(this, "MailsCollection");
}
#sent = [];
#queued = [];
trackSent(mail) {
this.#sent.push(mail);
}
trackQueued(mail) {
this.#queued.push(mail);
}
clear() {
this.#sent = [];
this.#queued = [];
}
/**
* Returns a list of sent emails captured by the fake mailer
*/
sent(filterFn) {
return filterFn ? this.#sent.filter(filterFn) : this.#sent;
}
/**
* Returns a list of queued emails captured by the fake mailer
*/
queued(filterFn) {
return filterFn ? this.#queued.filter(filterFn) : this.#queued;
}
/**
* Assert the mentioned mail was sent during the fake
* mode
*/
assertSent(mailConstructor, findFn) {
const matchingMail = this.#sent.find((mail) => {
if (!findFn) {
return mail instanceof mailConstructor;
}
return mail instanceof mailConstructor && findFn(mail);
});
if (!matchingMail) {
throw new AssertionError2({
message: `Expected mail "${mailConstructor.name}" was not sent`
});
}
}
/**
* Assert the mentioned mail was NOT sent during the fake
* mode
*/
assertNotSent(mailConstructor, findFn) {
const matchingMail = this.#sent.find((mail) => {
if (!findFn) {
return mail instanceof mailConstructor;
}
return mail instanceof mailConstructor && findFn(mail);
});
if (matchingMail) {
throw new AssertionError2({
message: `Unexpected mail "${mailConstructor.name}" was sent`
});
}
}
assertSentCount(mailConstructor, count) {
if (typeof mailConstructor === "number") {
const actual2 = this.#sent.length;
const expected2 = mailConstructor;
if (actual2 !== expected2) {
throw new AssertionError2({
message: `Expected to send "${expected2}" ${string2.pluralize("mail", expected2)}, instead received "${actual2}" ${string2.pluralize("mail", actual2)}`,
actual: actual2,
expected: expected2
});
}
return;
}
const actual = this.sent((mail) => mail instanceof mailConstructor).length;
const expected = count;
if (actual !== expected) {
throw new AssertionError2({
message: `Expected "${mailConstructor.name}" to be sent "${expected}" ${string2.pluralize("time", expected)}, instead it was sent "${actual}" ${string2.pluralize("time", actual)}`,
actual,
expected
});
}
}
/**
* Assert zero emails were sent
*/
assertNoneSent() {
if (this.#sent.length) {
throw new AssertionError2({
message: `Expected zero mail to be sent, instead received "${this.#sent.length}" mail`,
expected: [],
actual: [
this.#sent.map((mail) => mail.constructor.name)
]
});
}
}
/**
* Assert the mentioned mail was queued during the fake
* mode
*/
assertQueued(mailConstructor, findFn) {
const matchingMail = this.#queued.find((mail) => {
if (!findFn) {
return mail instanceof mailConstructor;
}
return mail instanceof mailConstructor && findFn(mail);
});
if (!matchingMail) {
throw new AssertionError2({
message: `Expected mail "${mailConstructor.name}" was not queued`
});
}
}
/**
* Assert the mentioned mail was NOT queued during the fake
* mode
*/
assertNotQueued(mailConstructor, findFn) {
const matchingMail = this.#queued.find((mail) => {
if (!findFn) {
return mail instanceof mailConstructor;
}
return mail instanceof mailConstructor && findFn(mail);
});
if (matchingMail) {
throw new AssertionError2({
message: `Unexpected mail "${mailConstructor.name}" was queued`
});
}
}
assertQueuedCount(mailConstructor, count) {
if (typeof mailConstructor === "number") {
const actual2 = this.#queued.length;
const expected2 = mailConstructor;
if (actual2 !== expected2) {
throw new AssertionError2({
message: `Expected to queue "${expected2}" ${string2.pluralize("mail", expected2)}, instead received "${actual2}" ${string2.pluralize("mail", actual2)}`,
actual: actual2,
expected: expected2
});
}
return;
}
const actual = this.queued((mail) => mail instanceof mailConstructor).length;
const expected = count;
if (actual !== expected) {
throw new AssertionError2({
message: `Expected "${mailConstructor.name}" to be queued "${expected}" ${string2.pluralize("time", expected)}, instead it was queued "${actual}" ${string2.pluralize("time", actual)}`,
actual,
expected
});
}
}
/**
* Assert zero emails were queued
*/
assertNoneQueued() {
if (this.#queued.length) {
throw new AssertionError2({
message: `Expected zero mail to be queued, instead received "${this.#queued.length}" mail`,
expected: [],
actual: [
this.#queued.map((mail) => mail.constructor.name)
]
});
}
}
};
var MessagesCollection = class MessagesCollection2 {
static {
__name(this, "MessagesCollection");
}
#sent = [];
#queued = [];
/**
* Default finder to find a message using search options
*/
#messageFinder = /* @__PURE__ */ __name((message, searchOptions) => {
if (searchOptions.from && !message.hasFrom(searchOptions.from)) {
return false;
}
if (searchOptions.to && !message.hasTo(searchOptions.to)) {
return false;
}
if (searchOptions.subject && !message.hasSubject(searchOptions.subject)) {
return false;
}
if (searchOptions.attachments && !searchOptions.attachments.every((attachment) => message.hasAttachment(attachment))) {
return false;
}
return true;
}, "#messageFinder");
trackSent(message) {
this.#sent.push(message);
}
trackQueued(message) {
this.#queued.push(message);
}
clear() {
this.#sent = [];
this.#queued = [];
}
/**
* Returns a list of sent messages captured by the fake mailer
*/
sent(filterFn) {
return filterFn ? this.#sent.filter(filterFn) : this.#sent;
}
/**
* Returns a list of queued messages captured by the fake mailer
*/
queued(filterFn) {
return filterFn ? this.#queued.filter(filterFn) : this.#queued;
}
/**
* Assert the mentioned message was sent during the fake
* mode
*/
assertSent(finder) {
const matchingMessage = this.#sent.find(typeof finder === "function" ? finder : (message) => this.#messageFinder(message, finder));
if (!matchingMessage) {
throw new AssertionError2({
message: `Expected message was not sent`
});
}
}
/**
* Assert the mentioned message was NOT sent during the fake
* mode
*/
assertNotSent(finder) {
const matchingMessage = this.#sent.find(typeof finder === "function" ? finder : (message) => this.#messageFinder(message, finder));
if (matchingMessage) {
throw new AssertionError2({
message: `Unexpected message was sent`
});
}
}
assertSentCount(finder, count) {
if (typeof finder === "number") {
const actual2 = this.#sent.length;
const expected2 = finder;
if (actual2 !== expected2) {
throw new AssertionError2({
message: `Expected to send "${expected2}" ${string2.pluralize("message", expected2)}, instead received "${actual2}" ${string2.pluralize("message", actual2)}`,
actual: actual2,
expected: expected2
});
}
return;
}
const actual = this.sent(typeof finder === "function" ? finder : (message) => this.#messageFinder(message, finder)).length;
const expected = count;
if (actual !== expected) {
throw new AssertionError2({
message: `Expected to send "${expected}" ${string2.pluralize("message", expected)}, instead received "${actual}" ${string2.pluralize("message", actual)}`,
actual,
expected
});
}
}
/**
* Assert zero messages were sent
*/
assertNoneSent() {
if (this.#sent.length) {
throw new AssertionError2({
message: `Expected zero messages to be sent, instead received "${this.#sent.length}" ${string2.pluralize("message", this.#sent.length)}`
});
}
}
/**
* Assert the mentioned message was queued during the fake
* mode
*/
assertQueued(finder) {
const matchingMessage = this.#queued.find(typeof finder === "function" ? finder : (message) => this.#messageFinder(message, finder));
if (!matchingMessage) {
throw new AssertionError2({
message: `Expected message was not queued`
});
}
}
/**
* Assert the mentioned message was NOT queued during the fake
* mode
*/
assertNotQueued(finder) {
const matchingMessage = this.#queued.find(typeof finder === "function" ? finder : (message) => this.#messageFinder(message, finder));
if (matchingMessage) {
throw new AssertionError2({
message: `Unexpected message was queued`
});
}
}
assertQueuedCount(finder, count) {
if (typeof finder === "number") {
const actual2 = this.#queued.length;
const expected2 = finder;
if (actual2 !== expected2) {
throw new AssertionError2({
message: `Expected to queue "${expected2}" ${string2.pluralize("message", expected2)}, instead received "${actual2}" ${string2.pluralize("message", actual2)}`,
actual: actual2,
expected: expected2
});
}
return;
}
const actual = this.queued(typeof finder === "function" ? finder : (message) => this.#messageFinder(message, finder)).length;
const expected = count;
if (actual !== expected) {
throw new AssertionError2({
message: `Expected to queue "${expected}" ${string2.pluralize("message", expected)}, instead received "${actual}" ${string2.pluralize("message", actual)}`,
actual,
expected
});
}
}
/**
* Assert zero messages were queued
*/
assertNoneQueued() {
if (this.#queued.length) {
throw new AssertionError2({
message: `Expected zero messages to be queued, instead received "${this.#queued.length}" ${string2.pluralize("message", this.#queued.length)}`
});
}
}
};
var FakeMailer = class extends Mailer {
static {
__name(this, "FakeMailer");
}
mails = new MailsCollection();
messages = new MessagesCollection();
constructor(name, emitter, config) {
super(name, new JSONTransport(), emitter, config);
super.setMessenger({
queue: /* @__PURE__ */ __name(async (mail, sendConfig) => {
return this.sendCompiled(mail, sendConfig);
}, "queue")
});
}
/**
* Define the messenger to use for queueing emails.
* The fake mailer ignores using a custom messenger
*/
setMessenger(_) {
return this;
}
/**
* @inheritdoc
*/
async send(callbackOrMail, config) {
if (callbackOrMail instanceof BaseMail) {
this.mails.trackSent(callbackOrMail);
const response2 = await super.send(callbackOrMail, config);
return response2;
}
const response = await super.send((message) => {
callbackOrMail(message);
this.messages.trackSent(message);
}, config);
return response;
}
/**
* @inheritdoc
*/
async sendLater(callbackOrMail, config) {
if (callbackOrMail instanceof BaseMail) {
this.mails.trackQueued(callbackOrMail);
await callbackOrMail.sendLater(this, config);
return;
}
await super.sendLater((message) => {
callbackOrMail(message);
this.messages.trackQueued(message);
}, config);
}
async close() {
this.messages.clear();
this.mails.clear();
super.close();
}
};
// src/mail_manager.ts
import { RuntimeException as RuntimeException2 } from "@poppinss/utils";
var MailManager = class {
static {
__name(this, "MailManager");
}
config;
#emitter;
/**
* Messenger to use on all mailers created
* using the mail manager
*/
#messenger;
/**
* Reference to the fake mailer (if any)
*/
#fakeMailer;
/**
* Cache of mailers
*/
#mailersCache;
constructor(emitter, config) {
this.config = config;
this.#mailersCache = {};
debug_default("creating mail manager %O", config);
this.#emitter = emitter;
}
/**
* Configure the messenger for all the mailers managed
* by the mail manager class.
*/
setMessenger(messenger) {
this.#messenger = messenger;
Object.keys(this.#mailersCache).forEach((name) => {
const mailer = this.#mailersCache[name];
mailer.setMessenger(messenger(mailer));
});
return this;
}
/**
* Send email using the default mailer
*/
send(callbackOrMail, config) {
return this.use().send(callbackOrMail, config);
}
/**
* Queue email using the default mailer
*/
async sendLater(callbackOrMail, config) {
await this.use().sendLater(callbackOrMail, config);
}
/**
* Create/use an instance of a known mailer. The mailer
* instances are cached for the lifecycle of the process
*/
use(mailerName) {
let mailerToUse = mailerName || this.config.default;
if (!mailerToUse) {
throw new RuntimeException2("Cannot create mailer instance. No default mailer is defined in the config");
}
if (!this.config.mailers[mailerToUse]) {
throw new RuntimeException2(`Unknow mailer "${String(mailerToUse)}". Make sure it is configured inside the config file`);
}
if (this.#fakeMailer) {
return this.#fakeMailer;
}
const cachedMailer = this.#mailersCache[mailerToUse];
if (cachedMailer) {
debug_default('using mailer from cache. name: "%s"', cachedMailer);
return cachedMailer;
}
const transportFactory = this.config.mailers[mailerToUse];
debug_default('creating mailer transport. name: "%s"', mailerToUse);
const mailer = new Mailer(mailerToUse, transportFactory(), this.#emitter, this.config);
if (this.#messenger) {
mailer.setMessenger(this.#messenger(mailer));
}
this.#mailersCache[mailerToUse] = mailer;
return mailer;
}
/**
* Turn on fake mode. After this all calls to "mail.use" will
* return an instance of the fake mailer
*/
fake() {
this.restore();
debug_default("creating fake mailer");
this.#fakeMailer = new FakeMailer("fake", this.#emitter, this.config);
return this.#fakeMailer;
}
/**
* Turn off fake mode and restore normal behavior
*/
restore() {
if (this.#fakeMailer) {
this.#fakeMailer.close();
this.#fakeMailer = void 0;
debug_default("restoring mailer fake");
}
}
/**
* Clear mailer from cache and close its transport
*/
async close(mailerName) {
const mailer = this.#mailersCache[mailerName];
if (mailer) {
debug_default("closing mailer %s", mailerName);
await mailer.close();
delete this.#mailersCache[mailerName];
}
}
/**
* Clear all mailers from cache and close their transports
*/
async closeAll() {
await Promise.all(Object.keys(this.#mailersCache).map((mailerName) => this.close(mailerName)));
}
};
// src/define_config.ts
import { configProvider } from "@adonisjs/core";
function defineConfig(config) {
return configProvider.create(async (app) => {
const { mailers, default: defaultMailer, ...rest } = config;
const mailersNames = Object.keys(mailers);
const transports2 = {};
for (let mailerName of mailersNames) {
const mailerTransport = mailers[mailerName];
if (typeof mailerTransport === "function") {
transports2[mailerName] = mailerTransport;
} else {
transports2[mailerName] = await mailerTransport.resolver(app);
}
}
return {
default: defaultMailer,
mailers: transports2,
...rest
};
});
}
__name(defineConfig, "defineConfig");
var transports = {
smtp(config) {
return configProvider.create(async () => {
const { SMTPTransport } = await import("./src/transports/smtp.js");
return () => new SMTPTransport(config);
});
},
ses(config) {
return configProvider.create(async () => {
const { SESTransport } = await import("./src/transports/ses.js");
return () => new SESTransport(config);
});
},
mailgun(config) {
return configProvider.create(async () => {
const { MailgunTransport } = await import("./src/transports/mailgun.js");
return () => new MailgunTransport(config);
});
},
sparkpost(config) {
return configProvider.create(async () => {
const { SparkPostTransport } = await import("./src/transports/sparkpost.js");
return () => new SparkPostTransport(config);
});
},
resend(config) {
return configProvider.create(async () => {
const { ResendTransport } = await import("./src/transports/resend.js");
return () => new ResendTransport(config);
});
},
brevo(config) {
return configProvider.create(async () => {
const { BrevoTransport } = await import("./src/transports/brevo.js");
return () => new BrevoTransport(config);
});
}
};
export {
Message,
BaseMail,
Mailer,
configure,
FakeMailer,
MailManager,
defineConfig,
transports
};
//# sourceMappingURL=chunk-ZEBABAZZ.js.map