NestboltNestbolt

@nestbolt/disposable-email

Configuration

All configuration options for @nestbolt/disposable-email explained in detail with examples.

The DisposableEmailModule supports both static and async configuration through the standard NestJS forRoot() and forRootAsync() patterns. This page documents every available option.

Configuration Options Reference

OptionTypeDefaultDescription
sourcesstring[]jsDelivr CDN URLURLs returning JSON arrays of disposable domains
storagePathstring'' (empty)Local file path to persist fetched domains (must end in .json)
whiteliststring[][]Domains to exclude from the disposable list
includeSubdomainsbooleanfalseWhen true, subdomains of disposable domains are also rejected
fetcherFetcherDefaultFetcherCustom fetcher implementation for retrieving domain lists

Static Configuration with forRoot()

Pass an options object directly to forRoot(). This is the simplest approach when all configuration values are known at compile time:

import { Module } from "@nestjs/common";
import { DisposableEmailModule } from "@nestbolt/disposable-email";

@Module({
  imports: [
    DisposableEmailModule.forRoot({
      whitelist: ["example.com", "mycompany.com"],
      includeSubdomains: true,
      storagePath: "./storage/disposable_domains.json",
      sources: [
        "https://cdn.jsdelivr.net/gh/disposable/disposable-email-domains@master/domains.json",
      ],
    }),
  ],
})
export class AppModule {}

Calling forRoot() with no arguments applies all defaults:

@Module({
  imports: [DisposableEmailModule.forRoot()],
})
export class AppModule {}

Async Configuration with forRootAsync()

Use forRootAsync() when configuration values need to be resolved at runtime -- for example, from environment variables via ConfigService or from a database:

import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { DisposableEmailModule } from "@nestbolt/disposable-email";

@Module({
  imports: [
    ConfigModule.forRoot(),
    DisposableEmailModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        whitelist: config
          .get<string>("DISPOSABLE_WHITELIST", "")
          .split(",")
          .filter(Boolean),
        includeSubdomains: config.get<boolean>(
          "DISPOSABLE_INCLUDE_SUBDOMAINS",
          false,
        ),
        storagePath: config.get<string>("DISPOSABLE_STORAGE_PATH", ""),
        sources: config
          .get<string>(
            "DISPOSABLE_SOURCES",
            "https://cdn.jsdelivr.net/gh/disposable/disposable-email-domains@master/domains.json",
          )
          .split(",")
          .filter(Boolean),
      }),
    }),
  ],
})
export class AppModule {}

The forRootAsync() method accepts the following properties:

PropertyTypeDescription
importsany[]Modules to import (makes their providers available to inject)
injectany[]Providers to inject into the useFactory function
useFactory(...args: any[]) => DisposableEmailOptions | Promise<DisposableEmailOptions>Factory function that returns the configuration object

The factory function can be asynchronous, so you can fetch configuration from a remote source at startup:

DisposableEmailModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: async (config: ConfigService) => {
    const whitelist = await fetchWhitelistFromDatabase();
    return {
      whitelist,
      includeSubdomains: config.get<boolean>("INCLUDE_SUBDOMAINS", false),
    };
  },
});

Options in Detail

sources

Type: string[] Default: ["https://cdn.jsdelivr.net/gh/disposable/disposable-email-domains@master/domains.json"]

An array of URLs that return JSON arrays of disposable domain strings. These sources are used by the updateDomains() method to fetch fresh domain lists. Each URL must return a response like:

["mailinator.com", "guerrillamail.com", "tempmail.com"]

When multiple sources are specified, all responses are merged and deduplicated:

DisposableEmailModule.forRoot({
  sources: [
    "https://cdn.jsdelivr.net/gh/disposable/disposable-email-domains@master/domains.json",
    "https://your-internal-api.com/blocked-domains.json",
  ],
});

The default source points to the disposable/disposable-email-domains repository via jsDelivr CDN.

Note that sources are only used when updateDomains() is called. At module startup, the service loads domains from local storage (if storagePath is set and the file exists) or from the bundled domains.json file that ships with the package. See Domain Updates for details on scheduling updates.

storagePath

Type: string Default: '' (empty string -- local persistence disabled)

A file path where fetched domains are saved after calling updateDomains(). The path must end with .json or an error is thrown at module initialization. It can be either absolute or relative (relative paths are resolved from the current working directory):

DisposableEmailModule.forRoot({
  storagePath: "./storage/disposable_domains.json",
});

When storagePath is set:

  1. On startup (bootstrap): The service checks if the file exists. If it does, domains are loaded from this file instead of the bundled list. This means previously fetched domains survive application restarts.
  2. On update (updateDomains): After fetching fresh domains from the configured sources, the service writes the merged list to this file.

If the file does not exist on startup, the bundled list is used as a fallback.

Make sure the directory containing the storage file exists and is writable by the application process. The module does not create parent directories.

whitelist

Type: string[] Default: []

An array of domains that should be excluded from the disposable list, even if they appear in the source data. This is useful if a domain is incorrectly classified as disposable or if your organization uses a domain that happens to be on the list:

