platform-mailing
Multi-strategy email delivery system with pluggable transports (SMTP, Postmark, Resend, SendGrid, Mailgun, Log), template engines (file-based, blob-based), and email verification support. Each external provider SDK is an optional peer dependency — install only the one you need.
Package: @breadstone/archipel-platform-mailing
npm install @breadstone/archipel-platform-mailingArchitecture
graph TD
Module["MailModule"]
MailService["MailService<br/><i>send() / sendTemplate()</i>"]
Base["DeliveryStrategyBase<br/><i>Abstract send() contract</i>"]
SMTP["SmtpDeliveryStrategy<br/><i>Built-in (nodemailer)</i>"]
Log["LogDeliveryStrategy<br/><i>Built-in (dev)</i>"]
Postmark["PostmarkDeliveryStrategy<br/><i>postmark</i>"]
Resend["ResendDeliveryStrategy<br/><i>resend</i>"]
SendGrid["SendGridDeliveryStrategy<br/><i>@sendgrid/mail</i>"]
Mailgun["MailgunDeliveryStrategy<br/><i>mailgun.js</i>"]
Module --> MailService
MailService --> Base
SMTP -->|extends| Base
Log -->|extends| Base
Postmark -->|extends| Base
Resend -->|extends| Base
SendGrid -->|extends| Base
Mailgun -->|extends| BaseQuick Start
import { Module } from '@nestjs/common';
import { MailModule } from '@breadstone/archipel-platform-mailing';
@Module({
imports: [MailModule],
})
export class AppModule {}The module auto-selects delivery and template strategies based on environment variables.
Environment Variables
Core
| Variable | Description | Values | Required |
|---|---|---|---|
MAIL_SENDER_EMAIL | Default "from" address | noreply@example.com | Yes |
MAIL_DELIVERY_STRATEGY | Transport strategy | 'smtp' / 'postmark' / 'resend' / 'sendgrid' / 'mailgun' / 'log' | Yes |
MAIL_TEMPLATE_STRATEGY | Template source | 'file' / 'blob' | Yes |
MAIL_TEMPLATE_ENGINE_FORMAT | Template format | 'html' / 'txt' | Yes |
SMTP (built-in)
| Variable | Description | Default | Required |
|---|---|---|---|
MAIL_SMTP_HOST | SMTP server host | localhost | If SMTP |
MAIL_SMTP_PORT | SMTP server port | 587 | If SMTP |
MAIL_SMTP_SECURE | Use TLS | false | No |
MAIL_SMTP_USER | Auth username | — | If SMTP |
MAIL_SMTP_PASSWORD | Auth password | — | If SMTP |
Delivery Providers
Postmark
Subpath: @breadstone/archipel-platform-mailing/postmarkSDK: postmark ≥ 4.0.0
| Variable | Description | Required |
|---|---|---|
MAIL_POSTMARK_API_KEY | Postmark API key | Yes |
import { PostmarkDeliveryStrategy, POSTMARK_CONFIG_ENTRIES } from '@breadstone/archipel-platform-mailing/postmark';Resend
Subpath: @breadstone/archipel-platform-mailing/resendSDK: resend ≥ 4.0.0
| Variable | Description | Required |
|---|---|---|
MAIL_RESEND_API_KEY | Resend API key | Yes |
import { ResendDeliveryStrategy, RESEND_CONFIG_ENTRIES } from '@breadstone/archipel-platform-mailing/resend';SendGrid
Subpath: @breadstone/archipel-platform-mailing/sendgridSDK: @sendgrid/mail ≥ 8.0.0
| Variable | Description | Required |
|---|---|---|
MAIL_SENDGRID_API_KEY | SendGrid API key | Yes |
import { SendGridDeliveryStrategy, SENDGRID_CONFIG_ENTRIES } from '@breadstone/archipel-platform-mailing/sendgrid';Mailgun
Subpath: @breadstone/archipel-platform-mailing/mailgunSDK: mailgun.js ≥ 10.0.0
| Variable | Description | Required |
|---|---|---|
MAIL_MAILGUN_API_KEY | Mailgun API key | Yes |
MAIL_MAILGUN_DOMAIN | Mailgun domain (e.g. mg.example.com) | Yes |
import { MailgunDeliveryStrategy, MAILGUN_CONFIG_ENTRIES } from '@breadstone/archipel-platform-mailing/mailgun';Log (built-in)
Logs emails to the console. No SDK or env vars required — useful for development and testing.
Switching Providers
Change a single environment variable — no code change needed:
# Switch from SMTP to Resend
MAIL_DELIVERY_STRATEGY=resend
MAIL_RESEND_API_KEY=re_xxxxxxxxxxxxDeliveryStrategyBase
All providers extend this abstract contract:
abstract class DeliveryStrategyBase {
abstract send(
from: string,
to: string,
subject: string,
content: string,
isHtml: boolean,
attachments?: Array<IMailAttachment>,
): Promise<void>;
}Attachment Support
MailService.send() accepts an optional fourth parameter for file attachments:
import type { IMailAttachment } from '@breadstone/archipel-platform-mailing';
const attachment: IMailAttachment = {
filename: 'invoice.pdf',
content: pdfBuffer, // Buffer or string
contentType: 'application/pdf',
};
await this._mail.send(
{ to: 'user@example.com', subject: 'Your Invoice' },
'<p>Please find your invoice attached.</p>',
true,
[attachment],
);Validation
| Constraint | Detail |
|---|---|
| MIME types | PDF, Word (.doc/.docx), Excel (.xls/.xlsx), ZIP, PNG, JPEG, GIF, WebP, plain text, CSV |
| Max size | 25 MB per attachment |
| Behavior | Invalid MIME type or oversized attachments throw immediately — the email is not sent |
IMailAttachment Interface
interface IMailAttachment {
readonly filename: string;
readonly content: Buffer | string;
readonly contentType: string;
}MailService
Core service for sending emails.
Send Plain Email
import { MailService } from '@breadstone/archipel-platform-mailing';
@Injectable()
export class NotificationService {
constructor(private readonly _mail: MailService) {}
public async sendWelcome(userEmail: string, userName: string): Promise<void> {
await this._mail.send(
{ to: userEmail, subject: `Welcome, ${userName}!` },
`<h1>Welcome</h1><p>Hey ${userName}, we're glad you're here.</p>`,
true, // isHtml
);
}
}Send Templated Email
await this._mail.sendTemplate(
{ to: userEmail, subject: 'Your Order Confirmation' },
{
templateName: 'order-confirmation',
templateParams: {
orderNumber: 'ORD-12345',
total: '€39.97',
},
},
);Custom Sender
await this._mail.send(
{
from: 'support@example.com', // overrides MAIL_SENDER_EMAIL
to: 'customer@example.com',
subject: 'Support Ticket #4567',
},
'Your ticket has been received.',
false, // plain text
);Template Strategies
File-Based Templates
Load templates from the filesystem:
// Set MAIL_TEMPLATE_STRATEGY=file
// Templates stored as files: assets/templates/welcome.htmlBlob-Based Templates
Load templates from blob storage (e.g., Vercel Blob):
// Set MAIL_TEMPLATE_STRATEGY=blob
// Templates stored in blob storage: templates/welcome.htmlSmtpConnectionVerifier
Verifies SMTP server connectivity. Replaces the deprecated MailVerificationService.
import { SmtpConnectionVerifier } from '@breadstone/archipel-platform-mailing';
@Injectable()
export class HealthCheckService {
constructor(private readonly _smtpVerifier: SmtpConnectionVerifier) {}
public async checkSmtp(): Promise<boolean> {
return this._smtpVerifier.verifyConnection();
}
public async sendTestEmail(to: string): Promise<boolean> {
return this._smtpVerifier.sendTestEmail(to);
}
}MailVerificationService (Deprecated)
Deprecated
MailVerificationService is deprecated. Use SmtpConnectionVerifier instead — it provides verifyConnection() for SMTP connectivity checks and sendTestEmail() for sending test messages.
import { MailVerificationService } from '@breadstone/archipel-platform-mailing';
@Injectable()
export class RegistrationService {
constructor(private readonly _mailVerification: MailVerificationService) {}
public async sendVerification(email: string): Promise<void> {
await this._mailVerification.sendVerificationEmail(email);
}
}MailModule.register()
The MailModule supports a register() static method for explicit module configuration:
import { MailModule } from '@breadstone/archipel-platform-mailing';
@Module({
imports: [
MailModule.register({
configEntries: MY_CUSTOM_CONFIG_ENTRIES, // optional
isGlobal: true, // optional, default: false
}),
],
})
export class AppModule {}Health Check
The MailHealthIndicator validates that the required mail configuration values (MAIL_HOST, MAIL_USER, MAIL_PORT) are present and non-empty. Import it from the /health subpath:
import { MailHealthIndicator } from '@breadstone/archipel-platform-mailing/health';
import { HealthModule } from '@breadstone/archipel-platform-health';
@Module({
imports: [
MailModule.register({ /* ... */ }),
HealthModule.withIndicators([MailHealthIndicator]),
],
})
export class AppModule {}| Key | Check | Dependencies |
|---|---|---|
mail | Validates mail host, user, and port config | ConfigService |
Real-World Example: Transactional Email Pipeline
@Injectable()
export class OrderEmailService {
constructor(
private readonly _mail: MailService,
private readonly _analytics: AnalyticsService,
) {}
public async sendOrderConfirmation(order: IOrder): Promise<void> {
try {
await this._mail.sendTemplate(
{
to: order.customerEmail,
subject: `Order ${order.number} Confirmed`,
},
{
templateName: 'order-confirmation',
templateParams: {
customerName: order.customerName,
orderNumber: order.number,
total: `€${order.total.toFixed(2)}`,
deliveryDate: order.estimatedDelivery.toLocaleDateString('de-DE'),
},
},
);
} catch (error) {
this._analytics.captureException(error, {
tags: { module: 'mailing', orderId: order.id },
});
}
}
}Error Handling
All delivery strategies throw a MailDeliveryError when email sending fails. This domain error class wraps the underlying provider SDK error and exposes structured metadata for error handling and observability.
import { MailDeliveryError } from '@breadstone/archipel-platform-mailing';
try {
await this._mail.send(options, content, true);
} catch (error) {
if (error instanceof MailDeliveryError) {
logger.error('Mail delivery failed', {
provider: error.provider, // 'postmark', 'resend', 'sendgrid', etc.
code: error.code, // 'MAIL_DELIVERY'
cause: error.cause, // Original provider SDK error
});
}
}| Property | Type | Description |
|---|---|---|
code | string | Always 'MAIL_DELIVERY' |
provider | string | Provider that failed (smtp, postmark, etc.) |
cause | unknown | Original error from the provider SDK |
Exports Summary
Core (@breadstone/archipel-platform-mailing)
| Export | Type | Description |
|---|---|---|
MailModule | NestJS Module | Global mail module |
MailService | Service | Send emails (plain + templated) |
SmtpConnectionVerifier | Service | SMTP connectivity verification |
MailVerificationService | Service | SmtpConnectionVerifier) |
MailTemplateEngine | Service | Template variable substitution |
DeliveryStrategyBase | Abstract class | Base for delivery strategies |
SmtpDeliveryStrategy | Strategy | SMTP transport (built-in) |
LogDeliveryStrategy | Strategy | Log-only (built-in) |
ResendDeliveryStrategy | Strategy | Resend API |
SendGridDeliveryStrategy | Strategy | SendGrid API |
MailgunDeliveryStrategy | Strategy | Mailgun API |
FileTemplateFetchStrategy | Strategy | File-based templates |
BlobTemplateFetchStrategy | Strategy | Blob-stored templates |
MailHealthIndicator | Health | Mail readiness check (/health subpath) |
MailDeliveryError | Error class | Domain error for delivery failures |
Provider subpaths
| Subpath | Exports |
|---|---|
/postmark | PostmarkDeliveryStrategy, POSTMARK_API_KEY, POSTMARK_CONFIG_ENTRIES |
/resend | ResendDeliveryStrategy, RESEND_API_KEY, RESEND_CONFIG_ENTRIES |
/sendgrid | SendGridDeliveryStrategy, SENDGRID_API_KEY, SENDGRID_CONFIG_ENTRIES |
/mailgun | MailgunDeliveryStrategy, MAILGUN_API_KEY, MAILGUN_DOMAIN, MAILGUN_CONFIG_ENTRIES |