Skip to content

apl9000/courier

Repository files navigation

✉️ Courier

Courier is a Deno email utility that integrates SMTP (iCloud and Microsoft Outlook), Nodemailer, and Handlebars templating into a clean, modular workflow. Use it to send branded transactional emails, notifications, and automated messages with minimal setup.

Written in Deno and published to JSR (JavaScript Registry)

Features

  • Multi-Provider SMTP Support - Built-in support for iCloud and Microsoft Outlook SMTP
  • Nodemailer Powered - Reliable email delivery with the popular Nodemailer library
  • Handlebars Templates - Create beautiful, branded emails with Handlebars templating
  • TypeScript Support - Full TypeScript definitions included
  • Easy to Use - Simple, intuitive API for sending emails
  • Secure - Uses secure SMTP connections with STARTTLS
  • Type-Safe - Complete type safety with TypeScript interfaces
  • Dedicated Template Functions - Type-safe functions for common email templates

Installation

Using Deno

# Add to your Deno project
deno add @apl/courier

# Or import directly from JSR
import { Courier } from "jsr:@apl/courier";

Using npm/Node.js

# Using npm (installs from JSR)
npx jsr add @apl/courier

# Or with yarn
yarn dlx jsr add @apl/courier

# Or with pnpm
pnpm dlx jsr add @apl/courier

Then import in your Node.js/TypeScript project:

import { Courier, SMTPProviders } from "@apl/courier";

Quick Start

Basic Usage with iCloud

import { Courier, SMTPProviders } from "@apl/courier";

// Initialize with iCloud SMTP
const courier = await Courier.initialize({
  smtp: {
    ...SMTPProviders.iCloud,
    user: "your-email@icloud.com",
    pass: "your-app-specific-password", // Get this from iCloud settings
  },
  defaultFrom: "your-email@icloud.com",
});

// Send a simple email
await courier.send({
  to: "recipient@example.com",
  subject: "Hello from Courier!",
  text: "This is a test email",
  html: "<p>This is a <strong>test</strong> email</p>",
});

courier.close();

Using Microsoft Outlook SMTP

import { Courier, SMTPProviders } from "@apl/courier";

const courier = await Courier.initialize({
  smtp: {
    ...SMTPProviders.Microsoft,
    user: "your-email@outlook.com",
    pass: "your-password",
  },
  defaultFrom: "your-email@outlook.com",
});

Using Default Email Templates

Courier includes six built-in email templates. To use them, you must specify the templatesDir configuration pointing to the templates directory:

// For Deno projects (development or direct usage)
const courier = await Courier.initialize({
  smtp: {
    ...SMTPProviders.iCloud,
    user: "your-email@icloud.com",
    pass: "your-app-specific-password",
  },
  defaultFrom: "your-email@icloud.com",
  templatesDir: "./src/emails", // Path to Courier's templates directory
});

// For Node.js/Next.js projects using Courier from node_modules
const courier = await Courier.initialize({
  smtp: {
    ...SMTPProviders.iCloud,
    user: "your-email@icloud.com",
    pass: "your-app-specific-password",
  },
  defaultFrom: "your-email@icloud.com",
  templatesDir: "./node_modules/@apl/courier/src/emails", // Path in node_modules
});

// Send a welcome email
await courier.sendWelcomeEmail(
  {
    name: "Alice",
    actionUrl: "https://example.com/getting-started",
    year: new Date().getFullYear(),
    companyName: "Acme Inc.",
  },
  {
    to: "alice@example.com",
    subject: "Welcome to Acme Inc.!",
  },
);

// Send a password reset email
await courier.sendPasswordReset(
  {
    name: "Bob",
    resetUrl: "https://example.com/reset?token=abc123",
    resetCode: "ABC123",
    expiryHours: 24,
    companyName: "Acme Inc.",
  },
  {
    to: "bob@example.com",
    subject: "Password Reset Request",
  },
);

// Send a notification
await courier.sendNotification(
  {
    type: "success",
    title: "Deployment Complete",
    message: "Your application has been deployed successfully.",
    details: "Build #1234 completed in 3m 45s",
    timestamp: new Date().toISOString(),
  },
  {
    to: "developer@example.com",
    subject: "Deployment Notification",
  },
);

Available Templates:

  • sendWelcomeEmail() - Welcome new users
  • sendEmailVerification() - Verify email addresses
  • sendPasswordReset() - Password reset requests
  • sendNotification() - System notifications
  • sendNewsletter() - Newsletter campaigns
  • sendUnsubscribeConfirmation() - Unsubscribe confirmations
