Envio de E-mails
Enviar e-mails é essencial para confirmação de conta, recuperação de senha e notificações.
Setup com Nodemailer
bash
npm install nodemailernpm install -D @types/nodemailerConfiguração
typescript
// src/config/mail.tsimport nodemailer from 'nodemailer'; const transporter = nodemailer.createTransport({ host: process.env.MAIL_HOST, port: Number(process.env.MAIL_PORT), secure: process.env.MAIL_SECURE === 'true', auth: { user: process.env.MAIL_USER, pass: process.env.MAIL_PASS, },}); export default transporter;Serviço de E-mail
typescript
// src/services/mail.service.tsimport transporter from '../config/mail';import { User } from '@prisma/client'; interface SendMailOptions { to: string; subject: string; html: string;} export class MailService { private from = `"E-commerce" <${process.env.MAIL_FROM}>`; async send({ to, subject, html }: SendMailOptions): Promise<void> { await transporter.sendMail({ from: this.from, to, subject, html, }); } async sendWelcome(user: User): Promise<void> { await this.send({ to: user.email, subject: 'Bem-vindo ao E-commerce!', html: ` <h1>Olá, ${user.name}!</h1> <p>Sua conta foi criada com sucesso.</p> <p>Comece a explorar nossos produtos!</p> `, }); } async sendPasswordReset(user: User, token: string): Promise<void> { const resetUrl = `${process.env.FRONTEND_URL}/reset-password?token=${token}`; await this.send({ to: user.email, subject: 'Recuperação de Senha', html: ` <h1>Recuperação de Senha</h1> <p>Você solicitou a recuperação de senha.</p> <p>Clique no link abaixo para criar uma nova senha:</p> <a href="${resetUrl}">Redefinir Senha</a> <p>O link expira em 1 hora.</p> <p>Se você não solicitou, ignore este e-mail.</p> `, }); } async sendOrderConfirmation(user: User, order: any): Promise<void> { const itemsHtml = order.items .map((item: any) => `<li>${item.product.name} x ${item.quantity} - R$ ${item.price}</li>`) .join(''); await this.send({ to: user.email, subject: `Pedido #${order.id} Confirmado`, html: ` <h1>Pedido Confirmado!</h1> <p>Obrigado pela sua compra, ${user.name}!</p> <h2>Itens:</h2> <ul>${itemsHtml}</ul> <p><strong>Total: R$ ${order.total}</strong></p> `, }); }}Templates com Handlebars
bash
npm install handlebarstypescript
// src/services/mail-template.service.tsimport Handlebars from 'handlebars';import fs from 'fs';import path from 'path'; export class MailTemplateService { private templatesDir = path.resolve(__dirname, '..', 'templates', 'emails'); compile(templateName: string, data: Record<string, any>): string { const templatePath = path.join(this.templatesDir, `${templateName}.hbs`); const source = fs.readFileSync(templatePath, 'utf-8'); const template = Handlebars.compile(source); return template(data); }}handlebars
<!-- src/templates/emails/welcome.hbs --><!DOCTYPE html><html><head> <style> .container { max-width: 600px; margin: 0 auto; font-family: Arial; } .header { background: #4F46E5; color: white; padding: 20px; } .content { padding: 20px; } .button { background: #4F46E5; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; } </style></head><body> <div class="container"> <div class="header"> <h1>Bem-vindo!</h1> </div> <div class="content"> <p>Olá, {{name}}!</p> <p>Sua conta foi criada com sucesso.</p> <a href="{{shopUrl}}" class="button">Começar a Comprar</a> </div> </div></body></html>Filas com Bull
bash
npm install bullnpm install -D @types/bulltypescript
// src/jobs/mail.job.tsimport Bull from 'bull';import { MailService } from '../services/mail.service'; interface MailJobData { to: string; subject: string; html: string;} const mailQueue = new Bull<MailJobData>('mail', { redis: process.env.REDIS_URL,}); const mailService = new MailService(); mailQueue.process(async (job) => { await mailService.send(job.data);}); export const addMailJob = (data: MailJobData) => { return mailQueue.add(data, { attempts: 3, backoff: { type: 'exponential', delay: 2000 }, });};Resumo
- ✅ Nodemailer para envio
- ✅ Templates HTML com Handlebars
- ✅ E-mails transacionais (boas-vindas, pedido)
- ✅ Filas para envio assíncrono
Próxima aula: Cache com Redis! ⚡