NestJS integration

Transactional email for NestJS — inject once, fire events everywhere

Create an injectable EmailService that wraps the else.events API. Any NestJS module fires typed domain events — no email library in your providers tree.

Email libraries in NestJS apps accumulate hidden coupling

Email SDKs injected into domain modules

Injecting Nodemailer or a provider SDK into BillingModule or AuthModule couples domain logic to delivery infrastructure.

Templates hardcoded in providers

Inline templates or template file paths scattered across providers make copy changes require code reviews.

No central event log

Multiple providers.send() call sites mean you cannot quickly answer "which emails did this user receive?".

Provider migrations break every module

Moving from one email library to another requires updating every NestJS provider that injects it.

One injectable service — typed events, zero email boilerplate

Thin injectable wrapper

A single EmailService with a sendEvent() method. Inject it anywhere. The HTTP call to else.events is the entire implementation.

Typed DTOs per event

Define a PaymentFailedEvent DTO. Your billing service stays type-safe — no template names, no magic strings.

Swap providers at the else.events level

Change the delivery provider in else.events config. No changes to your NestJS module graph.

Built for the else.events + NestJS/Angular stack

else.events is built by a team that uses NestJS. The event-driven model maps directly to NestJS domain events and CQRS patterns.

// NestJS — injectable EmailService

One injectable service. Typed events throughout your app.

// email/email.service.ts
@Injectable()
export class EmailService {
  async sendEvent(type: string, user: { email: string; name: string }, data: Record<string, unknown>) {
    await fetch('https://app.else.events/api/events', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.ELSE_EVENTS_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ type, user, data }),
    });
  }
}

// billing/billing.service.ts
@Injectable()
export class BillingService {
  constructor(private readonly email: EmailService) {}

  async handlePaymentFailure(invoice: Invoice) {
    await this.email.sendEvent('invoice.payment_failed',
      { email: invoice.user.email, name: invoice.user.name },
      { plan: invoice.plan, amount: invoice.amount, currency: invoice.currency,
        update_payment_url: `https://app.example.com/billing` },
    );
  }
}

BillingService knows nothing about email templates. EmailService is the only module that talks to else.events. Provider changes affect exactly one file.

Frequently asked questions

Should I use a NestJS module or a plain service?
A plain @Injectable() service registered in a shared module works well. You can also wrap it in an EmailModule with a forRoot() pattern if you want to configure the API key via module options.
Can I use this with NestJS CQRS?
Yes. Fire events from command handlers or event handlers. The else.events API is a side-effect — a fetch call at the end of your domain handler.
How do I test the EmailService?
Mock fetch in your unit tests or create a stub EmailService with a no-op sendEvent() method. No email SDK mocking required.
Can I use NestJS EventEmitter to trigger else.events calls?
Yes. Listen to domain events with @OnEvent() and call EmailService.sendEvent() from the listener. This keeps email side-effects out of your command handlers.

Clean email architecture for NestJS apps.

One injectable service. Typed events. Templates managed outside your repository.

  • Built by a NestJS-first team
  • No email SDK in your provider graph
  • Templates editable without redeploys