@nestbolt/disposable-email
Service API
Using DisposableEmailService for programmatic disposable email checks, domain management, and updates.
While the @IsNotDisposableEmail() decorator handles most use cases, you can also inject DisposableEmailService directly for programmatic email validation, domain inspection, and manual updates.
Injecting the Service
The DisposableEmailService is available for injection in any provider, controller, or service once the DisposableEmailModule is registered. Since the module is global, no additional imports are needed:
import { Injectable } from "@nestjs/common";
import { DisposableEmailService } from "@nestbolt/disposable-email";
@Injectable()
export class UsersService {
constructor(private readonly disposableEmail: DisposableEmailService) {}
}Methods
isDisposable(email: string): boolean
Returns true if the email address belongs to a known disposable email provider. The method extracts the domain from the email, lowercases it, and checks it against the loaded domain set.
import { Injectable, BadRequestException } from "@nestjs/common";
import { DisposableEmailService } from "@nestbolt/disposable-email";
@Injectable()
export class UsersService {
constructor(private readonly disposableEmail: DisposableEmailService) {}
async createUser(email: string, name: string) {
if (this.disposableEmail.isDisposable(email)) {
throw new BadRequestException(
"Disposable email addresses are not allowed.",
);
}
// Proceed with user creation...
}
}When includeSubdomains is enabled in the module configuration, subdomain checking is performed automatically. For example, if mailinator.com is in the disposable list, then sub.mailinator.com and deep.sub.mailinator.com are also considered disposable.
Edge cases:
- If the email string does not contain exactly one
@symbol, the method returnsfalse(not disposable). - If the domain portion is empty after
@, the method returnsfalse. - Domain matching is case-insensitive.
User@MAILINATOR.COMis treated the same asuser@mailinator.com.
isNotDisposable(email: string): boolean
The inverse of isDisposable(). Returns true if the email address does not belong to a known disposable provider:
@Injectable()
export class EmailVerificationService {
constructor(private readonly disposableEmail: DisposableEmailService) {}
canSendVerification(email: string): boolean {
// Only send verification emails to permanent addresses
return this.disposableEmail.isNotDisposable(email);
}
}This is the same method that the @IsNotDisposableEmail() decorator calls internally.
getDomains(): string[]
Returns the full list of currently loaded disposable domains as an array of strings:
import { Controller, Get, UseGuards } from "@nestjs/common";
import { DisposableEmailService } from "@nestbolt/disposable-email";
@Controller("admin/disposable-emails")
export class DisposableEmailAdminController {
constructor(private readonly disposableEmail: DisposableEmailService) {}
@Get("domains")
@UseGuards(AdminGuard)
listDomains() {
const domains = this.disposableEmail.getDomains();
return {
count: domains.length,
domains,
};
}
}The returned array is a copy of the internal domain set. Modifying the array does not affect the service's internal state.
updateDomains(): Promise<void>
Fetches fresh domain lists from all configured sources, merges and deduplicates them, applies the whitelist, and loads the result into memory. If a storagePath is configured, the fetched domains are also persisted to disk.
import { Controller, Post, UseGuards } from "@nestjs/common";
import { DisposableEmailService } from "@nestbolt/disposable-email";
@Controller("admin/disposable-emails")
export class DisposableEmailAdminController {
constructor(private readonly disposableEmail: DisposableEmailService) {}
@Post("update")
@UseGuards(AdminGuard)
async triggerUpdate() {
await this.disposableEmail.updateDomains();
const domains = this.disposableEmail.getDomains();
return {
message: "Disposable domains list updated successfully.",
count: domains.length,
};
}
}How the update process works:
- For each URL in the
sourcesarray, the configuredfetcheris called to retrieve a JSON array of domain strings. - If a source fails (network error, timeout, invalid response), the error is logged and the remaining sources are still processed.
- All successfully fetched domains are merged into a single array and deduplicated.
- The whitelist is applied, removing any whitelisted domains from the set.
- The internal domain set is replaced with the new data.
- If
storagePathis configured, the merged domain list is written to disk as a JSON file.
The update is performed in-place. There is no downtime -- the old domain set remains active until the new one is fully loaded.
bootstrap(): Promise<void>
Reloads domains from local storage or the bundled list. This method is called automatically during module initialization (onModuleInit), but you can also call it manually to reset the domain set:
@Injectable()
export class DisposableEmailResetService {
constructor(private readonly disposableEmail: DisposableEmailService) {}
async resetToDefault() {
// Reload domains from storage file (if it exists) or the bundled list
await this.disposableEmail.bootstrap();
}
}How bootstrap works:
- If
storagePathis set and the file exists, domains are loaded from that file. - Otherwise, domains are loaded from the bundled
domains.jsonthat ships with the package. - The whitelist is applied, removing any whitelisted domains.
- The internal domain set is replaced with the loaded data.
Use Cases
Validating Emails in a Service Layer
When you need validation logic beyond what a DTO decorator provides -- for example, checking against additional business rules:
import { Injectable, BadRequestException } from "@nestjs/common";
import { DisposableEmailService } from "@nestbolt/disposable-email";
@Injectable()
export class UsersService {
private readonly blockedDomains = ["competitor.com", "spam-domain.org"];
constructor(private readonly disposableEmail: DisposableEmailService) {}
async register(email: string, password: string) {
// Check disposable emails
if (this.disposableEmail.isDisposable(email)) {
throw new BadRequestException(
"Please use a permanent email address to register.",
);
}
// Additional business-specific domain checks
const domain = email.split("@")[1]?.toLowerCase();
if (this.blockedDomains.includes(domain)) {
throw new BadRequestException(
"This email domain is not accepted.",
);
}
// Proceed with registration...
}
}Guard-Based Validation
Use the service in a NestJS guard to block disposable emails at the request level before the handler executes:
import {
CanActivate,
ExecutionContext,
Injectable,
BadRequestException,
} from "@nestjs/common";
import { DisposableEmailService } from "@nestbolt/disposable-email";
@Injectable()
export class DisposableEmailGuard implements CanActivate {
constructor(private readonly disposableEmail: DisposableEmailService) {}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const email = request.body?.email;
if (email && this.disposableEmail.isDisposable(email)) {
throw new BadRequestException(
"Disposable email addresses are not allowed.",
);
}
return true;
}
}Apply the guard to specific routes:
import { Controller, Post, Body, UseGuards } from "@nestjs/common";
@Controller("users")
export class UsersController {
@Post()
@UseGuards(DisposableEmailGuard)
create(@Body() body: { email: string; name: string }) {
return { message: "User created", email: body.email };
}
}Bulk Email Validation
Use the service to filter disposable emails from a batch:
import { Injectable } from "@nestjs/common";
import { DisposableEmailService } from "@nestbolt/disposable-email";
@Injectable()
export class EmailImportService {
constructor(private readonly disposableEmail: DisposableEmailService) {}
filterDisposableEmails(emails: string[]): {
valid: string[];
disposable: string[];
} {
const valid: string[] = [];
const disposable: string[] = [];
for (const email of emails) {
if (this.disposableEmail.isDisposable(email)) {
disposable.push(email);
} else {
valid.push(email);
}
}
return { valid, disposable };
}
}Monitoring Domain List Health
Build an admin endpoint to inspect the current state of the domain list:
import { Controller, Get, Post, UseGuards } from "@nestjs/common";
import { DisposableEmailService } from "@nestbolt/disposable-email";
@Controller("admin/disposable-emails")
@UseGuards(AdminGuard)
export class DisposableEmailAdminController {
constructor(private readonly disposableEmail: DisposableEmailService) {}
@Get("status")
getStatus() {
const domains = this.disposableEmail.getDomains();
return {
totalDomains: domains.length,
sampleDomains: domains.slice(0, 10),
};
}
@Get("check/:email")
checkEmail(@Param("email") email: string) {
return {
email,
isDisposable: this.disposableEmail.isDisposable(email),
};
}
@Post("update")
async updateDomains() {
await this.disposableEmail.updateDomains();
return {
message: "Domain list updated.",
totalDomains: this.disposableEmail.getDomains().length,
};
}
@Post("reset")
async resetDomains() {
await this.disposableEmail.bootstrap();
return {
message: "Domain list reset to stored/bundled list.",
totalDomains: this.disposableEmail.getDomains().length,
};
}
}