Get your API key
All integrations Framework

NestJS + MailKite

NestJS doesn't ship its own mailer — the ecosystem convention is @nestjs-modules/mailer, which wraps nodemailer. MailKite plugs straight into it via nodemailer-mailkite-transport: one transport option, no MailKite-specific NestJS package needed.

What you need

  • A verified domain with SPF + DKIM published
  • Your API key (mk_live_…)
  • NestJS 10/11 with @nestjs/platform-express (or fastify)

Install

terminal
npm install @nestjs-modules/mailer nodemailer nodemailer-mailkite-transport

nodemailer-mailkite-transport pulls in the mailkite SDK automatically. Pick a template engine (Handlebars, Pug, EJS) only if you want templated emails — plain HTML/text strings work with no extra dependency.

Configure .env

.env
# .env
MAILKITE_API_KEY=mk_live_...
MAIL_FROM="MyApp <hello@yourdomain.com>"

Register the mailer module

MailerModule.forRoot()'s transport option accepts any nodemailer Transport object — and that's exactly what mailkiteTransport(...) returns. No adapter code, no custom provider:

src/mail/mail.module.ts
// src/mail/mail.module.ts
import { Module } from '@nestjs/common';
import { MailerModule } from '@nestjs-modules/mailer';
import { mailkiteTransport } from 'nodemailer-mailkite-transport';

import { MailController } from './mail.controller';

@Module({
imports: [
MailerModule.forRoot({
transport: mailkiteTransport({
apiKey: process.env.MAILKITE_API_KEY,
}),
defaults: {
from: process.env.MAIL_FROM,
},
}),
],
controllers: [MailController],
})
export class MailModule {}

Send email

Inject the standard MailerService anywhere in your app and call sendMail() — it resolves with MailKite's own message id:

src/mail/mail.controller.ts
// src/mail/mail.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { MailerService } from '@nestjs-modules/mailer';

@Controller('mail')
export class MailController {
constructor(private readonly mailerService: MailerService) {}

@Post('send')
async send(@Body() dto: { to: string; subject: string; html: string }) {
const info = await this.mailerService.sendMail({
to: dto.to,
subject: dto.subject,
html: dto.html,
});

// info.messageId is MailKite's own message id (msg_…)
return { messageId: info.messageId, status: info.status };
}
}

Test it

terminal
curl -X POST http://localhost:3000/mail/send \
-H "Content-Type: application/json" \
-d '{"to":"ada@example.com","subject":"Hello from Nest","html":"<p>Hi!</p>"}'

A successful response looks like {"messageId":"msg_…","status":"queued"}.

Full runnable starter

Want to see it wired end to end — including a static test form so you can send without curl? Clone the NestJS starter: a POST /mail/send controller, request validation, and a zero-dependency HTML form served at /.

Why no dedicated NestJS package

@nestjs-modules/mailer already provides the module system, dependency injection, and async configuration NestJS apps expect from a mailer. Wrapping that in a MailKite-specific MailKiteModule would save one import and one config key at the cost of a whole package to maintain — not worth it. The transport-level integration is the right amount of code here.

Troubleshooting

  • ERR_PACKAGE_PATH_NOT_EXPORTED on install — make sure you're on nodemailer-mailkite-transport@0.1.1 or later; earlier versions had a CommonJS packaging bug that broke require()-based NestJS builds.
  • 401 invalid tokenMAILKITE_API_KEY isn't a real mk_live_… key, or it's revoked. Check Settings → API key in the dashboard.
  • Send fails with a domain/from errorMAIL_FROM must be an address on a verified domain with SPF + DKIM published.
  • Attachments with cid (inline images) or custom headers throw — the MailKite send API has no equivalent for those nodemailer fields; the transport refuses instead of silently dropping them. Host inline images by URL instead.

See the Send API docs for the full payload reference, or Inbound webhooks to receive email in a NestJS controller.