Skip to main content

Overview

The SMTP Mail provider extends your application ports with email sending capabilities using nodemailer. It supports any SMTP server, making it ideal for using existing email infrastructure or legacy systems.

Installation

npm install @contract-kit/provider-mail-smtp nodemailer

Configuration

The Mail provider reads configuration from environment variables:
VariableRequiredDescriptionExample
MAIL_HOSTYesSMTP server hostnamesmtp.gmail.com
MAIL_PORTYesSMTP server port587 (TLS) or 465 (SSL)
MAIL_USERYesSMTP username[email protected]
MAIL_PASSYesSMTP passwordyour-password
MAIL_FROMYesDefault sender email address[email protected]
Port 465 automatically uses SSL, while other ports use TLS.

Example .env

MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
[email protected]
MAIL_PASS=your-app-password
[email protected]

Setup

Basic Setup

import { createServer } from "@contract-kit/server";
import { mailSmtpProvider } from "@contract-kit/provider-mail-smtp";

const app = createServer({
  ports: basePorts,
  providers: [mailSmtpProvider],
  createContext: ({ ports }) => ({
    ports,
    // ... other context
  }),
  routes: [
    // ... your routes
  ],
});

Type Your Ports

To get proper type inference for the mailer port, extend your ports type:
import type { MailerPort } from "@contract-kit/mail";
import type { Transporter } from "nodemailer";

// Your base ports
const basePorts = definePorts({
  db: dbAdapter,
});

// After using mailSmtpProvider, your ports will have this shape:
type AppPorts = typeof basePorts & {
  mailer: MailerPort<Transporter>;
};

API Reference

The provider extends your ports with a mailer property:

sendText(to, subject, text)

Send a plain text email using the default sender address.
await ctx.ports.mailer.sendText(
  "[email protected]",
  "Welcome",
  "Thanks for joining!"
);
Parameters:
  • to: Recipient email address
  • subject: Email subject
  • text: Plain text content
Returns: Promise<void>

sendHtml(to, subject, html)

Send an HTML email using the default sender address.
await ctx.ports.mailer.sendHtml(
  "[email protected]",
  "Welcome",
  "<h1>Thanks for joining!</h1>"
);
Parameters:
  • to: Recipient email address
  • subject: Email subject
  • html: HTML content
Returns: Promise<void>

send(options)

Send an email with full control over options, including the ability to override the sender.
await ctx.ports.mailer.send({
  from: "[email protected]", // Optional sender override
  to: "[email protected]",
  subject: "Hello",
  html: "<p>Custom email</p>",
});
Parameters:
  • options.from: Sender email address (optional, defaults to MAIL_FROM)
  • options.to: Recipient email address
  • options.subject: Email subject
  • options.text: Plain text content (if not using html)
  • options.html: HTML content (if not using text)
Returns: Promise<void>

client

Access the underlying nodemailer transporter for advanced operations.
// Use nodemailer features directly
await ctx.ports.mailer.client.sendMail({
  from: "[email protected]",
  to: ["[email protected]", "[email protected]"],
  subject: "Bulk Email",
  text: "Message to multiple recipients",
  attachments: [
    {
      filename: "document.pdf",
      path: "/path/to/document.pdf",
    },
  ],
});
Type: Transporter (from nodemailer)

Usage Examples

Send Welcome Email

async function sendWelcomeEmail(ctx: AppCtx, user: User) {
  await ctx.ports.mailer.sendText(
    user.email,
    "Welcome!",
    "Thank you for signing up!"
  );
}

Send Password Reset Email

async function sendPasswordReset(ctx: AppCtx, user: User, resetLink: string) {
  await ctx.ports.mailer.sendHtml(
    user.email,
    "Password Reset",
    `<h1>Reset Your Password</h1>
     <p>Click <a href="${resetLink}">here</a> to reset your password.</p>`
  );
}

Send Custom Email

async function sendCustomEmail(ctx: AppCtx) {
  await ctx.ports.mailer.send({
    from: "[email protected]", // Override default sender
    to: "[email protected]",
    subject: "Custom Email",
    html: "<h1>Hello!</h1>",
  });
}

Advanced: Email with Attachments

await ctx.ports.mailer.client.sendMail({
  from: "[email protected]",
  to: ["[email protected]", "[email protected]"],
  subject: "Report",
  text: "Please find the attached report.",
  attachments: [
    {
      filename: "report.pdf",
      path: "/path/to/report.pdf",
    },
    {
      filename: "data.csv",
      content: csvBuffer,
    },
  ],
});

Gmail

MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
[email protected]
MAIL_PASS=your-app-password  # Use App Password, not regular password
[email protected]
For Gmail, you need to use an App Password, not your regular password.

SendGrid

MAIL_HOST=smtp.sendgrid.net
MAIL_PORT=587
MAIL_USER=apikey
MAIL_PASS=your-sendgrid-api-key
[email protected]

AWS SES

MAIL_HOST=email-smtp.us-east-1.amazonaws.com
MAIL_PORT=587
MAIL_USER=your-ses-smtp-username
MAIL_PASS=your-ses-smtp-password
[email protected]

Lifecycle

The SMTP Mail provider:
  1. During register:
    • Creates nodemailer transporter
    • Verifies connection to SMTP server
    • Adds the mailer port
  2. During onAppStop: Closes the transporter connection

Error Handling

The provider will throw errors in these cases:
  • Missing required environment variables
  • Failed connection to SMTP server
  • Invalid email addresses
Make sure to handle these during application startup.

Comparing with Resend Provider

While the SMTP provider works with any SMTP server, the Resend provider offers:
  • Simpler setup: Just an API key, no SMTP server configuration
  • Better deliverability: Resend handles infrastructure and reputation
  • Modern features: Built-in analytics, webhooks, and templates
  • No port/firewall issues: Uses HTTPS instead of SMTP ports
Use the SMTP provider when you:
  • Need to use your existing SMTP server
  • Have strict data residency requirements
  • Want to integrate with legacy email systems
  • Prefer more control over email infrastructure

Best Practices

For services like Gmail, use app-specific passwords instead of your main account password.
Verify your SMTP connection during application startup to catch configuration issues early.
Implement retry logic for transient SMTP connection failures.
Keep track of bounce rates and delivery failures to maintain sender reputation.
nodemailer automatically handles connection pooling for better performance.

Next Steps