DisposableEmailModule.forRoot({
  whitelist: ["example.com", "legitimate-service.com", "company-domain.org"],
});

Whitelisted domains are removed from the disposable set at load time (during bootstrap() and updateDomains()), so there is no per-validation overhead. Domain matching is case-insensitive -- domains are lowercased before comparison.

includeSubdomains

Type: boolean Default: false

When set to true, the validator also checks parent domains of the email's domain against the disposable list. This catches attempts to bypass the filter by using subdomains of known disposable providers:

DisposableEmailModule.forRoot({
  includeSubdomains: true,
});

With subdomain matching enabled, the behavior is:

EmailincludeSubdomains: falseincludeSubdomains: true
user@mailinator.comBlocked (exact match)Blocked (exact match)
user@sub.mailinator.comAllowedBlocked (subdomain match)
user@deep.sub.mailinator.comAllowedBlocked (subdomain match)
user@gmail.comAllowedAllowed

The subdomain check works by walking up the domain labels. For sub.deep.mailinator.com, the service checks deep.mailinator.com, then mailinator.com. It stops as soon as a match is found.

fetcher

Type: Fetcher Default: DefaultFetcher (uses native fetch() with a 30-second timeout)

A custom implementation of the Fetcher interface for retrieving domain lists from remote sources. The interface is defined as:

export interface Fetcher {
  fetch(url: string): Promise<string[]>;
}

The fetch method receives a source URL and must return a promise that resolves to an array of domain strings. The built-in DefaultFetcher uses the native fetch() API with a 30-second timeout and AbortController:

import { Fetcher } from "@nestbolt/disposable-email";

export class DefaultFetcher implements Fetcher {
  async fetch(url: string): Promise<string[]> {
    const response = await globalThis.fetch(url, {
      signal: AbortSignal.timeout(30_000),
    });

    if (!response.ok) {
      throw new Error(`Failed to fetch: ${response.statusText}`);
    }

    return response.json();
  }
}

You might implement a custom fetcher when your application:

  • Runs behind a corporate proxy
  • Needs custom HTTP headers (e.g., authentication tokens)
  • Uses a different HTTP client like axios
  • Requires request logging or metrics

Here is an example using axios with proxy support:

import { Fetcher } from "@nestbolt/disposable-email";
import axios from "axios";

export class AxiosFetcher implements Fetcher {
  async fetch(url: string): Promise<string[]> {
    const response = await axios.get<string[]>(url, {
      timeout: 30_000,
      proxy: {
        host: "proxy.corporate.com",
        port: 8080,
      },
    });

    return response.data;
  }
}

Pass the custom fetcher in the module configuration:

DisposableEmailModule.forRoot({
  fetcher: new AxiosFetcher(),
});

Global Module

The DisposableEmailModule is registered as a global module by default. This means you only need to import it once in your root AppModule, and the DisposableEmailService and @IsNotDisposableEmail() decorator are available throughout your entire application -- in any module, controller, or service -- without additional imports.

// app.module.ts -- register once here
@Module({
  imports: [
    DisposableEmailModule.forRoot({
      includeSubdomains: true,
    }),
    UsersModule,
    OrdersModule,
  ],
})
export class AppModule {}

// users/users.service.ts -- available here without importing the module again
@Injectable()
export class UsersService {
  constructor(private readonly disposableEmail: DisposableEmailService) {}
}

// orders/dto/create-order.dto.ts -- decorator works here too
export class CreateOrderDto {
  @IsEmail()
  @IsNotDisposableEmail()
  contactEmail: string;
}

Full Configuration Example

Here is a complete example combining all options with async configuration:

import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { DisposableEmailModule } from "@nestbolt/disposable-email";
import { AxiosFetcher } from "./common/fetchers/axios.fetcher";

@Module({
  imports: [
    ConfigModule.forRoot(),
    DisposableEmailModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        sources: [
          "https://cdn.jsdelivr.net/gh/disposable/disposable-email-domains@master/domains.json",
          config.get<string>("CUSTOM_BLOCKLIST_URL", ""),
        ].filter(Boolean),
        storagePath: config.get<string>(
          "DISPOSABLE_STORAGE_PATH",
          "./storage/disposable_domains.json",
        ),
        whitelist: config
          .get<string>("DISPOSABLE_WHITELIST", "")
          .split(",")
          .filter(Boolean),
        includeSubdomains: config.get<boolean>(
          "DISPOSABLE_INCLUDE_SUBDOMAINS",
          true,
        ),
        fetcher: new AxiosFetcher(),
      }),
    }),
  ],
})
export class AppModule {}

With a corresponding .env file:

DISPOSABLE_STORAGE_PATH=./storage/disposable_domains.json
DISPOSABLE_WHITELIST=partner-domain.com,trusted-service.io
DISPOSABLE_INCLUDE_SUBDOMAINS=true
CUSTOM_BLOCKLIST_URL=https://internal-api.example.com/blocked-domains.json