## Configuration

### SMTP Setup

#### iCloud SMTP

1. Go to [appleid.apple.com](https://appleid.apple.com)
2. Sign in with your Apple ID
3. Go to "Sign-In and Security" section
4. Under "App-Specific Passwords", generate a new password
5. Use this password in your Courier configuration

#### Microsoft Outlook SMTP

Use your regular Outlook/Hotmail account credentials.

### Configuration Options

```typescript
interface CourierConfig {
  smtp: {
    user: string; // Your email address
    pass: string; // App-specific password or account password
    host?: string; // SMTP host (defaults to smtp.mail.me.com for iCloud)
    port?: number; // SMTP port (defaults to 587)
    secure?: boolean; // Enable SSL (defaults to false for STARTTLS)
  };
  defaultFrom?: string | { email: string; name?: string };
  templatesDir?: string; // Path to templates directory (required for template methods)
  theme?: ThemeConfig; // Optional runtime theme customization
  debug?: boolean; // Enable debug logging
}

Important: The templatesDir option is required to use template methods like sendWelcomeEmail(), sendPasswordReset(), etc. Point it to:

  • "./src/emails" when using Courier directly in Deno projects
  • "./node_modules/@apl/courier/src/emails" when using Courier in Node.js/Next.js projects
### Custom Theming

Customize email styles at runtime by providing a theme configuration:

```typescript
import { Courier, SMTPProviders } from "@apl/courier";

const courier = await Courier.initialize({
  smtp: {
    ...SMTPProviders.iCloud,
    user: "your-email@icloud.com",
    pass: "your-app-specific-password",
  },
  // Custom theme configuration
  theme: {
    colors: {
      text: "#1a1a1a",
      textAlt: "#6b7280",
      background: "#ffffff",
      backgroundAlt: "#f3f4f6",
      border: "#000000",
      accent: "#3b82f6", // Blue accent
      accentHover: "#2563eb",
    },
    typography: {
      fontFamily: "system-ui, sans-serif", // Use system fonts
      fontSize: {
        base: "16px",
        sm: "14px",
        lg: "18px",
        xl: "20px",
        "2xl": "28px", // Larger headings
      },
      fontWeight: {
        normal: "400",
        medium: "500",
        bold: "700",
      },
      lineHeight: "1.5",
    },
    spacing: {
      line: "1.5rem",
      containerPadding: "2rem",
    },
    borders: {
      width: "1px",
      widthThick: "2px",
      widthDouble: "4px",
    },
    container: {
      maxWidth: "680px",
    },
  },
});

// Now all emails use your custom theme!
await courier.sendWelcomeEmail(
  {
    name: "Alex",
    actionUrl: "https://app.example.com",
    companyName: "My Company",
    year: 2025,
  },
  {
    to: "alex@example.com",
    subject: "Welcome!",
  },
);

Note: If no theme is provided, Courier uses the default monospace/brutalist design system.

Email Functions

Core Functions

send(message: EmailMessage): Promise<SendResult>
Send a basic email message.

sendWithTemplate(templateName: string, data: TemplateData, message: EmailMessage): Promise<SendResult>
Send an email using a registered template.

registerTemplate(name: string, template: string): void
Register a Handlebars template from a string.

loadTemplate(name: string, filepath: string): Promise<void>
Load and register a template from a file.

verify(): Promise<boolean>
Verify the SMTP connection is valid.

close(): void
Close the SMTP connection.

Template-Specific Functions

sendWelcomeEmail(data: WelcomeEmailData, message): Promise<SendResult>
Send a welcome email with name, action URL, year, and company name.

sendEmailVerification(data: EmailVerificationData, message): Promise<SendResult>
Send an email verification with verification URL/code and expiry time.

sendPasswordReset(data: PasswordResetData, message): Promise<SendResult>
Send a password reset email with reset URL/code and expiry time.

sendNotification(data: NotificationData, message): Promise<SendResult>
Send a notification email with type (info/warning/error/success), title, message, and details.

sendNewsletter(data: NewsletterData, message): Promise<SendResult>
Send a newsletter with title, sections, and unsubscribe link.

sendUnsubscribeConfirmation(data: UnsubscribeData, message): Promise<SendResult>
Send an unsubscribe confirmation with resubscribe link.

Sending Emails

Simple Email

await courier.send({
  to: "recipient@example.com",
  subject: "Hello!",
  text: "Plain text content",
  html: "<p>HTML content</p>",
});

Multiple Recipients

await courier.send({
  to: ["user1@example.com", "user2@example.com"],
  cc: "manager@example.com",
  bcc: "archive@example.com",
  subject: "Team Update",
  html: "<p>Update for the team</p>",
});

Named Email Addresses

await courier.send({
  from: { email: "noreply@example.com", name: "Acme Inc." },
  to: { email: "user@example.com", name: "John Doe" },
  replyTo: { email: "support@example.com", name: "Support Team" },
  subject: "Welcome!",
  html: "<p>Welcome to our service!</p>",
});

Templates

Courier includes six pre-built templates:

  • welcome - Professional welcome email with call-to-action button
  • email-verification - Email verification with code and link
  • password-reset - Secure password reset email with token and code
  • notification - System notification with severity levels (info, warning, error, success)
  • newsletter - Newsletter template with sections and unsubscribe link
  • unsubscribe - Unsubscribe confirmation with resubscribe option

Custom Templates

You can create your own templates or load custom ones:

// Register inline template
courier.registerTemplate("custom", "<h1>Hello {{name}}</h1><p>{{message}}</p>");

// Load from file
await courier.loadTemplate("custom", "./my-templates/custom.hbs");

// Use the template
await courier.sendWithTemplate(
  "custom",
  { name: "Alice", message: "Welcome!" },
  {
    to: "alice@example.com",
    subject: "Custom Email",
  },
);

Custom Themes and Styling

Courier uses runtime CSS generation with a monospace/brutalist theme by default. You can customize the theme to match your brand using the theme option:

1. Use a Preset Theme

Choose from built-in preset themes:

import { ColorfulTheme, Courier, DarkTheme, ProfessionalTheme } from "@apl/courier";

const courier = await Courier.initialize({
  smtp: {
    /* ... */
  },
  theme: DarkTheme, // or ProfessionalTheme, ColorfulTheme
});

2. Create a Custom Theme

Override specific theme properties:

const courier = await Courier.initialize({
  smtp: {
    /* ... */
  },
  theme: {
    colors: {
      text: "#1a1a1a",
      background: "#f5f5f5",
      accent: "#0891b2",
    },
    typography: {
      fontFamily: "'Inter', sans-serif",
    },
  },
});

3. Extend a Preset Theme

Combine preset themes with custom overrides:

import { createTheme, ProfessionalTheme } from "@apl/courier";

const myTheme = createTheme(ProfessionalTheme, {
  colors: { accent: "#ff6b6b" },
  container: { maxWidth: "800px" },
});

const courier = await Courier.initialize({
  smtp: {
    /* ... */
  },
  theme: myTheme,
});

4. Create Custom Email Templates

Create your own .hbs templates using email CSS classes:

{{!< layouts-main }} {{> header title="Custom Email" logo="https://example.com/logo.png"}}

<p class="text-brand-primary font-sans">Your custom content here</p>

{{> footer companyName="Your Company" companyAddress="Your Address"}}

4. Use Custom Templates

Load and use your custom templates:

// Load custom template directory
const courier = await Courier.initialize({
  smtp: smtpConfig,
  templatesDir: "./my-custom-templates", // Your custom templates
});

// Or use built-in templates with custom templates merged in
const courier = await Courier.initialize({
  smtp: smtpConfig,
  templatesDir: "./node_modules/@apl/courier/src/emails", // Use built-in templates first
});

// Then load additional custom templates
await courier.loadTemplate("my-template", "./path/to/template.hbs");
await courier.sendWithTemplate("my-template", data, message);

Note: When specifying templatesDir, Courier loads all templates from that directory. Custom templates with the same name as built-in templates will override the built-in ones.

Note: The default monospace/brutalist theme provides excellent email client compatibility and accessibility. Custom themes should be tested across multiple email clients (Gmail, Outlook, Apple Mail) to ensure compatibility.

API Reference

Types

import type {
  Courier,
  CourierConfig,
  EmailAddress,
  EmailMessage,
  EmailVerificationData,
  NewsletterData,
  NotificationData,
  PasswordResetData,
  SendResult,
  SMTPConfig,
  TemplateData,
  UnsubscribeData,
  // Template-specific types
  WelcomeEmailData,
} from "@apl/courier";

SMTP Providers

import { SMTPProviders } from "@apl/courier";

// Available providers:
SMTPProviders.iCloud = {
  host: "smtp.mail.me.com",
  port: 587,
  secure: false,
};

SMTPProviders.Microsoft = {
  host: "smtp-mail.outlook.com",
  port: 587,
  secure: false,
};

Testing Locally

  • Environment Variables: Set these in your shell or a .env file (kept out of git by .gitignore).

    • SMTP_HOST: SMTP server host (default smtp.mail.me.com)
    • SMTP_USER: Your SMTP username (email)
    • SMTP_PASS: Your SMTP password or app-specific password
    • SMTP_FROM: Default from address (falls back to SMTP_USER)
  • Quick setup (macOS zsh):

    export SMTP_HOST="smtp.mail.me.com"
    export SMTP_USER="your-email@icloud.com"
    export SMTP_PASS="your-app-specific-password"
    export SMTP_FROM="$SMTP_USER"
  • Run tests: Use tasks which include the required permissions (notably --allow-env).

    deno task lint
    deno task fmt
    deno task test         # all tests
    deno task test:unit    # unit tests only
    deno task test:integration  # integration tests (needs --allow-net)
    deno task coverage     # generate lcov coverage
  • Run individual integration tests: Test specific email templates one at a time:

    # Test SMTP connection and basic email functionality
    deno task test:smtp
    
    # Test individual email templates
    deno task test:welcome
    deno task test:verification
    deno task test:password-reset
    deno task test:notification
    deno task test:newsletter
    deno task test:unsubscribe
  • Why --allow-env matters: Nodemailer may read environment variables during import; tests must be run with --allow-env to avoid permission errors.

  • Multi-recipient testing: To test with multiple email addresses, set the SMTP_TEST_TO environment variable with comma-separated email addresses:

    SMTP_TEST_TO="test1@example.com,test2@example.com,test3@example.com" deno task test:smtp

    If not set, tests will use the SMTP_USER as the default recipient.

  • Template snapshots (optional): To preview rendered HTML for built-in templates without sending email, run:

    deno task snapshots

    This writes HTML files under tests/snapshots/.

Security

  • Always use app-specific passwords for iCloud (never your main password)
  • Store credentials in environment variables, not in code
  • Use HTTPS/TLS for secure email transmission
  • Validate and sanitize template data to prevent injection attacks

Troubleshooting

Templates Not Working in Node.js/Next.js

If you're using Courier in a Node.js or Next.js project and template methods like sendWelcomeEmail() fail, make sure you've specified the templatesDir configuration:

const courier = await Courier.initialize({
  smtp: {
    ...SMTPProviders.iCloud,
    user: "your-email@icloud.com",
    pass: "your-app-specific-password",
  },
  defaultFrom: "your-email@icloud.com",
  // REQUIRED for template methods in Node.js/Next.js
  templatesDir: "./node_modules/@apl/courier/src/emails",
});

Module Not Found Errors

If you see errors like Module not found: Can't resolve './emails' during build:

  1. Ensure you're using the latest version of Courier (0.0.6+)
  2. Verify you have templatesDir configured in your initialization
  3. Clear your build cache and reinstall dependencies:
    rm -rf node_modules package-lock.json .next
    npm install

SMTP Connection Failures

  • iCloud users: Make sure you're using an app-specific password, not your main Apple ID password
  • Outlook users: Verify your account allows SMTP access (some accounts require enabling this in settings)
  • Test your connection with await courier.verify() before sending emails

Contributing

Contributions are welcome! Please file an issue on the GitHub repository to discuss proposed changes.

Contributing

Contributions are welcome! We use Conventional Commits for automated versioning and releases.

Quick Start for Contributors

  1. Fork and clone the repository
  2. Create a feature branch: git checkout -b feat/your-feature
  3. Make changes and commit using conventional format:
    • feat: for new features (minor version bump)
    • fix: for bug fixes (patch version bump)
    • feat!: or BREAKING CHANGE: for breaking changes (major version bump)
  4. Run tests: deno task test
  5. Push and open a Pull Request

For detailed guidelines, see CONTRIBUTING.md.

Issues

If you encounter any problems, please file an issue on the GitHub repository.

Learn More

License

Courier is released under the MIT License.

About

Meet your Courier — a fast, flexible email toolkit for Node.js. Build templates once, send anywhere. Powered by iCloud and Nodemailer.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors