Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"edge.js": "^6.3.0",
"eslint": "^9.38.0",
"luxon": "^3.7.2",
"mjml": "^4.16.1",
"mjml": "^4.18.0",
"prettier": "^3.6.2",
"release-it": "^19.0.5",
"sinon": "^21.0.0",
Expand All @@ -82,11 +82,11 @@
"dependencies": {
"@poppinss/macroable": "^1.1.0",
"@poppinss/object-builder": "^1.1.0",
"@types/nodemailer": "^7.0.3",
"@types/nodemailer": "^7.0.4",
"fastq": "^1.19.1",
"ical-generator": "^9.0.0",
"ky": "^1.13.0",
"nodemailer": "^7.0.10"
"nodemailer": "^7.0.11"
},
"peerDependencies": {
"@adonisjs/assembler": "^8.0.0-next.14",
Expand Down
9 changes: 9 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@

import { Exception } from '@adonisjs/core/exceptions'

/**
* The error is raised when one or more configuration
* properties are missing or invalid
*/
export const E_INVALID_CONFIG = class InvalidConfigException extends Exception {
static status = 500
static code = 'E_INVALID_CONFIG'
}

/**
* The error is raised when the transport is unable to
* send the email
Expand Down
29 changes: 29 additions & 0 deletions src/transports/base_api_transport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* @adonisjs/mail
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { validateConfig } from '../utils.js'
import type { MailResponse } from '../mail_response.js'
import type { NodeMailerMessage, MailTransportContract } from '../types.js'

/**
* Base class for HTTP based transports to validate the config
* and normalize the base URL
*/
export abstract class BaseApiTransport<
Config extends { key: string; baseUrl: string },
> implements MailTransportContract {
constructor(
protected name: string,
protected config: Config
) {
validateConfig(name, config)
}

abstract send(message: NodeMailerMessage, config?: any): Promise<MailResponse<any>>
}
28 changes: 14 additions & 14 deletions src/transports/brevo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@
*/

import ky from 'ky'
import { type Transport, createTransport } from 'nodemailer'
import { createTransport, type Transport } from 'nodemailer'
import type { Address } from 'nodemailer/lib/mailer/index.js'
import type MailMessage from 'nodemailer/lib/mailer/mail-message.js'

import debug from '../debug.js'
import { MailResponse } from '../mail_response.js'
import { BaseApiTransport } from './base_api_transport.js'
import { normalizeBaseUrl } from '../utils.js'
import { E_MAIL_TRANSPORT_ERROR } from '../errors.js'

import type {
BrevoConfig,
NodeMailerMessage,
BrevoRuntimeConfig,
BrevoSentMessageInfo,
MailTransportContract,
} from '../types.js'

/**
Expand Down Expand Up @@ -116,21 +118,21 @@ class NodeMailerTransport implements Transport {
* Returns base url for sending emails
*/
#getBaseUrl(): string {
return this.#config.baseUrl.replace(/\/$/, '')
return normalizeBaseUrl(this.#config.baseUrl)
}

/**
* Send mail
*/
async send(mail: MailMessage, callback: (err: Error | null, info: BrevoSentMessageInfo) => void) {
const url = `${this.#getBaseUrl()}/smtp/email`
const envelope = mail.message.getEnvelope()
const payload = this.#preparePayload(mail)
try {
const url = `${this.#getBaseUrl()}/smtp/email`
const envelope = mail.message.getEnvelope()
const payload = this.#preparePayload(mail)

debug('brevo email url %s', url)
debug('brevo email payload %O', payload)
debug('brevo email url %s', url)
debug('brevo email payload %O', payload)

try {
const response = await ky.post<{ messageId: string }>(url, {
headers: {
'Accept': 'application/json',
Expand Down Expand Up @@ -161,11 +163,9 @@ class NodeMailerTransport implements Transport {
/**
* Transport for sending emails using the Brevo `/emails/send` API.
*/
export class BrevoTransport implements MailTransportContract {
#config: BrevoConfig

export class BrevoTransport extends BaseApiTransport<BrevoConfig> {
constructor(config: BrevoConfig) {
this.#config = config
super('brevo', config)
}

/**
Expand All @@ -175,7 +175,7 @@ export class BrevoTransport implements MailTransportContract {
message: NodeMailerMessage,
config?: BrevoRuntimeConfig
): Promise<MailResponse<BrevoSentMessageInfo>> {
const brevoTransport = new NodeMailerTransport({ ...this.#config, ...config })
const brevoTransport = new NodeMailerTransport({ ...this.config, ...config })
const transporter = createTransport(brevoTransport)

const brevoResponse = await transporter.sendMail(message)
Expand Down
29 changes: 14 additions & 15 deletions src/transports/mailgun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ import { type Transport, createTransport } from 'nodemailer'
import type MailMessage from 'nodemailer/lib/mailer/mail-message.js'

import debug from '../debug.js'
import { streamToBlob } from '../utils.js'
import { streamToBlob, normalizeBaseUrl } from '../utils.js'
import { MailResponse } from '../mail_response.js'
import { BaseApiTransport } from './base_api_transport.js'
import { E_MAIL_TRANSPORT_ERROR } from '../errors.js'

import type {
MailgunConfig,
NodeMailerMessage,
MailgunRuntimeConfig,
MailTransportContract,
MailgunSentMessageInfo,
} from '../types.js'

Expand Down Expand Up @@ -70,8 +71,8 @@ class NodeMailerTransport implements Transport<MailgunSentMessageInfo> {
*/
#getBaseUrl(): string {
return this.#config.domain
? `${this.#config.baseUrl.replace(/\/$/, '')}/${this.#config.domain}`
: this.#config.baseUrl.replace(/\/$/, '')
? `${normalizeBaseUrl(this.#config.baseUrl)}/${this.#config.domain}`
: normalizeBaseUrl(this.#config.baseUrl)
}

/**
Expand Down Expand Up @@ -185,14 +186,14 @@ class NodeMailerTransport implements Transport<MailgunSentMessageInfo> {
mail: MailMessage,
callback: (err: Error | null, info: MailgunSentMessageInfo) => void
) {
const envelope = mail.message.getEnvelope()
const url = `${this.#getBaseUrl()}/messages.mime`
const form = await this.#createFormData(mail)
try {
const envelope = mail.message.getEnvelope()
const url = `${this.#getBaseUrl()}/messages.mime`
const form = await this.#createFormData(mail)

debug('mailgun mail url %s', url)
debug('mailgun mail envelope %s', envelope)
debug('mailgun mail url %s', url)
debug('mailgun mail envelope %s', envelope)

try {
const response = await ky.post<{ id: string }>(url, {
headers: {
Accept: 'application/json',
Expand Down Expand Up @@ -223,11 +224,9 @@ class NodeMailerTransport implements Transport<MailgunSentMessageInfo> {
* AdonisJS Mail transport for sending emails using the
* Mailgun's `/messages.mime` API endpoint.
*/
export class MailgunTransport implements MailTransportContract {
#config: MailgunConfig

export class MailgunTransport extends BaseApiTransport<MailgunConfig> {
constructor(config: MailgunConfig) {
this.#config = config
super('mailgun', config)
}

/**
Expand All @@ -237,7 +236,7 @@ export class MailgunTransport implements MailTransportContract {
message: NodeMailerMessage,
config?: MailgunRuntimeConfig
): Promise<MailResponse<MailgunSentMessageInfo>> {
const mailgunTransport = new NodeMailerTransport({ ...this.#config, ...config })
const mailgunTransport = new NodeMailerTransport({ ...this.config, ...config })
const transporter = createTransport(mailgunTransport)

const mailgunResponse = await transporter.sendMail(message)
Expand Down
26 changes: 13 additions & 13 deletions src/transports/resend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import type MailMessage from 'nodemailer/lib/mailer/mail-message.js'

import debug from '../debug.js'
import { MailResponse } from '../mail_response.js'
import { BaseApiTransport } from './base_api_transport.js'
import { normalizeBaseUrl } from '../utils.js'
import { E_MAIL_TRANSPORT_ERROR } from '../errors.js'

import type {
ResendConfig,
NodeMailerMessage,
MailTransportContract,
ResendRuntimeConfig,
ResendSentMessageInfo,
} from '../types.js'
Expand Down Expand Up @@ -129,7 +131,7 @@ class NodeMailerTransport implements Transport {
* Returns the normalized base URL for the API
*/
#getBaseUrl() {
return this.#config.baseUrl.replace(/\/$/, '')
return normalizeBaseUrl(this.#config.baseUrl)
}

/**
Expand All @@ -139,14 +141,14 @@ class NodeMailerTransport implements Transport {
mail: MailMessage,
callback: (err: Error | null, info: ResendSentMessageInfo) => void
) {
const url = `${this.#getBaseUrl()}/emails`
const envelope = mail.message.getEnvelope()
const payload = this.#preparePayload(mail)
try {
const url = `${this.#getBaseUrl()}/emails`
const envelope = mail.message.getEnvelope()
const payload = this.#preparePayload(mail)

debug('resend mail url "%s"', url)
debug('resend mail payload %O', payload)
debug('resend mail url "%s"', url)
debug('resend mail payload %O', payload)

try {
const response = await ky.post<{ id: string }>(url, {
json: payload,
headers: {
Expand Down Expand Up @@ -177,11 +179,9 @@ class NodeMailerTransport implements Transport {
/**
* Transport for sending using the Resend `/emails` API.
*/
export class ResendTransport implements MailTransportContract {
#config: ResendConfig

export class ResendTransport extends BaseApiTransport<ResendConfig> {
constructor(config: ResendConfig) {
this.#config = config
super('resend', config)
}

/**
Expand All @@ -191,7 +191,7 @@ export class ResendTransport implements MailTransportContract {
message: NodeMailerMessage,
config?: ResendRuntimeConfig
): Promise<MailResponse<ResendSentMessageInfo>> {
const resendTransport = new NodeMailerTransport({ ...this.#config, ...config })
const resendTransport = new NodeMailerTransport({ ...this.config, ...config })
const transporter = createTransport(resendTransport)

const resendResponse = await transporter.sendMail(message)
Expand Down
32 changes: 16 additions & 16 deletions src/transports/sparkpost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import type MailMessage from 'nodemailer/lib/mailer/mail-message.js'

import debug from '../debug.js'
import { MailResponse } from '../mail_response.js'
import { BaseApiTransport } from './base_api_transport.js'
import { normalizeBaseUrl } from '../utils.js'
import { E_MAIL_TRANSPORT_ERROR } from '../errors.js'

import type {
SparkPostConfig,
NodeMailerMessage,
MailTransportContract,
SparkPostRuntimeConfig,
SparkPostSentMessageInfo,
} from '../types.js'
Expand All @@ -41,7 +43,7 @@ class NodeMailerTransport implements Transport {
* Returns base url for sending emails
*/
#getBaseUrl(): string {
return this.#config.baseUrl.replace(/\/$/, '')
return normalizeBaseUrl(this.#config.baseUrl)
}

/**
Expand Down Expand Up @@ -132,17 +134,17 @@ class NodeMailerTransport implements Transport {
mail: MailMessage,
callback: (err: Error | null, info: SparkPostSentMessageInfo) => void
) {
const url = `${this.#getBaseUrl()}/transmissions`
const options = this.#getOptions(this.#config)
const envelope = mail.message.getEnvelope()
const recipients = this.#getRecipients(mail)
try {
const url = `${this.#getBaseUrl()}/transmissions`
const options = this.#getOptions(this.#config)
const envelope = mail.message.getEnvelope()
const recipients = this.#getRecipients(mail)

debug('sparkpost mail url "%s"', url)
debug('sparkpost mail options %O', options)
debug('sparkpost mail envelope %O', envelope)
debug('sparkpost mail recipients %O', recipients)
debug('sparkpost mail url "%s"', url)
debug('sparkpost mail options %O', options)
debug('sparkpost mail envelope %O', envelope)
debug('sparkpost mail recipients %O', recipients)

try {
/**
* The sparkpost API doesn't accept the multipart stream and hence we
* need to convert the stream to a string
Expand Down Expand Up @@ -181,11 +183,9 @@ class NodeMailerTransport implements Transport {
* AdonisJS mail transport implementation to send emails
* using Sparkpost's `/message.mime` API endpoint.
*/
export class SparkPostTransport implements MailTransportContract {
#config: SparkPostConfig

export class SparkPostTransport extends BaseApiTransport<SparkPostConfig> {
constructor(config: SparkPostConfig) {
this.#config = config
super('sparkpost', config)
}

/**
Expand All @@ -195,7 +195,7 @@ export class SparkPostTransport implements MailTransportContract {
message: NodeMailerMessage,
config?: SparkPostRuntimeConfig
): Promise<MailResponse<SparkPostSentMessageInfo>> {
const nodemailerTransport = new NodeMailerTransport({ ...this.#config, ...config })
const nodemailerTransport = new NodeMailerTransport({ ...this.config, ...config })
const transporter = createTransport(nodemailerTransport)

const sparkPostResponse = await transporter.sendMail(message)
Expand Down
Loading