Skip to main content

Mailable Classes

Create reusable email classes with clean, organized structure. Mailable classes help you build complex emails with attachments, custom headers, and dynamic content.

Basic Mailable

import { 
Mailable,
MailableEnvelope,
MailableContent
} from 'nestjs-mailable';

export class WelcomeEmail extends Mailable {
constructor(private user: any) {
super();
}

// Define subject, tags, metadata
envelope(): MailableEnvelope {
return {
subject: `Welcome ${this.user.name}!`,
tags: ['welcome', 'onboarding']
};
}

// Define template and data
content(): MailableContent {
return {
template: 'emails/welcome',
with: {
user: this.user,
appName: 'My App'
}
};
}
}

// Usage - must specify recipient
await this.mailService.to(user.email).send(new WelcomeEmail(user));

Mailable Structure

Every mailable has four methods you can use:

1. envelope() - Required

Define subject, tags, and metadata:

envelope(): MailableEnvelope {
return {
subject: 'Your email subject',
tags: ['tag1', 'tag2'], // for tracking
metadata: { userId: '123' } // custom data
};
}

2. content() - Required

Define what goes in the email:

// Template with data
content(): MailableContent {
return {
template: 'emails/welcome',
with: { name: 'John', date: new Date() }
};
}

// HTML content
content(): MailableContent {
return {
html: '<h1>Hello World</h1><p>Welcome!</p>'
};
}

// Text content
content(): MailableContent {
return {
text: 'Hello World\nWelcome!'
};
}

// Markdown content
content(): MailableContent {
return {
markdown: '# Hello World\n\nWelcome to **our app**!'
};
}

3. attachments() - Optional

Add files to the email:

import { AttachmentBuilder } from 'nestjs-mailable';

attachments(): MailableAttachment[] {
return [
// File from path
AttachmentBuilder
.fromPath('./files/guide.pdf')
.as('welcome-guide.pdf')
.build(),

// Generated data
AttachmentBuilder
.fromData(() => this.generateReport(), 'report.csv')
.withMime('text/csv')
.build(),

// File from storage
AttachmentBuilder
.fromStorage('uploads/logo.png')
.as('company-logo.png')
.build()
];
}

4. headers() - Optional

Add custom email headers:

headers(): MailableHeaders {
return {
messageId: `welcome-${this.user.id}@myapp.com`,
references: ['parent-email@myapp.com'],
text: {
'X-Priority': '1',
'X-Custom-Header': 'custom-value'
}
};
}

Complete Example

export class OrderConfirmation extends Mailable {
constructor(
private order: any,
private customer: any
) {
super();
}

envelope(): MailableEnvelope {
return {
subject: `Order Confirmation #${this.order.id}`,
tags: ['order', 'confirmation'],
metadata: {
orderId: this.order.id,
customerId: this.customer.id,
total: this.order.total
}
};
}

content(): MailableContent {
return {
template: 'emails/order-confirmation',
with: {
customerName: this.customer.name,
orderNumber: this.order.id,
items: this.order.items,
total: this.order.total,
trackingUrl: `https://mystore.com/track/${this.order.id}`
}
};
}

attachments(): MailableAttachment[] {
return [
AttachmentBuilder
.fromPath(`./receipts/${this.order.id}.pdf`)
.as(`receipt-${this.order.id}.pdf`)
.withMime('application/pdf')
.build()
];
}

headers(): MailableHeaders {
return {
messageId: `order-${this.order.id}@mystore.com`,
text: {
'X-Order-ID': this.order.id.toString()
}
};
}
}

// Usage
await this.mailService
.to(customer.email)
.send(new OrderConfirmation(order, customer));

More Examples

Password Reset Email

export class PasswordResetEmail extends Mailable {
constructor(
private user: any,
private resetToken: string
) {
super();
}

envelope(): MailableEnvelope {
return {
subject: 'Reset Your Password',
tags: ['password-reset', 'security']
};
}

content(): MailableContent {
return {
template: 'emails/password-reset',
with: {
userName: this.user.name,
resetUrl: `https://myapp.com/reset?token=${this.resetToken}`,
expiresIn: '1 hour'
}
};
}

headers(): MailableHeaders {
return {
text: {
'X-Priority': '1' // High priority
}
};
}
}

Newsletter Email

export class NewsletterEmail extends Mailable {
constructor(
private subscriber: any,
private articles: any[]
) {
super();
}

envelope(): MailableEnvelope {
return {
subject: 'Weekly Newsletter',
tags: ['newsletter', 'weekly'],
metadata: {
subscriberId: this.subscriber.id,
articleCount: this.articles.length
}
};
}

content(): MailableContent {
return {
template: 'emails/newsletter',
with: {
subscriberName: this.subscriber.name,
articles: this.articles,
unsubscribeUrl: `https://myapp.com/unsubscribe/${this.subscriber.token}`
}
};
}

headers(): MailableHeaders {
return {
text: {
'List-Unsubscribe': `<https://myapp.com/unsubscribe/${this.subscriber.token}>`
}
};
}
}

Attachment Types

File Attachments

// PDF from file system
AttachmentBuilder
.fromPath('./documents/manual.pdf')
.as('user-manual.pdf')
.withMime('application/pdf')
.build()

// Image from file system
AttachmentBuilder
.fromPath('./images/logo.png')
.as('company-logo.png')
.build()

Data Attachments

// Generated CSV data
AttachmentBuilder
.fromData(() => this.generateCsvReport(), 'report.csv')
.withMime('text/csv')
.build()

// JSON data
AttachmentBuilder
.fromData(() => JSON.stringify(this.data, null, 2), 'data.json')
.withMime('application/json')
.build()

// PDF from buffer
AttachmentBuilder
.fromData(() => this.generatePdfBuffer(), 'invoice.pdf')
.withMime('application/pdf')
.build()

Storage Attachments

// File from storage directory (relative to storage/)
AttachmentBuilder
.fromStorage('uploads/user-avatar.jpg')
.as('avatar.jpg')
.build()

Dynamic Content

Create emails that change based on data:

export class ConditionalEmail extends Mailable {
constructor(
private user: any,
private isPremium: boolean
) {
super();
}

envelope(): MailableEnvelope {
const subject = this.isPremium
? `✨ Premium Member - Welcome ${this.user.name}!`
: `Welcome ${this.user.name}!`;

return {
subject,
tags: this.isPremium ? ['welcome', 'premium'] : ['welcome', 'basic']
};
}

content(): MailableContent {
const template = this.isPremium
? 'emails/welcome-premium'
: 'emails/welcome-basic';

return {
template,
with: {
user: this.user,
isPremium: this.isPremium,
benefits: this.isPremium ? this.getPremiumBenefits() : []
}
};
}

private getPremiumBenefits() {
return ['Priority Support', 'Advanced Features', 'Early Access'];
}
}

Using in Services

@Injectable()
export class EmailService {
constructor(private mailService: MailService) {}

async sendWelcome(user: any) {
await this.mailService
.to(user.email)
.send(new WelcomeEmail(user));
}

async sendOrderConfirmation(order: any, customer: any) {
await this.mailService
.to(customer.email)
.cc('orders@mystore.com')
.send(new OrderConfirmation(order, customer));
}

async sendBulkNewsletter(subscribers: any[], articles: any[]) {
for (const subscriber of subscribers) {
await this.mailService
.to(subscriber.email)
.send(new NewsletterEmail(subscriber, articles));
}
}
}

Testing Mailables

Test your mailable classes easily:

describe('WelcomeEmail', () => {
it('should build correct content', async () => {
const user = { name: 'John', email: 'john@example.com' };
const email = new WelcomeEmail(user);

const content = await email.build();

expect(content.subject).toBe('Welcome John!');
expect(content.template).toBe('emails/welcome');
expect(content.context.user).toBe(user);
expect(content.tags).toContain('welcome');
});

it('should include attachments', async () => {
const user = { name: 'John', email: 'john@example.com' };
const email = new WelcomeEmail(user);

const content = await email.build();

expect(content.attachments).toHaveLength(1);
expect(content.attachments[0].filename).toBe('welcome-guide.pdf');
});
});

Error Handling

Handle errors when sending mailables:

@Injectable()
export class EmailService {
constructor(private mailService: MailService) {}

async sendEmailSafely(user: any) {
try {
await this.mailService
.to(user.email)
.send(new WelcomeEmail(user));

console.log('Email sent successfully');
} catch (error) {
console.error('Failed to send email:', error.message);
// Log error, retry, or notify admin
}
}
}

Mailable classes keep your email logic organized and make emails easy to test and reuse throughout your application.