summary |
---|
Aprenda a enviar e-mails do seu aplicativo AdonisJS usando o pacote @adonisjs/mail. |
Você pode enviar e-mails do seu aplicativo AdonisJS usando o pacote @adonisjs/mail
. O pacote de e-mail é criado em cima do Nodemailer, trazendo as seguintes melhorias de qualidade de vida em relação ao Nodemailer.
- API Fluent para configurar mensagens de e-mail.
- Capacidade de definir e-mails como classes para melhor organização e testes mais fáceis.
- Um conjunto extenso de transportes mantidos oficialmente. Inclui
smtp
,ses
,mailgun
,sparkpost
,resend
ebrevo
. - Experiência de teste aprimorada usando a API Fakes.
- Mensageiro de e-mail para enfileirar e-mails.
- APIs funcionais para gerar eventos de calendário.
Instale e configure o pacote usando o seguinte comando:
node ace add @adonisjs/mail
# Pré-defina transportes para usar via sinalizador CLI
node ace add @adonisjs/mail --transports=resend --transports=smtp
::: details Veja os passos realizados pelo comando add
-
Instala o pacote
@adonisjs/mail
usando o gerenciador de pacotes detectado. -
Registra o seguinte provedor de serviço e comando dentro do arquivo
adonisrc.ts
.{ commands: [ // ...outros comandos () => import('@adonisjs/mail/commands') ], providers: [ // ...outros provedores () => import('@adonisjs/mail/mail_provider') ] }
-
Crie o arquivo
config/mail.ts
. -
Define as variáveis de ambiente e suas validações para os serviços de e-mail selecionados
:::
A configuração do pacote de e-mail é armazenada dentro do arquivo config/mail.ts
. Dentro deste arquivo, você pode configurar vários serviços de e-mail como mailers
para usá-los em seu aplicativo.
Veja também: Config stub
import env from '#start/env'
import { defineConfig, transports } from '@adonisjs/mail'
const mailConfig = defineConfig({
default: 'smtp',
/**
* Um endereço estático para a propriedade "from". Ele será
* usado a menos que um endereço from explícito seja definido no
* Email
*/
from: {
address: '',
name: '',
},
/**
* Um endereço estático para a propriedade "reply-to". Ele será
* usado a menos que um endereço replyTo explícito seja definido no
* E-mail
*/
replyTo: {
address: '',
name: '',
},
/**
* O objeto mailers pode ser usado para configurar vários mailers
* cada um usando um transporte diferente ou o mesmo transporte com um
* opções diferentes.
*/
mailers: {
smtp: transports.smtp({
host: env.get('SMTP_HOST'),
port: env.get('SMTP_PORT'),
}),
resend: transports.resend({
key: env.get('RESEND_API_KEY'),
baseUrl: 'https://api.resend.com',
}),
},
})
O nome do mailer a ser usado por padrão para enviar e-mails.
Um endereço global estático a ser usado para a propriedade from
. O endereço global será usado a menos que um endereço from
explícito seja definido no e-mail.
Um endereço global estático a ser usado para a propriedade reply-to
. O endereço global será usado a menos que um endereço replyTo
explícito seja definido no e-mail.
O objeto mailers
é usado para configurar um ou mais mailers que você deseja usar para enviar e-mails. Você pode alternar entre os mailers em tempo de execução usando o método mail.use
.
A seguir está uma referência completa das opções de configuração aceitas pelos transportes oficialmente suportados.
Veja também: Tipos TypeScript para objeto de configuração
::: details Configuração do Mailgun
As seguintes opções de configuração são enviadas para o endpoint da API /messages.mime
do Mailgun.
{
mailers: {
mailgun: transports.mailgun({
baseUrl: 'https://api.mailgun.net/v3',
key: env.get('MAILGUN_API_KEY'),
domain: env.get('MAILGUN_DOMAIN'),
/**
* As seguintes opções podem ser substituídas em
* tempo de execução ao chamar o método `mail.send`.
*/
oDkim: true,
oTags: ['transactional', 'adonisjs_app'],
oDeliverytime: new Date(2024, 8, 18),
oTestMode: false,
oTracking: false,
oTrackingClick: false,
oTrackingOpens: false,
headers: {
// h:prefixed cabeçalhos
},
variables: {
appId: '',
userId: '',
// v:prefixed variáveis
}
})
}
}
:::
::: details Configuração SMTP
As seguintes opções de configuração são encaminhadas para o Nodemailer como estão. Então, por favor, verifique a documentação do Nodemailer também.
{
mailers: {
smtp: transports.smtp({
host: env.get('SMTP_HOST'),
port: env.get('SMTP_PORT'),
secure: false,
auth: {
type: 'login',
user: env.get('SMTP_USERNAME'),
pass: env.get('SMTP_PASSWORD')
},
tls: {},
ignoreTLS: false,
requireTLS: false,
pool: false,
maxConnections: 5,
maxMessages: 100,
})
}
}
:::
::: details Configuração SES
As seguintes opções de configuração são encaminhadas para o Nodemailer como estão. Então, verifique a documentação do Nodemailer também.
Certifique-se de instalar o pacote @aws-sdk/client-ses
para usar o transporte SES.
{
mailers: {
ses: transports.ses({
/**
* Encaminhado para aws sdk
*/
apiVersion: '2010-12-01',
region: 'us-east-1',
credentials: {
accessKeyId: env.get('AWS_ACCESS_KEY_ID'),
secretAccessKey: env.get('AWS_SECRET_ACCESS_KEY'),
},
/**
* Específico do Nodemailer
*/
sendingRate: 10,
maxConnections: 5,
})
}
}
:::
::: details Configuração do SparkPost
As seguintes opções de configuração são enviadas para o endpoint da API /transmissions
do SparkPost.
{
mailers: {
sparkpost: transports.sparkpost({
baseUrl: 'https://api.sparkpost.com/api/v1',
key: env.get('SPARKPOST_API_KEY'),
/**
* As seguintes opções podem ser substituídas em
* tempo de execução ao chamar o método `mail.send`.
*/
startTime: new Date(),
openTracking: false,
clickTracking: false,
initialOpen: false,
transactional: true,
sandbox: false,
skipSuppression: false,
ipPool: '',
})
}
}
:::
::: details Configuração do Resend
As seguintes opções de configuração são enviadas para o endpoint da API /emails
do Resend.
{
mailers: {
resend: transports.resend({
baseUrl: 'https://api.resend.com',
key: env.get('RESEND_API_KEY'),
/**
* As seguintes opções podem ser substituídas em
* tempo de execução ao chamar o método `mail.send`.
*/
tags: [
{
name: 'category',
value: 'confirm_email'
}
]
})
}
}
:::
Depois que a configuração inicial for concluída, você pode enviar e-mails usando o método mail.send
. O serviço de e-mail é uma instância singleton da classe MailManager criada usando o arquivo de configuração.
O método mail.send
passa uma instância da classe Message para o retorno de chamada e entrega o e-mail usando o mailer default
configurado dentro do arquivo de configuração.
No exemplo a seguir, disparamos um e-mail do controlador após criar uma nova conta de usuário.
import User from '#models/user'
import { HttpContext } from '@adonisjs/core/http'
import mail from '@adonisjs/mail/services/main'
export default class UsersController {
async store({ request }: HttpContext) {
/**
* Apenas para demonstração. Você deve validar os dados
* antes de armazená-los dentro do banco de dados.
*/
const user = await User.create(request.all())
await mail.send((message) => {
message
.to(user.email)
.from('[email protected]')
.subject('Verify your email address')
.htmlView('emails/verify_email', { user })
})
}
}
Como o envio de e-mails pode ser demorado, você pode querer colocá-los em uma fila e enviar e-mails em segundo plano. Você pode fazer o mesmo usando o método mail.sendLater
.
O método sendLater
aceita os mesmos parâmetros que o método send
. No entanto, em vez de enviar o e-mail imediatamente, ele usará o Mail messenger para colocá-lo na fila.
await mail.send((message) => { // [!code --]
await mail.sendLater((message) => { // [!code ++]
message
.to(user.email)
.from('[email protected]')
.subject('Verify your email address')
.htmlView('emails/verify_email', { user })
})
Por padrão, o mail messenger usa uma fila na memória, o que significa que a fila descartará os trabalhos se o seu processo morrer com trabalhos pendentes. Isso pode não ser um grande problema se a interface do usuário do seu aplicativo permitir o reenvio de e-mails com ações manuais. No entanto, você sempre pode configurar um messenger personalizado e usar uma fila com suporte de banco de dados.
npm i bullmq
No exemplo a seguir, usamos o método mail.setMessenger
para configurar uma fila personalizada que usa bullmq
nos bastidores para armazenar trabalhos.
Armazenamos o e-mail compilado, a configuração de tempo de execução e o nome do mailer dentro do trabalho. Mais tarde, usaremos esses dados para enviar e-mails dentro de um processo de trabalho.
import { Queue } from 'bullmq'
import mail from '@adonisjs/mail/services/main'
const emailsQueue = new Queue('emails')
mail.setMessenger((mailer) => {
return {
async queue(mailMessage, config) {
await emailsQueue.add('send_email', {
mailMessage,
config,
mailerName: mailer.name,
})
}
}
})
Finalmente, vamos escrever o código para a fila Worker. Dependendo do fluxo de trabalho do seu aplicativo, você pode ter que iniciar outro processo para os workers processarem os trabalhos.
No exemplo a seguir:
- Processamos trabalhos chamados
send_email
da filaemails
. - Acessamos a mensagem de e-mail compilada, a configuração de tempo de execução e o nome do mailer dos dados do trabalho.
- E enviamos o e-mail usando o método
mailer.sendCompiled
.
import { Worker } from 'bullmq'
import mail from '@adonisjs/mail/services/main'
new Worker('emails', async (job) => {
if (job.name === 'send_email') {
const {
mailMessage,
config,
mailerName
} = job.data
await mail
.use(mailerName)
.sendCompiled(mailMessage, config)
}
})
Isso é tudo! Você pode continuar usando o método mail.sendLater
. No entanto, os e-mails serão enfileirados dentro de um banco de dados redis desta vez.
Você pode alternar entre os mailers configurados usando o método mail.use
. O método mail.use
aceita o nome do mailer (conforme definido dentro do arquivo de configuração) e retorna uma instância da classe Mailer.
import mail from '@adonisjs/mail/services/main'
mail.use() // Instância do mailer padrão
mail.use('mailgun') // Instância do mailer Mailgun
Você pode chamar os métodos mailer.send
ou mailer.sendLater
para enviar e-mail usando uma instância do mailer. Por exemplo:
await mail
.use('mailgun')
.send((message) => {
})
await mail
.use('mailgun')
.sendLater((message) => {
})
As instâncias do mailer são armazenadas em cache durante o ciclo de vida do processo. Você pode usar o método mail.close
para destruir uma instância existente e recriar uma nova instância do zero.
import mail from '@adonisjs/mail/services/main'
/**
* Feche o transporte e remova a instância do cache
*/
await mail.close('mailgun')
/**
* Criar uma nova instância
*/
mail.use('mailgun')
Por padrão, o pacote de e-mail é configurado para usar o mecanismo de modelo Edge para definir o conteúdo de HTML e Texto simples do e-mail.§
No entanto, conforme mostrado no exemplo a seguir, você também pode registrar um mecanismo de modelo personalizado substituindo a propriedade Message.templateEngine
.
Veja também: Definindo conteúdo de e-mail
import { Message } from '@adonisjs/mail'
Message.templateEngine = {
async render(templatePath, data) {
return someTemplateEngine.render(templatePath, data)
}
}
Consulte o guia de referência de eventos para visualizar a lista de eventos despachados pelo pacote @adonisjs/mail
.
As propriedades de um e-mail são definidas usando a classe Message. Uma instância desta classe é fornecida para a função de retorno de chamada criada usando os métodos mail.send
ou mail.sendLater
.
import { Message } from '@adonisjs/mail'
import mail from '@adonisjs/mail/services/main'
await mail.send((message) => {
console.log(message instanceof Message) // true
})
await mail.sendLater((message) => {
console.log(message instanceof Message) // true
})
Você pode definir o assunto do e-mail usando o método message.subject
e o remetente do e-mail usando o método message.from
.
await mail.send((message) => {
message
.subject('Verify your email address')
.from('[email protected]')
})
O método from
aceita o endereço de e-mail como uma string ou um objeto com o nome do remetente e o endereço de e-mail.
message
.from({
address: '[email protected]',
name: 'AdonisJS'
})
O remetente também pode ser definido globalmente dentro do arquivo de configuração. O remetente global será usado se nenhum remetente explícito for definido para uma mensagem individual.
const mailConfig = defineConfig({
from: {
address: '[email protected]',
name: 'AdonisJS'
}
})
Você pode definir os destinatários de e-mail usando os métodos ``message.to,
message.cc` e `message.bcc`. Esses métodos aceitam o endereço de e-mail como uma string ou um objeto com o nome do destinatário e o endereço de e-mail.
await mail.send((message) => {
message
.to(user.email)
.cc(user.team.email)
.bcc(user.team.admin.email)
})
await mail.send((message) => {
message
.to({
address: user.email,
name: user.fullName,
})
.cc({
address: user.team.email,
name: user.team.name,
})
.bcc({
address: user.team.admin.email,
name: user.team.admin.fullName,
})
})
Você pode definir vários destinatários cc
e bcc
como uma matriz de endereços de e-mail ou um objeto com endereços de e-mail e o nome do destinatário.
await mail.send((message) => {
message
.cc(['[email protected]', '[email protected]'])
.bcc([
{
name: 'First recipient',
address: '[email protected]'
},
{
name: 'Second recipient',
address: '[email protected]'
}
])
})
Você também pode definir o endereço de e-mail replyTo
usando o método message.replyTo
.
await mail.send((message) => {
message
.from('[email protected]')
.replyTo('[email protected]')
})
await mail.send((message) => {
/**
* Conteúdo HTML
*/
message.html(`
<h1> Verify email address </h1>
<p> <a href="https://myapp.com">Click here</a> to verify your email address </a>
`)
/**
* Conteúdo de texto simples
*/
message.text(`
Verify email address
Please visit https://myapp.com to verify your email address
`)
})
Como escrever conteúdo em linha pode ser trabalhoso, você pode usar modelos do Edge. Se você já configurou o Edge, você pode usar os métodos message.htmlView
e message.textView
para renderizar modelos.
# Criar modelos
node ace make:view emails/verify_email_html
node ace make:view emails/verify_email_text
// Use-os para definir conteúdos
await mail.send((message) => {
message.htmlView('emails/verify_email_html', stateToShare)
message.textView('emails/verify_email_text', stateToShare)
})
MJML é uma linguagem de marcação para criar e-mails sem escrever todo o HTML complexo para fazer seus e-mails ficarem bem em todos os clientes de e-mail.
O primeiro passo é instalar o pacote mjml do npm.
npm i mjml
Uma vez feito isso, você pode escrever a marcação MJML dentro dos seus modelos Edge, envolvendo-a dentro da tag @mjml
.
::: info NOTA
Como a saída do MJML contém as tags html
, head
e body
, não é necessário defini-las dentro dos seus modelos Edge.
:::
@mjml()
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>
Hello World!
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
@end
Você pode passar as opções de configuração MJML como props para a tag @mjml
.
@mjml({
keepComments: false,
fonts: {
Lato: 'https://fonts.googleapis.com/css?family=Lato:400,500,700'
}
})
Você pode usar o método message.attach
para enviar anexos em um e-mail. O método attach
aceita um caminho absoluto ou uma URL do sistema de arquivos de um arquivo que você deseja enviar como anexo.
import app from '@adonisjs/core/services/app'
await mail.send((message) => {
message.attach(app.makePath('uploads/invoice.pdf'))
})
Você pode definir o nome do arquivo para o anexo usando a propriedade options.filename
.
message.attach(app.makePath('uploads/invoice.pdf'), {
filename: 'invoice_october_2023.pdf'
})
A lista completa de opções aceitas pelo método message.attach
segue.
Opção | Descrição |
---|---|
filename |
O nome de exibição do anexo. O padrão é o nome base do caminho do anexo. |
contentType |
O tipo de conteúdo do anexo. Se não for definido, o contentType será inferido da extensão do arquivo. |
contentDisposition |
Tipo de disposição de conteúdo do anexo. O padrão é attachment . |
headers |
Cabeçalhos personalizados para o nó do anexo. A propriedade headers é um par chave-valor. |
Você pode criar anexos de e-mail de fluxos e buffers usando o método message.attachData
. O método aceita um fluxo legível ou o buffer como o primeiro argumento e o objeto de opções como o segundo argumento.
::: info NOTA
O método message.attachData
não deve ser usado ao enfileirar e-mails usando o método mail.sendLater
. Como os trabalhos enfileirados são serializados e persistidos dentro de um banco de dados, anexar dados brutos aumentará o tamanho do armazenamento.
Além disso, enfileirar um e-mail falhará se você anexar um fluxo usando o método message.attachData
.
:::
message.attachData(fs.createReadStream('./invoice.pdf'), {
filename: 'invoice_october_2023.pdf'
})
message.attachData(Buffer.from('aGVsbG8gd29ybGQh'), {
encoding: 'base64',
filename: 'greeting.txt',
})
Você pode incorporar imagens dentro do conteúdo do seu e-mail usando o auxiliar de visualização embedImage
. O método embedImage
sob o capô usa CID para marcar a imagem como um anexo e usa seu ID de conteúdo como a fonte da imagem.
<img src="{{
embedImage(app.makePath('assets/hero.jpg'))
}}" />
A seguir está o HTML de saída
<img src="cid:a-random-content-id" />
O anexo a seguir será definido automaticamente na carga útil do e-mail.
{
attachments: [{
path: '/root/app/assets/hero.jpg',
filename: 'hero.jpg',
cid: 'a-random-content-id'
}]
}
Assim como o método embedImage
, você pode usar o método embedImageData
para incorporar uma imagem de dados brutos.
<img src="{{
embedImageData(rawBuffer, { filename: 'hero.jpg' })
}}" />
Você pode anexar eventos de calendário a um e-mail usando o método message.icalEvent
. O método icalEvent
aceita o conteúdo do evento como o primeiro parâmetro e o objeto options
como o segundo parâmetro.
const contents = 'BEGIN:VCALENDAR\r\nPRODID:-//ACME/DesktopCalendar//EN\r\nMETHOD:REQUEST\r\n...'
await mail.send((message) => {
message.icalEvent(contents, {
method: 'PUBLISH',
filename: 'invite.ics',
})
})
Como definir o conteúdo do arquivo de evento manualmente pode ser trabalhoso, você pode passar uma função de retorno de chamada para o método icalEvent
e gerar o conteúdo do convite usando a API JavaScript.
O objeto calendar
fornecido para a função de retorno de chamada é uma referência do pacote npm ical-generator, então certifique-se de ler o arquivo README do pacote também.
message.icalEvent((calendar) => {
calendar
.createEvent({
summary: 'Adding support for ALS',
start: DateTime.local().plus({ minutes: 30 }),
end: DateTime.local().plus({ minutes: 60 }),
})
}, {
method: 'PUBLISH',
filename: 'invite.ics',
})
Você pode definir o conteúdo do convite de um arquivo ou URL HTTP usando os métodos icalEventFromFile
ou icalEventFromUrl
.
message.icalEventFromFile(
app.resourcesPath('calendar-invites/invite.ics'),
{
filename: 'invite.ics',
method: 'PUBLISH'
}
)
message.icalEventFromFile(
'https://myapp.com/users/1/invite.ics',
{
filename: 'invite.ics',
method: 'PUBLISH'
}
)
Você pode definir cabeçalhos de e-mail adicionais usando o método message.header
. O método aceita a chave do cabeçalho como o primeiro parâmetro e o valor como o segundo parâmetro.
message.header('x-my-key', 'header value')
/**
* Defina uma matriz de valores
*/
message.header('x-my-key', ['header value', 'another value'])
Por padrão, os cabeçalhos de e-mail são codificados e dobrados para atender ao requisito de ter mensagens ASCII simples com linhas de no máximo 78 bytes. No entanto, se quiser ignorar as regras de codificação, você pode definir um cabeçalho usando o método message.preparedHeader
.
message.preparedHeader(
'x-unprocessed',
'a really long header or value with non-ascii characters 👮',
)
A classe message inclui métodos auxiliares para definir cabeçalhos complexos como List-Unsubscribe ou List-Help com facilidade. Você pode aprender sobre as regras de codificação para cabeçalhos List
no site do nodemailer.
message.listHelp('[email protected]?subject=help')
// List-Help: <mailto:[email protected]?subject=help>
message.listUnsubscribe({
url: 'http://example.com',
comment: 'Comment'
})
// List-Unsubscribe: <http://example.com> (Comment)
/**
* Repetindo o cabeçalho várias vezes
*/
message.listSubscribe('[email protected]?subject=subscribe')
message.listSubscribe({
url: 'http://example.com',
comment: 'Subscribe'
})
// List-Subscribe: <mailto:[email protected]?subject=subscribe>
// List-Subscribe: <http://example.com> (Subscribe)
Para todos os outros cabeçalhos List
arbitrários, você pode usar o método addListHeader
.
message.addListHeader('post', 'http://example.com/post')
// List-Post: <http://example.com/post>
Em vez de escrever e-mails dentro do fechamento do método mail.send
, você pode movê-los para classes de e-mail dedicadas para melhor organização e testes mais fáceis.
As classes de e-mail são armazenadas dentro do diretório ./app/mails
, e cada arquivo representa um único e-mail. Você pode criar uma classe de e-mail executando o comando ace make:mail
.
Veja também: Comando Make mail
node ace make:mail verify_email
A classe mail estende a classe BaseMail e é estruturada com as seguintes propriedades e métodos. Você pode configurar a mensagem de e-mail dentro do método prepare
usando a propriedade this.message
.
import User from '#models/user'
import { BaseMail } from '@adonisjs/mail'
export default class VerifyEmailNotification extends BaseMail {
from = '[email protected]'
subject = 'Verify email'
prepare() {
this.message.to('[email protected]')
}
}
Configure o endereço de e-mail do remetente. Se você omitir essa propriedade, deverá chamar o método message.from
para definir o remetente.
Configure o assunto do e-mail. Se você omitir essa propriedade, deverá usar o método message.subject
para definir o assunto do e-mail.
Configure o endereço de e-mail replyTo
.
O método prepare
é chamado automaticamente pelo método build
para preparar a mensagem de e-mail para envio.
Você deve definir o conteúdo do e-mail, anexos, destinatários, etc., dentro deste método.
O método build
é herdado da classe BaseMail
. O método é chamado automaticamente no momento do envio do e-mail.
Certifique-se de referenciar a implementação original se você decidir substituir este método.
Você pode chamar o método mail.send
e passar uma instância da classe mail para enviar o e-mail. Por exemplo:
// Enviar e-mail
import mail from '@adonisjs/mail/services/main'
import VerifyEmailNotification from '#mails/verify_email'
await mail.send(new VerifyEmailNotification())
// Fila de e-mail
import mail from '@adonisjs/mail/services/main'
import VerifyEmailNotification from '#mails/verify_email'
await mail.sendLater(new VerifyEmailNotification())
Você pode compartilhar dados com a classe mail usando argumentos construtores. Por exemplo:
/**
* Criando um usuário
*/
const user = await User.create(payload)
await mail.send(
/**
* Passando o usuário para a classe de e-mail
*/
new VerifyEmailNotification(user)
)
Um dos principais benefícios de usar classes Mail é uma melhor experiência de teste. Você pode criar classes mail sem enviá-las e escrever asserções para as propriedades da mensagem.
import { test } from '@japa/runner'
import VerifyEmailNotification from '#mails/verify_email'
test.group('Verify email notification', () => {
test('prepare email for sending', async () => {
const email = new VerifyEmailNotification()
/**
* Crie uma mensagem de e-mail e renderize modelos para
* calcular o HTML do e-mail e o texto simples
* conteúdo
*/
await email.buildWithContents()
/**
* Escreva afirmações para garantir que a mensagem seja construída
* conforme o esperado
*/
email.message.assertTo('[email protected]')
email.message.assertFrom('[email protected]')
email.message.assertSubject('Verify email address')
email.message.assertReplyTo('[email protected]')
})
})
Você pode escrever asserções para o conteúdo da mensagem da seguinte forma.
const email = new VerifyEmailNotification()
await email.buildWithContents()
email.message.assertHtmlIncludes(
`<a href="/emails/1/verify"> Verify email address </a>`
)
email.message.assertTextIncludes('Verify email address')
Além disso, você pode escrever asserções para os anexos. As asserções funcionam apenas com anexos baseados em arquivo e não para fluxos ou conteúdo bruto.
const email = new VerifyEmailNotification()
await email.buildWithContents()
email.message.assertAttachment(
app.makePath('uploads/invoice.pdf')
)
Sinta-se à vontade para olhar o código-fonte da classe Message para todos os métodos de asserção disponíveis.
Você pode querer usar o Fake mailer durante o teste para impedir que seu aplicativo envie e-mails. O Fake mailer coleta todos os e-mails de saída na memória e oferece uma API fácil de usar para escrever asserções contra eles.
No exemplo a seguir:
- FakeMailer usando o método
mail.fake
. - Em seguida, chamamos a API de endpoint
/register
. - Por fim, usamos a propriedade
mails
do remetente falso para afirmar queVerifyEmailNotification
foi enviado.
import { test } from '@japa/runner'
import mail from '@adonisjs/mail/services/main'
import VerifyEmailNotification from '#mails/verify_email'
test.group('Users | register', () => {
test('create a new user account', async ({ client, route }) => {
/**
* Ative o modo falso
*/
const { mails } = mail.fake()
/**
* Faça uma chamada de API
*/
await client
.post(route('users.store'))
.send(userData)
/**
* Afirme que o controlador realmente enviou o
* VerifyEmailNotification mail
*/
mails.assertSent(VerifyEmailNotification, ({ message }) => {
return message
.hasTo(userData.email)
.hasSubject('Verify email address')
})
})
})
Depois de terminar de escrever o teste, você deve restaurar o falso usando o método mail.restore
.
test('create a new user account', async ({ client, route, cleanup }) => {
const { mails } = mail.fake()
/**
* Os ganchos de limpeza são executados após o teste
* terminar com sucesso ou com um erro.
*/
cleanup(() => {
mail.restore()
})
})
O método mails.assertSent
aceita o construtor da classe mail como o primeiro argumento e lança uma exceção quando não consegue encontrar nenhum e-mail para a classe esperada.
const { mails } = mail.fake()
/**
* Afirme que o e-mail foi enviado
*/
mails.assertSent(VerifyEmailNotification)
Você pode passar uma função de retorno de chamada para o método assertSent
para verificar se o e-mail foi enviado ao destinatário esperado ou tem o assunto correto.
A função de retorno de chamada recebe uma instância da classe mail e você pode usar a propriedade .message
para obter acesso ao objeto message.
mails.assertSent(VerifyEmailNotification, (email) => {
return email.message.hasTo(userData.email)
})
Você pode executar asserções no objeto message
dentro do retorno de chamada. Por exemplo:
mails.assertSent(VerifyEmailNotification, (email) => {
email.message.assertTo(userData.email)
email.message.assertFrom('[email protected]')
email.message.assertSubject('Verify your email address')
/**
* Todas as asserções foram aprovadas, então retorne true para considerar o
* e-mail como enviado.
*/
return true
})
Você pode usar o método mails.assertNotSent
para afirmar que um e-mail não foi enviado durante o teste. Este método é o oposto do método assertSent
e aceita os mesmos argumentos.
const { mails } = mail.fake()
mails.assertNotSent(PasswordResetNotification)
Finalmente, você pode afirmar a contagem de e-mails enviados usando os métodos assertSentCount
e assertNoneSent
.
const { mails } = mail.fake()
// Afirmar que 2 e-mails foram enviados no total
mails.assertSentCount(2)
// Afirmar que apenas um VerifyEmailNotification foi enviado
mails.assertSentCount(VerifyEmailNotification, 1)
const { mails } = mail.fake()
// Afirmar que zero e-mails foram enviados
mails.assertNoneSent()
Se você tiver e-mails enfileirados usando o método mail.sendLater
, você pode usar os seguintes métodos para escrever asserções para eles.
const { mails } = mail.fake()
/**
* Afirmar que o e-mail "VerifyEmailNotification" foi enfileirado
* Opcionalmente, você pode passar a função finder para
* restringir o e-mail
*/
mails.assertQueued(VerifyEmailNotification)
/**
* Afirmar que o e-mail "VerifyEmailNotification" não foi enfileirado
* Opcionalmente, você pode passar a função finder para
* restringir o e-mail
*/
mails.assertNotQueued(PasswordResetNotification)
/**
* Afirmar que dois e-mails foram enfileirados no total.
*/
mails.assertQueuedCount(2)
/**
* Afirmar que o e-mail "VerifyEmailNotification" foi colocado na fila
* apenas uma vez
*/
mails.assertQueuedCount(VerifyEmailNotification , 1)
/**
* Afirmar que nada foi enfileirado
*/
mails.assertNoneQueued()
Você pode usar os métodos mails.sent
ou mails.queued
para obter uma matriz de e-mails enviados/enfileirados durante os testes.
const { mails } = mail.fake()
const sentEmails = mails.sent()
const queuedEmails = mails.queued()
const email = sentEmails.find((email) => {
return email instanceof VerifyEmailNotification
})
if (email) {
email.message.assertTo(userData.email)
email.message.assertFrom(userData.email)
email.message.assertHtmlIncludes('<a href="/verify/email"> Verify your email address</a>')
}
Os transportes do AdonisJS Mail são construídos em cima dos transportes do Nodemailer; portanto, você deve criar/usar um transporte do nodemailer antes de registrá-lo com o pacote Mail.
Neste guia, vamos encapsular o nodemailer-postmark-transport em um transporte de e-mail do AdonisJS.
npm i nodemailer nodemailer-postmark-transport
Como você pode ver no exemplo a seguir, o trabalho pesado de enviar um e-mail é feito pelo nodemailer
. O transporte do AdonisJS atua como um adaptador encaminhando a mensagem para o nodemailer e normalizando sua resposta para uma instância de MailResponse.
import nodemailer from 'nodemailer'
import nodemailerTransport from 'nodemailer-postmark-transport'
import { MailResponse } from '@adonisjs/mail'
import type {
NodeMailerMessage,
MailTransportContract
} from '@adonisjs/mail/types'
/**
* Configuração aceita pelo transporte
*/
export type PostMarkConfig = {
auth: {
apiKey: string
}
}
/**
* Implementação do transporte
*/
export class PostMarkTransport implements MailTransportContract {
#config: PostMarkConfig
constructor(config: PostMarkConfig) {
this.#config = config
}
#createNodemailerTransport(config: PostMarkConfig) {
return nodemailer.createTransport(nodemailerTransport(config))
}
async send(
message: NodeMailerMessage,
config?: PostMarkConfig
): Promise<MailResponse> {
/**
* Criar transporte nodemailer
*/
const transporter = this.#createNodemailerTransport({
...this.#config,
...config,
})
/**
* Enviar e-mail
*/
const response = await transporter.sendMail(message)
/**
* Normalizar resposta a uma instância da classe "MailResponse"
*/
return new MailResponse(response.messageId, response.envelope, response)
}
}
Para referenciar o transporte acima dentro do arquivo config/mail.ts
, você deve criar uma função de fábrica que retorne uma instância do transporte.
Você pode escrever o código a seguir dentro do mesmo arquivo da implementação do seu transporte.
import type {
NodeMailerMessage,
MailTransportContract,
MailManagerTransportFactory // [!code ++]
} from '@adonisjs/mail/types'
export function postMarkTransport(
config: PostMarkConfig
): MailManagerTransportFactory {
return () => {
return new PostMarkTransport(config)
}
}
Finalmente, você pode referenciar o transporte dentro do seu arquivo de configuração usando o auxiliar postMarkTransport
.
import env from '#start/env'
import { defineConfig } from '@adonisjs/mail'
import { postMarkTransport } from 'my-custom-package'
const mailConfig = defineConfig({
mailers: {
postmark: postMarkTransport({
auth: {
apiKey: env.get('POSTMARK_API_KEY'),
},
}),
},
})