NestboltNestbolt

@nestbolt/disposable-email

Decorator

Using the @IsNotDisposableEmail() decorator with custom messages, validation groups, and in combination with other class-validator decorators.

The @IsNotDisposableEmail() decorator is the primary way to block disposable emails in your application. It integrates with the class-validator ecosystem and works like any other validation decorator.

Basic Usage

Apply the decorator to any string property that holds an email address:

import { IsEmail } from "class-validator";
import { IsNotDisposableEmail } from "@nestbolt/disposable-email";

export class CreateUserDto {
  @IsEmail()
  @IsNotDisposableEmail()
  email: string;
}

When validation fails, the default error message is:

Disposable email addresses are not allowed.

Custom Error Messages

Override the default message by passing a message property in the validation options:

export class RegisterDto {
  @IsEmail()
  @IsNotDisposableEmail({
    message: "Please use a permanent email address to register.",
  })
  email: string;
}

You can also use a function for dynamic messages. The function receives a ValidationArguments object with context about the validation:

import { ValidationArguments } from "class-validator";

export class RegisterDto {
  @IsEmail()
  @IsNotDisposableEmail({
    message: (args: ValidationArguments) => {
      return `The email "${args.value}" uses a disposable provider. Please use a permanent email address.`;
    },
  })
  email: string;
}

The ValidationArguments object provides access to:

PropertyTypeDescription
valueanyThe value being validated
propertystringThe name of the property ("email")
targetNamestringThe name of the DTO class ("RegisterDto")
objectobjectThe entire DTO object being validated
constraintsany[]The constraint arguments (empty for this decorator)

Validation Groups

Use the groups option to apply the disposable email check only in specific validation scenarios:

export class UpdateProfileDto {
  @IsEmail()
  @IsNotDisposableEmail({ groups: ["registration"] })
  email: string;
}

In this example, the disposable email check only runs when the registration group is specified in the ValidationPipe:

app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
    groups: ["registration"],
  }),
);

Or when using manual validation:

import { validate } from "class-validator";

const errors = await validate(dto, { groups: ["registration"] });

Conditional Validation

Combine with the @ValidateIf() decorator to conditionally apply the disposable email check:

import { IsEmail, ValidateIf } from "class-validator";
import { IsNotDisposableEmail } from "@nestbolt/disposable-email";

export class CreateUserDto {
  @IsEmail()
  @ValidateIf((obj) => obj.accountType === "premium")
  @IsNotDisposableEmail({
    message: "Premium accounts require a permanent email address.",
  })
  email: string;

  accountType: string;
}

In this example, disposable emails are only rejected for premium accounts. Free accounts can use any email address.

Combining with Other Validators

The @IsNotDisposableEmail() decorator is designed to work alongside other class-validator decorators. Here are common patterns:

Standard Registration DTO

import {
  IsEmail,
  IsNotEmpty,
  IsString,
  MinLength,
  MaxLength,
} from "class-validator";
import { IsNotDisposableEmail } from "@nestbolt/disposable-email";

export class RegisterUserDto {
  @IsNotEmpty()
  @IsEmail()
  @IsNotDisposableEmail()
  email: string;

  @IsNotEmpty()
  @IsString()
  @MinLength(2)
  @MaxLength(50)
  firstName: string;

  @IsNotEmpty()
  @IsString()
  @MinLength(2)
  @MaxLength(50)
  lastName: string;

  @IsNotEmpty()
  @IsString()
  @MinLength(8)
  @MaxLength(128)
  password: string;
}

DTO with Multiple Email Fields

You can apply the decorator to multiple properties in the same DTO:

import { IsEmail, IsNotEmpty, IsOptional } from "class-validator";
import { IsNotDisposableEmail } from "@nestbolt/disposable-email";

export class CreateOrganizationDto {
  @IsNotEmpty()
  name: string;

  @IsNotEmpty()
  @IsEmail()
  @IsNotDisposableEmail()
  adminEmail: string;

  @IsOptional()
  @IsEmail()
  @IsNotDisposableEmail({
    message: "The billing contact email must be a permanent address.",
  })
  billingEmail?: string;

  @IsOptional()
  @IsEmail()
  @IsNotDisposableEmail({
    message: "The support email must be a permanent address.",
  })
  supportEmail?: string;
}

With class-transformer

The decorator works alongside class-transformer decorators like @Transform():

import { IsEmail, IsNotEmpty } from "class-validator";
import { Transform } from "class-transformer";
import { IsNotDisposableEmail } from "@nestbolt/disposable-email";

export class LoginDto {
  @IsNotEmpty()
  @Transform(({ value }) => value?.toLowerCase().trim())
  @IsEmail()
  @IsNotDisposableEmail()
  email: string;

  @IsNotEmpty()
  password: string;
}

In this example, the email is lowercased and trimmed by class-transformer before class-validator runs the decorators, so the disposable check is performed on the normalized value.

Nested DTOs

The decorator works in nested DTOs when used with @ValidateNested() and @Type():

import { IsEmail, IsNotEmpty, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
import { IsNotDisposableEmail } from "@nestbolt/disposable-email";

class ContactInfoDto {
  @IsNotEmpty()
  @IsEmail()
  @IsNotDisposableEmail()
  email: string;

  @IsNotEmpty()
  phone: string;
}

export class CreateOrderDto {
  @IsNotEmpty()
  productId: string;

  @ValidateNested()
  @Type(() => ContactInfoDto)
  contact: ContactInfoDto;
}

Array of Emails

Validate each email in an array using @ValidateNested() with each: true, or use each directly on the decorator:

import { IsEmail, IsNotEmpty, IsArray } from "class-validator";
import { IsNotDisposableEmail } from "@nestbolt/disposable-email";

export class InviteUsersDto {
  @IsArray()
  @IsEmail({}, { each: true })
  @IsNotDisposableEmail({ each: true })
  emails: string[];
}

Standalone Mode

The decorator works even without registering the DisposableEmailModule. In standalone mode, it loads the bundled domains.json file directly and performs a basic domain check:

import { validate } from "class-validator";
import { IsNotDisposableEmail } from "@nestbolt/disposable-email";

class EmailDto {
  @IsNotDisposableEmail()
  email: string;
}

const dto = new EmailDto();
dto.email = "user@mailinator.com";

const errors = await validate(dto);
console.log(errors);
// [{
//   property: 'email',
//   constraints: {
//     isNotDisposableEmail: 'Disposable email addresses are not allowed.'
//   }
// }]

Standalone mode is useful for quick prototyping or when the decorator is used outside of a NestJS context. However, it does not support whitelist, subdomain matching, or custom fetchers. For the full feature set, register the DisposableEmailModule and configure useContainer() as described in the Quick Start guide.

Decorator Internals

Under the hood, the @IsNotDisposableEmail() decorator registers a custom class-validator constraint called IsNotDisposableEmailConstraint. This constraint is an @Injectable() NestJS provider that optionally receives the DisposableEmailService via dependency injection:

  • With DI (module registered + useContainer configured): The constraint delegates to DisposableEmailService.isNotDisposable(), which supports all configuration options including whitelist, subdomain matching, and updated domain lists.
  • Without DI (standalone mode): The constraint loads the bundled domains.json file lazily on first validation and checks directly against that set. The bundled list is loaded once and cached in memory for subsequent validations.

The constraint accepts ValidationOptions from class-validator, which means all standard options work: message, groups, each, always, and context.