Skip to content

Commit 10d2452

Browse files
juandavclaude
andcommitted
blog: add three new posts on templates, transporters, and testing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 39c2883 commit 10d2452

File tree

4 files changed

+363
-2
lines changed

4 files changed

+363
-2
lines changed

apps/website/blog/2020-03-07-mailer.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ authors:
55
tags: [nestjs, mailer, nodemailer]
66
---
77

8-
# Introducing @nestjs-modules/mailer
9-
108
A mailer module for the NestJS framework powered by Nodemailer.
119

10+
<!-- truncate -->
11+
1212
## Install
1313

1414
```bash
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
---
2+
title: "Using Multiple SMTP Transporters in Production"
3+
authors:
4+
- name: Juan David
5+
tags: [nestjs, smtp, transporters, production]
6+
---
7+
8+
Many production applications need more than one email provider. You might send transactional emails through one service and marketing emails through another, or you might want a fallback transporter for reliability.
9+
10+
<!-- truncate -->
11+
12+
## Why Multiple Transporters?
13+
14+
Common scenarios:
15+
16+
- **Separate transactional and marketing emails** - different providers optimize for different use cases
17+
- **Failover** - if your primary SMTP goes down, switch to a backup
18+
- **Regional compliance** - send emails from specific regions based on user location
19+
- **Cost optimization** - use a cheaper provider for bulk sends
20+
21+
## Configuration
22+
23+
Define multiple transporters in your module setup:
24+
25+
```typescript
26+
MailerModule.forRoot({
27+
defaults: {
28+
from: '"App" <noreply@example.com>',
29+
},
30+
// Default transporter
31+
transport: {
32+
host: 'smtp.primary.com',
33+
port: 587,
34+
auth: {
35+
user: 'primary@example.com',
36+
pass: 'password',
37+
},
38+
},
39+
});
40+
```
41+
42+
Then add additional transporters at runtime:
43+
44+
```typescript
45+
@Injectable()
46+
export class EmailService {
47+
constructor(private readonly mailerService: MailerService) {
48+
// Register additional transporters
49+
this.mailerService.addTransporter('marketing', {
50+
host: 'smtp.marketing.com',
51+
port: 587,
52+
auth: {
53+
user: 'marketing@example.com',
54+
pass: 'password',
55+
},
56+
});
57+
58+
this.mailerService.addTransporter('backup', {
59+
host: 'smtp.backup.com',
60+
port: 587,
61+
auth: {
62+
user: 'backup@example.com',
63+
pass: 'password',
64+
},
65+
});
66+
}
67+
}
68+
```
69+
70+
## Sending with a Specific Transporter
71+
72+
Use the `transporterName` option in `sendMail()`:
73+
74+
```typescript
75+
// Uses the default transporter
76+
await this.mailerService.sendMail({
77+
to: 'user@example.com',
78+
subject: 'Order Confirmation',
79+
template: 'order-confirmation',
80+
context: { orderId: '12345' },
81+
});
82+
83+
// Uses the marketing transporter
84+
await this.mailerService.sendMail({
85+
transporterName: 'marketing',
86+
to: 'user@example.com',
87+
subject: 'Weekly Newsletter',
88+
template: 'newsletter',
89+
context: { edition: 42 },
90+
});
91+
```
92+
93+
## Simple Failover Pattern
94+
95+
```typescript
96+
async sendWithFallback(mailOptions: ISendMailOptions) {
97+
try {
98+
return await this.mailerService.sendMail(mailOptions);
99+
} catch (error) {
100+
console.warn('Primary transporter failed, trying backup:', error.message);
101+
return await this.mailerService.sendMail({
102+
...mailOptions,
103+
transporterName: 'backup',
104+
});
105+
}
106+
}
107+
```
108+
109+
## Tips
110+
111+
- **Use environment variables** for all SMTP credentials. Never hardcode them.
112+
- **Monitor delivery rates** per transporter to catch issues early.
113+
- **Set timeouts** on your transporters to avoid hanging requests.
114+
115+
Check the [Configuration](/docs/configuration#multiple-transporters) docs for more details.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
title: "Choosing the Right Template Engine for Your Emails"
3+
authors:
4+
- name: Juan David
5+
tags: [nestjs, templates, handlebars, pug, ejs, mjml]
6+
---
7+
8+
Picking the right template engine can make or break your email development workflow. Here's a practical comparison of the engines supported by `@nestjs-modules/mailer`.
9+
10+
<!-- truncate -->
11+
12+
## Handlebars
13+
14+
The most popular choice. Handlebars keeps logic out of templates and is easy to learn.
15+
16+
```bash
17+
pnpm add @nestjs-modules/mailer-handlebars-adapter handlebars
18+
```
19+
20+
```typescript
21+
import { HandlebarsAdapter } from '@nestjs-modules/mailer/adapters/handlebars.adapter';
22+
23+
MailerModule.forRoot({
24+
template: {
25+
dir: join(__dirname, 'templates'),
26+
adapter: new HandlebarsAdapter(),
27+
options: { strict: true },
28+
},
29+
});
30+
```
31+
32+
**Best for:** Most projects. Simple syntax, great community support, and partials/layouts work well for email structures.
33+
34+
## Pug
35+
36+
Pug's indentation-based syntax produces clean, readable templates with less markup.
37+
38+
```bash
39+
pnpm add @nestjs-modules/mailer-pug-adapter pug
40+
```
41+
42+
```typescript
43+
import { PugAdapter } from '@nestjs-modules/mailer/adapters/pug.adapter';
44+
45+
MailerModule.forRoot({
46+
template: {
47+
dir: join(__dirname, 'templates'),
48+
adapter: new PugAdapter(),
49+
},
50+
});
51+
```
52+
53+
**Best for:** Teams that prefer concise markup and are already familiar with Pug from web projects.
54+
55+
## EJS
56+
57+
EJS uses plain JavaScript inside templates, giving you full control without learning a new syntax.
58+
59+
```bash
60+
pnpm add @nestjs-modules/mailer-ejs-adapter ejs
61+
```
62+
63+
```typescript
64+
import { EjsAdapter } from '@nestjs-modules/mailer/adapters/ejs.adapter';
65+
66+
MailerModule.forRoot({
67+
template: {
68+
dir: join(__dirname, 'templates'),
69+
adapter: new EjsAdapter(),
70+
},
71+
});
72+
```
73+
74+
**Best for:** Developers who want to use plain JavaScript expressions in templates without learning a DSL.
75+
76+
## MJML
77+
78+
MJML is purpose-built for responsive emails. It compiles to battle-tested HTML that works across all email clients.
79+
80+
```bash
81+
pnpm add mjml
82+
```
83+
84+
**Best for:** Production email systems where cross-client rendering is critical. MJML handles the responsive email quirks so you don't have to.
85+
86+
## Quick Comparison
87+
88+
| Engine | Syntax | Learning Curve | Responsive Emails | Use Case |
89+
|------------|-------------|----------------|--------------------|-----------------------|
90+
| Handlebars | Mustache | Low | Manual | General purpose |
91+
| Pug | Indentation | Medium | Manual | Clean markup |
92+
| EJS | JS inline | Low | Manual | JS-native templates |
93+
| MJML | XML tags | Medium | Built-in | Production emails |
94+
95+
## Recommendation
96+
97+
Start with **Handlebars** if you're unsure. It covers most use cases, has the most examples in the community, and integrates cleanly with layouts and partials.
98+
99+
If you're building marketing or transactional emails that must look perfect across Gmail, Outlook, and Apple Mail, consider adding **MJML**.
100+
101+
Check the [Adapters](/docs/adapters) docs for detailed setup instructions.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
title: "Testing Email Sending in NestJS"
3+
authors:
4+
- name: Juan David
5+
tags: [nestjs, testing, jest, e2e]
6+
---
7+
8+
Testing email functionality without actually sending emails is essential for any CI/CD pipeline. Here are practical patterns for unit and integration testing with `@nestjs-modules/mailer`.
9+
10+
<!-- truncate -->
11+
12+
## Unit Testing with a Mock
13+
14+
The simplest approach: mock `MailerService` in your unit tests.
15+
16+
```typescript
17+
import { Test, TestingModule } from '@nestjs/testing';
18+
import { MailerService } from '@nestjs-modules/mailer';
19+
import { NotificationService } from './notification.service';
20+
21+
describe('NotificationService', () => {
22+
let service: NotificationService;
23+
let mailerService: MailerService;
24+
25+
beforeEach(async () => {
26+
const module: TestingModule = await Test.createTestingModule({
27+
providers: [
28+
NotificationService,
29+
{
30+
provide: MailerService,
31+
useValue: {
32+
sendMail: jest.fn().mockResolvedValue({ messageId: 'test-id' }),
33+
},
34+
},
35+
],
36+
}).compile();
37+
38+
service = module.get(NotificationService);
39+
mailerService = module.get(MailerService);
40+
});
41+
42+
it('should send welcome email with correct parameters', async () => {
43+
await service.sendWelcomeEmail('user@example.com', 'John');
44+
45+
expect(mailerService.sendMail).toHaveBeenCalledWith(
46+
expect.objectContaining({
47+
to: 'user@example.com',
48+
subject: expect.stringContaining('Welcome'),
49+
context: expect.objectContaining({ name: 'John' }),
50+
}),
51+
);
52+
});
53+
54+
it('should throw when email fails', async () => {
55+
jest.spyOn(mailerService, 'sendMail').mockRejectedValueOnce(
56+
new Error('SMTP connection refused'),
57+
);
58+
59+
await expect(
60+
service.sendWelcomeEmail('user@example.com', 'John'),
61+
).rejects.toThrow('SMTP connection refused');
62+
});
63+
});
64+
```
65+
66+
## Integration Testing with JSON Transport
67+
68+
For integration tests where you want to verify the full pipeline (templates, context, attachments) without sending real emails, use Nodemailer's `jsonTransport`:
69+
70+
```typescript
71+
import { Test, TestingModule } from '@nestjs/testing';
72+
import { MailerModule, MailerService } from '@nestjs-modules/mailer';
73+
import { HandlebarsAdapter } from '@nestjs-modules/mailer/adapters/handlebars.adapter';
74+
import { join } from 'path';
75+
76+
describe('Email Integration', () => {
77+
let mailerService: MailerService;
78+
79+
beforeEach(async () => {
80+
const module: TestingModule = await Test.createTestingModule({
81+
imports: [
82+
MailerModule.forRoot({
83+
transport: { jsonTransport: true },
84+
template: {
85+
dir: join(__dirname, '../templates'),
86+
adapter: new HandlebarsAdapter(),
87+
},
88+
}),
89+
],
90+
}).compile();
91+
92+
mailerService = module.get(MailerService);
93+
});
94+
95+
it('should render welcome template correctly', async () => {
96+
const result = await mailerService.sendMail({
97+
to: 'user@example.com',
98+
subject: 'Welcome!',
99+
template: 'welcome',
100+
context: { name: 'John', code: 'ABC123' },
101+
});
102+
103+
// jsonTransport returns the message as a JSON string
104+
const message = JSON.parse(result.message);
105+
expect(message.subject).toBe('Welcome!');
106+
expect(message.html).toContain('John');
107+
expect(message.html).toContain('ABC123');
108+
});
109+
});
110+
```
111+
112+
The `jsonTransport` option tells Nodemailer to return the composed email as a JSON object instead of sending it. This lets you inspect the fully rendered HTML, subject, headers, and attachments.
113+
114+
## Testing with Ethereal (Dev/Staging)
115+
116+
For manual testing or staging environments, [Ethereal](https://ethereal.email/) provides a free fake SMTP service that captures emails without delivering them:
117+
118+
```typescript
119+
import * as nodemailer from 'nodemailer';
120+
121+
// Generate test SMTP credentials
122+
const testAccount = await nodemailer.createTestAccount();
123+
124+
MailerModule.forRoot({
125+
transport: {
126+
host: 'smtp.ethereal.email',
127+
port: 587,
128+
auth: {
129+
user: testAccount.user,
130+
pass: testAccount.pass,
131+
},
132+
},
133+
});
134+
```
135+
136+
After sending, you can view captured emails at `https://ethereal.email/messages`.
137+
138+
## Tips
139+
140+
- **Use `jsonTransport` in CI** - it's fast, has no network dependencies, and lets you assert on rendered output.
141+
- **Mock at the service level for unit tests** - don't pull in the full `MailerModule` when you only need to verify your service logic.
142+
- **Use Ethereal for visual testing** - when you need to see what the email actually looks like before going to production.
143+
- **Test error paths** - verify your code handles SMTP failures gracefully.
144+
145+
Check the [Getting Started](/docs/getting-started) guide for setup instructions.

0 commit comments

Comments
 (0)