Email validation reference
Arcjet allows you validate & verify an email address. This is useful for preventing users from signing up with fake email addresses and can significantly reduce the amount of spam or fraudulent accounts.
Plan availability
Arcjet email validation availability depends depends on your pricing plan.
Plan | Email validation |
---|---|
Free | No |
Pro | Yes |
Enterprise | Custom |
Configuration
Email validation is configured by specifying the email types you wish to allow or deny, and whether you wish to modify certain validation options.
The arcjet
client can be configured with one or more Email validation rules,
which are constructed with the validateEmail(options: EmailOptions)
function
and configured by EmailOptions
:
type EmailOptions = | { mode?: ArcjetMode | undefined; allow: ArcjetEmailType[]; block?: never | undefined; deny?: never | undefined; requireTopLevelDomain?: boolean | undefined; allowDomainLiteral?: boolean | undefined; } | { mode?: ArcjetMode | undefined; allow?: never | undefined; block?: never | undefined; deny: ArcjetEmailType[]; requireTopLevelDomain?: boolean | undefined; allowDomainLiteral?: boolean | undefined; } | { mode?: ArcjetMode | undefined; allow?: never | undefined; block: ArcjetEmailType[]; deny?: never | undefined; requireTopLevelDomain?: boolean | undefined; allowDomainLiteral?: boolean | undefined; };
type ArcjetMode = "LIVE" | "DRY_RUN";
type ArcjetEmailType = | "DISPOSABLE" | "FREE" | "NO_MX_RECORDS" | "NO_GRAVATAR" | "INVALID";
The allow
and deny
lists are mutually-exclusive, such that using allow
will
result in a DENY
decision for any email type that is not specified in the
allow
list and using deny
will result in an ALLOW
decision for any email
type that is not specified in the deny
list.
The validation options can usually be left as the defaults. However, if you wish to allow certain types of email addresses, you can modify the options:
requireTopLevelDomain
: Whether or not to allow email addresses that don’t contain at least 2 domain segments (the domain name and TLD). Defaults totrue
. Changing tofalse
means thatfoo@bar
would be allowed.allowDomainLiteral
: Whether or not to allow email addresses with domain literals. Defaults tofalse
. Changing totrue
means thatfoo@[123.456.789.0]
would be allowed.
Pages & Server Actions
Arcjet can be used inside Next.js middleware, API routes, pages, server components, and server actions. Client components cannot be protected because they run on the client only.
See the Next.js SDK reference for examples of pages / page components and server actions.
Decision
Arcjet provides a single protect
function that is used to execute your
protection rules. This requires a request
argument which is the request
context as passed to the request handler. When configured with a validateEmail
rule it also requires an additional email
prop.
Arcjet can be integrated using a global guard
or per route guards. However these can’t be used for signup protection rules
because you can’t access the decision to handle the form submission. Instead,
you call the protect
function in your route handler. See
bot protection for an example where guards are
used.
This function returns a Promise
that resolves to an
ArcjetDecision
object. This contains the following properties:
id
(string
) - The unique ID for the request. This can be used to look up the request in the Arcjet dashboard. It is prefixed withreq_
for decisions involving the Arcjet cloud API. For decisions taken locally, the prefix islreq_
.conclusion
(ArcjetConclusion
) - The final conclusion based on evaluating each of the configured rules. If you wish to accept Arcjet’s recommended action based on the configured rules then you can use this property.reason
(ArcjetReason
) - An object containing more detailed information about the conclusion.results
(ArcjetRuleResult[]
) - An array ofArcjetRuleResult
objects containing the results of each rule that was executed.ip
(ArcjetIpDetails
) - An object containing Arcjet’s analysis of the client IP address. See the SDK reference for more information.
You check if a deny conclusion has been returned by an email validation rule by
using decision.isDenied()
and decision.reason.isEmail()
.
You can iterate through the results and check whether an email validation rule was applied:
for (const result of decision.results) { console.log("Rule Result", result);}
This example will log the full result as well as the email validation rule:
import arcjet, { detectBot, validateEmail } from "@arcjet/bun";import { env } from "bun";
const aj = arcjet({ key: env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export default { port: 3000, fetch: aj.handler(async (req) => { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. // TypeScript will guide you based on the configured rules email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return new Response("Forbidden", { status: 403 }); }
return new Response("Hello world"); }),};
import arcjet, { detectBot, validateEmail } from "@arcjet/bun";import { env } from "bun";
const aj = arcjet({ key: env.ARCJET_KEY, // Get your site key from https://app.arcjet.com rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export default { port: 3000, fetch: aj.handler(async (req) => { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return new Response("Forbidden", { status: 403 }); }
return new Response("Hello world"); }),};
import arcjet, { validateEmail, detectBot } from "@arcjet/next";import { NextResponse } from "next/server";
const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export async function POST(req: Request) { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. // TypeScript will guide you based on the configured rules email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); }
return NextResponse.json({ message: "Hello world", });}
import arcjet, { validateEmail, detectBot } from "@arcjet/next";import { NextResponse } from "next/server";
const aj = arcjet({ key: process.env.ARCJET_KEY, rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export async function POST(req) { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); }
return NextResponse.json({ message: "Hello world", });}
import arcjet, { detectBot, validateEmail } from "@arcjet/next";
const aj = arcjet({ key: process.env.ARCJET_KEY, rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export default async function handler(req, res) { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return res .status(403) .json({ error: "Forbidden", reason: decision.reason }); }
res.status(200).json({ name: "Hello world" });}
import arcjet, { detectBot, validateEmail } from "@arcjet/next";import type { NextApiRequest, NextApiResponse } from "next";
const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export default async function handler( req: NextApiRequest, res: NextApiResponse,) { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. // TypeScript will guide you based on the configured rules email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return res .status(403) .json({ error: "Forbidden", reason: decision.reason }); }
res.status(200).json({ name: "Hello world" });}
import { env } from "$env/dynamic/private";import arcjet, { detectBot, validateEmail } from "@arcjet/sveltekit";import { error, json, type RequestEvent } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export async function POST(event: RequestEvent) { const decision = await aj.protect(event, { // The email prop is required when a validateEmail rule is configured. // TypeScript will guide you based on the configured rules email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return error(403, "Forbidden"); }
return json({ message: "Hello world" });}
import { env } from "$env/dynamic/private";import arcjet, { detectBot, validateEmail } from "@arcjet/sveltekit";import { error, json } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY, // Get your site key from https://app.arcjet.com rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export async function POST(event) { const decision = await aj.protect(event, { // The email prop is required when a validateEmail rule is configured. email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return error(403, "Forbidden"); }
return json({ message: "Hello world" });}
import arcjet, { shield, validateEmail } from "@arcjet/remix";import type { ActionFunctionArgs } from "@remix-run/node";
const aj = arcjet({ // Get your site key from https://app.arcjet.com and set it as an environment // variable rather than hard coding. key: process.env.ARCJET_KEY!, rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), shield({ mode: "LIVE", }), ],});
export async function action(args: ActionFunctionArgs) { // The request body is a FormData object //const formData = await args.request.formData(); //const email = formData.get("email") as string; const email = "test@0zc7eznv3rsiswlohu.tk"; // Disposable email for demo
const decision = await aj.protect(args, { email });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); }
if (result.reason.isShield()) { console.log("Shield protection rule", result); } }
if (decision.isDenied()) { return Response.json({ error: "Forbidden." }, { status: 403 }); }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}
import arcjet, { shield, validateEmail } from "@arcjet/remix";
const aj = arcjet({ // Get your site key from https://app.arcjet.com and set it as an environment // variable rather than hard coding. key: process.env.ARCJET_KEY, rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), shield({ mode: "LIVE", }), ],});
export async function action(args) { // The request body is a FormData object //const formData = await args.request.formData(); //const email = formData.get("email") as string; const email = "test@0zc7eznv3rsiswlohu.tk"; // Disposable email for demo
const decision = await aj.protect(args, { email });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); }
if (result.reason.isShield()) { console.log("Shield protection rule", result); } }
if (decision.isDenied()) { return Response.json({ error: "Forbidden." }, { status: 403 }); }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}
import arcjet, { detectBot, validateEmail } from "@arcjet/node";import express from "express";
const app = express();const port = 3000;
app.use(express.urlencoded({ extended: false }));
const aj = arcjet({ // Get your site key from https://app.arcjet.com and set it as an environment // variable rather than hard coding. key: process.env.ARCJET_KEY!, rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
app.post("/", async (req, res) => { //const email = req.body.email; const email = "test@0zc7eznv3rsiswlohu.tk"; // Disposable email for demo console.log("Email received: ", email);
const decision = await aj.protect(req, { email }); console.log("Arcjet decision", decision);
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); }
if (result.reason.isBot()) { console.log("Bot protection rule", result); } }
if (decision.isDenied()) { res.writeHead(403, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Forbidden" })); } else { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello World", email })); }});
app.listen(port, () => { console.log(`Example app listening on port ${port}`);});
import arcjet, { detectBot, validateEmail } from "@arcjet/node";import express from "express";
const app = express();const port = 3000;
app.use(express.urlencoded({ extended: false }));
const aj = arcjet({ // Get your site key from https://app.arcjet.com and set it as an environment // variable rather than hard coding. key: process.env.ARCJET_KEY, rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
app.post("/", async (req, res) => { //const email = req.body.email; const email = "test@0zc7eznv3rsiswlohu.tk"; // Disposable email for demo console.log("Email received: ", email);
const decision = await aj.protect(req, { email }); console.log("Arcjet decision", decision);
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); }
if (result.reason.isBot()) { console.log("Bot protection rule", result); } }
if (decision.isDenied()) { res.writeHead(403, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Forbidden" })); } else { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello World", email })); }});
app.listen(port, () => { console.log(`Example app listening on port ${port}`);});
import { ARCJET, type ArcjetNest, detectBot, validateEmail,} from "@arcjet/nest";import { Body, Controller, HttpException, HttpStatus, Inject, Injectable, Logger, Post, Req, UseInterceptors,} from "@nestjs/common";import { NoFilesInterceptor } from "@nestjs/platform-express";import { IsNotEmpty } from "class-validator";import type { Request } from "express";
// Validation class as described at// https://docs.nestjs.com/techniques/validation. We're not using the IsEmail// decorator here because Arcjet handles this for you.export class SignupDto { @IsNotEmpty() // @ts-ignore: This is a DTO class so ignore that it's not definitely assigned email: string;}
// This would normally go in your service file e.g.// src/signup/signup.service.ts@Injectable()export class SignupService { private readonly logger = new Logger(SignupService.name);
signup(email: string): { message: string } { this.logger.log(`Form submission: ${email}`);
return { message: "Hello world", }; }}
// This would normally go in your controller file e.g.// src/signup/signup.controller.ts@Controller("signup")export class SignupController { private readonly logger = new Logger(SignupController.name);
constructor( private readonly signupService: SignupService, @Inject(ARCJET) private readonly arcjet: ArcjetNest, ) { }
// Implement a form handler following // https://docs.nestjs.com/techniques/file-upload#no-files. Note this isn't // compatible with the NestJS Fastify adapter. @Post() @UseInterceptors(NoFilesInterceptor()) async index(@Req() req: Request, @Body() body: SignupDto) { const decision = await this.arcjet .withRule( detectBot({ mode: "LIVE", // will block requests. Use "DRY_RUN" to log only // configured with a list of bots to allow from // https://arcjet.com/bot-list allow: [], // blocks all automated clients }), ) .withRule( validateEmail({ mode: "LIVE", // will block requests. Use "DRY_RUN" to log only // block disposable, invalid, and email addresses with no MX records deny: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"], }), ) .protect(req, { email: body.email });
this.logger.log(`Arcjet: id = ${decision.id}`); this.logger.log(`Arcjet: decision = ${decision.conclusion}`);
for (const result of decision.results) { this.logger.log("Rule Result", result);
if (result.reason.isBot()) { this.logger.log("Bot protection rule", result); }
if (result.reason.isEmail()) { this.logger.log("Email validation rule", result); } }
if (decision.isDenied()) { if (decision.reason.isEmail()) { this.logger.log(`Arcjet: email error = ${decision.reason.emailTypes}`);
let message: string;
// These are specific errors to help the user, but will also reveal the // validation to a spammer. if (decision.reason.emailTypes.includes("INVALID")) { message = "email address format is invalid. Is there a typo?"; } else if (decision.reason.emailTypes.includes("DISPOSABLE")) { message = "we do not allow disposable email addresses."; } else if (decision.reason.emailTypes.includes("NO_MX_RECORDS")) { message = "your email domain does not have an MX record. Is there a typo?"; } else { // This is a catch all, but the above should be exhaustive based on the // configured rules. message = "invalid email."; }
throw new HttpException(`Error: ${message}`, HttpStatus.BAD_REQUEST); } else { throw new HttpException("Forbidden", HttpStatus.FORBIDDEN); } }
return this.signupService.signup(body.email); }}
Allowing specific email types
In addition to being able to deny specific email types, you can also configure Arcjet to only allow specific email types and all other types will be blocked.
import arcjet, { detectBot, validateEmail } from "@arcjet/bun";import { env } from "bun";
const aj = arcjet({ key: env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com rules: [ validateEmail({ mode: "LIVE", allow: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export default { port: 3000, fetch: aj.handler(async (req) => { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. // TypeScript will guide you based on the configured rules email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return new Response("Forbidden", { status: 403 }); }
return new Response("Hello world"); }),};
import arcjet, { detectBot, validateEmail } from "@arcjet/bun";import { env } from "bun";
const aj = arcjet({ key: env.ARCJET_KEY, // Get your site key from https://app.arcjet.com rules: [ validateEmail({ mode: "LIVE", allow: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export default { port: 3000, fetch: aj.handler(async (req) => { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return new Response("Forbidden", { status: 403 }); }
return new Response("Hello world"); }),};
import arcjet, { validateEmail, detectBot } from "@arcjet/next";import { NextResponse } from "next/server";
const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [ validateEmail({ mode: "LIVE", allow: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export async function POST(req: Request) { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. // TypeScript will guide you based on the configured rules email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); }
return NextResponse.json({ message: "Hello world", });}
import arcjet, { validateEmail, detectBot } from "@arcjet/next";import { NextResponse } from "next/server";
const aj = arcjet({ key: process.env.ARCJET_KEY, rules: [ validateEmail({ mode: "LIVE", allow: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export async function POST(req) { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); }
return NextResponse.json({ message: "Hello world", });}
import arcjet, { detectBot, validateEmail } from "@arcjet/next";
const aj = arcjet({ key: process.env.ARCJET_KEY, rules: [ validateEmail({ mode: "LIVE", allow: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export default async function handler(req, res) { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return res .status(403) .json({ error: "Forbidden", reason: decision.reason }); }
res.status(200).json({ name: "Hello world" });}
import arcjet, { detectBot, validateEmail } from "@arcjet/next";import type { NextApiRequest, NextApiResponse } from "next";
const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [ validateEmail({ mode: "LIVE", allow: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export default async function handler( req: NextApiRequest, res: NextApiResponse,) { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. // TypeScript will guide you based on the configured rules email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return res .status(403) .json({ error: "Forbidden", reason: decision.reason }); }
res.status(200).json({ name: "Hello world" });}
import { env } from "$env/dynamic/private";import arcjet, { detectBot, validateEmail } from "@arcjet/sveltekit";import { error, json, type RequestEvent } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com rules: [ validateEmail({ mode: "LIVE", allow: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export async function POST(event: RequestEvent) { const decision = await aj.protect(event, { // The email prop is required when a validateEmail rule is configured. // TypeScript will guide you based on the configured rules email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return error(403, "Forbidden"); }
return json({ message: "Hello world" });}
import { env } from "$env/dynamic/private";import arcjet, { detectBot, validateEmail } from "@arcjet/sveltekit";import { error, json } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY, // Get your site key from https://app.arcjet.com rules: [ validateEmail({ mode: "LIVE", allow: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export async function POST(event) { const decision = await aj.protect(event, { // The email prop is required when a validateEmail rule is configured. email: "test@0zc7eznv3rsiswlohu.tk", });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); } }
if (decision.isDenied()) { return error(403, "Forbidden"); }
return json({ message: "Hello world" });}
import arcjet, { shield, validateEmail } from "@arcjet/remix";import type { ActionFunctionArgs } from "@remix-run/node";
const aj = arcjet({ // Get your site key from https://app.arcjet.com and set it as an environment // variable rather than hard coding. key: process.env.ARCJET_KEY!, rules: [ validateEmail({ mode: "LIVE", allow: ["DISPOSABLE"], }), shield({ mode: "LIVE", }), ],});
export async function action(args: ActionFunctionArgs) { // The request body is a FormData object //const formData = await args.request.formData(); //const email = formData.get("email") as string; const email = "test@0zc7eznv3rsiswlohu.tk"; // Disposable email for demo
const decision = await aj.protect(args, { email });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); }
if (result.reason.isShield()) { console.log("Shield protection rule", result); } }
if (decision.isDenied()) { return Response.json({ error: "Forbidden." }, { status: 403 }); }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}
import arcjet, { shield, validateEmail } from "@arcjet/remix";
const aj = arcjet({ // Get your site key from https://app.arcjet.com and set it as an environment // variable rather than hard coding. key: process.env.ARCJET_KEY, rules: [ validateEmail({ mode: "LIVE", allow: ["DISPOSABLE"], }), shield({ mode: "LIVE", }), ],});
export async function action(args) { // The request body is a FormData object //const formData = await args.request.formData(); //const email = formData.get("email") as string; const email = "test@0zc7eznv3rsiswlohu.tk"; // Disposable email for demo
const decision = await aj.protect(args, { email });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); }
if (result.reason.isShield()) { console.log("Shield protection rule", result); } }
if (decision.isDenied()) { return Response.json({ error: "Forbidden." }, { status: 403 }); }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}
import arcjet, { detectBot, validateEmail } from "@arcjet/node";import express from "express";
const app = express();const port = 3000;
app.use(express.urlencoded({ extended: false }));
const aj = arcjet({ // Get your site key from https://app.arcjet.com and set it as an environment // variable rather than hard coding. key: process.env.ARCJET_KEY!, rules: [ validateEmail({ mode: "LIVE", allow: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
app.post("/", async (req, res) => { //const email = req.body.email; const email = "test@0zc7eznv3rsiswlohu.tk"; // Disposable email for demo console.log("Email received: ", email);
const decision = await aj.protect(req, { email }); console.log("Arcjet decision", decision);
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); }
if (result.reason.isBot()) { console.log("Bot protection rule", result); } }
if (decision.isDenied()) { res.writeHead(403, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Forbidden" })); } else { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello World", email })); }});
app.listen(port, () => { console.log(`Example app listening on port ${port}`);});
import arcjet, { detectBot, validateEmail } from "@arcjet/node";import express from "express";
const app = express();const port = 3000;
app.use(express.urlencoded({ extended: false }));
const aj = arcjet({ // Get your site key from https://app.arcjet.com and set it as an environment // variable rather than hard coding. key: process.env.ARCJET_KEY, rules: [ validateEmail({ mode: "LIVE", allow: ["DISPOSABLE"], }), detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
app.post("/", async (req, res) => { //const email = req.body.email; const email = "test@0zc7eznv3rsiswlohu.tk"; // Disposable email for demo console.log("Email received: ", email);
const decision = await aj.protect(req, { email }); console.log("Arcjet decision", decision);
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isEmail()) { console.log("Email rule", result); }
if (result.reason.isBot()) { console.log("Bot protection rule", result); } }
if (decision.isDenied()) { res.writeHead(403, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Forbidden" })); } else { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello World", email })); }});
app.listen(port, () => { console.log(`Example app listening on port ${port}`);});
import { ARCJET, type ArcjetNest, detectBot, validateEmail,} from "@arcjet/nest";import { Body, Controller, HttpException, HttpStatus, Inject, Injectable, Logger, Post, Req, UseInterceptors,} from "@nestjs/common";import { NoFilesInterceptor } from "@nestjs/platform-express";import { IsNotEmpty } from "class-validator";import type { Request } from "express";
// Validation class as described at// https://docs.nestjs.com/techniques/validation. We're not using the IsEmail// decorator here because Arcjet handles this for you.export class SignupDto { @IsNotEmpty() // @ts-ignore: This is a DTO class so ignore that it's not definitely assigned email: string;}
// This would normally go in your service file e.g.// src/signup/signup.service.ts@Injectable()export class SignupService { private readonly logger = new Logger(SignupService.name);
signup(email: string): { message: string } { this.logger.log(`Form submission: ${email}`);
return { message: "Hello world", }; }}
// This would normally go in your controller file e.g.// src/signup/signup.controller.ts@Controller("signup")export class SignupController { private readonly logger = new Logger(SignupController.name);
constructor( private readonly signupService: SignupService, @Inject(ARCJET) private readonly arcjet: ArcjetNest, ) { }
// Implement a form handler following // https://docs.nestjs.com/techniques/file-upload#no-files. Note this isn't // compatible with the NestJS Fastify adapter. @Post() @UseInterceptors(NoFilesInterceptor()) async index(@Req() req: Request, @Body() body: SignupDto) { const decision = await this.arcjet .withRule( detectBot({ mode: "LIVE", // will block requests. Use "DRY_RUN" to log only // configured with a list of bots to allow from // https://arcjet.com/bot-list allow: [], // blocks all automated clients }), ) .withRule( validateEmail({ mode: "LIVE", // will block requests. Use "DRY_RUN" to log only // block disposable, invalid, and email addresses with no MX records allow: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"], }), ) .protect(req, { email: body.email });
this.logger.log(`Arcjet: id = ${decision.id}`); this.logger.log(`Arcjet: decision = ${decision.conclusion}`);
for (const result of decision.results) { this.logger.log("Rule Result", result);
if (result.reason.isBot()) { this.logger.log("Bot protection rule", result); }
if (result.reason.isEmail()) { this.logger.log("Email validation rule", result); } }
if (decision.isDenied()) { if (decision.reason.isEmail()) { this.logger.log(`Arcjet: email error = ${decision.reason.emailTypes}`);
let message: string;
// These are specific errors to help the user, but will also reveal the // validation to a spammer. if (decision.reason.emailTypes.includes("INVALID")) { message = "email address format is invalid. Is there a typo?"; } else if (decision.reason.emailTypes.includes("DISPOSABLE")) { message = "we do not allow disposable email addresses."; } else if (decision.reason.emailTypes.includes("NO_MX_RECORDS")) { message = "your email domain does not have an MX record. Is there a typo?"; } else { // This is a catch all, but the above should be exhaustive based on the // configured rules. message = "invalid email."; }
throw new HttpException(`Error: ${message}`, HttpStatus.BAD_REQUEST); } else { throw new HttpException("Forbidden", HttpStatus.FORBIDDEN); } }
return this.signupService.signup(body.email); }}
Checking the email type
Arcjet will return the type of email address that was verified. This will be one or several of the reported email types.
// See https://docs.arcjet.com/email-validation/concepts#email-typestype ArcjetEmailType = | "DISPOSABLE" // Disposable email address from a throwaway email service | "FREE" // Email address from a free email service | "NO_MX_RECORDS" // Email address with no MX records i.e. is undeliverable | "NO_GRAVATAR" // Email address with no Gravatar profile | "INVALID"; // Email address that is invalid
You can check the email type using the decision.reason.emailTypes
array:
let message = "";// You could return specific messages based on the email type, but this would// also reveal the validation to a spammerif (decision.reason.emailTypes.includes("DISPOSABLE")) { message = "We do not allow disposable email addresses.";} else if (decision.reason.emailTypes.includes("FREE")) { message = "We do not allow free email addresses, please use a business address.";} else if (decision.reason.emailTypes.includes("NO_MX_RECORDS")) { message = "Your email domain does not have an MX record. Is there a typo?";} else if (decision.reason.emailTypes.includes("NO_GRAVATAR")) { message = "We require a Gravatar profile to sign up.";} else { // This is a catch all message = "Invalid email.";}
Error handling
Arcjet is designed to fail open so that a service issue or misconfiguration does
not block all requests. The SDK will also time out and fail open after 1000ms
when NODE_ENV
or ARCJET_ENV
is development
and 500ms otherwise. However,
in most cases, the response time will be less than 20-30ms.
If there is an error condition when processing the rule, Arcjet will return an
ERROR
result for that rule and you can check the message
property on the rule’s
error result for more information.
If all other rules that were run returned an ALLOW
result, then the final Arcjet
conclusion will be ERROR
.
import arcjet, { validateEmail } from "@arcjet/bun";import { env } from "bun";
const aj = arcjet({ key: env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com rules: [ validateEmail({ mode: "LIVE", block: ["DISPOSABLE"], }), ],});
export default { port: 3000, fetch: aj.handler(async (req) => { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. // TypeScript will guide you based on the configured rules email: "test@0zc7eznv3rsiswlohu.tk", });
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes // return new Response("Service unavailable", { status: 503 }); } }
if (decision.isDenied()) { return new Response("Forbidden", { status: 403 }); }
return new Response("Hello world"); }),};
import arcjet, { validateEmail } from "@arcjet/bun";import { env } from "bun";
const aj = arcjet({ key: env.ARCJET_KEY, // Get your site key from https://app.arcjet.com rules: [ validateEmail({ mode: "LIVE", block: ["DISPOSABLE"], }), ],});
export default { port: 3000, fetch: aj.handler(async (req) => { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. email: "test@0zc7eznv3rsiswlohu.tk", });
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes // return new Response("Service unavailable", { status: 503 }); } }
if (decision.isDenied()) { return new Response("Forbidden", { status: 403 }); }
return new Response("Hello world"); }),};
import { env } from "$env/dynamic/private";import arcjet, { validateEmail } from "@arcjet/sveltekit";import { error, json, type RequestEvent } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), ],});
export async function POST(event: RequestEvent) { const decision = await aj.protect(event, { // The email prop is required when a validateEmail rule is configured. // TypeScript will guide you based on the configured rules email: "test@0zc7eznv3rsiswlohu.tk", });
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes //return error(503, { message: "Service unavailable" }); } }
if (decision.isDenied()) { return error(403, { message: "Forbidden" }); }
return json({ message: "Hello world" });}
import { env } from "$env/dynamic/private";import arcjet, { validateEmail } from "@arcjet/sveltekit";import { error, json } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY, // Get your site key from https://app.arcjet.com rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), ],});
export async function POST(event) { const decision = await aj.protect(event, { // The email prop is required when a validateEmail rule is configured. email: "test@0zc7eznv3rsiswlohu.tk", });
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes //return error(503, { message: "Service unavailable" }); } }
if (decision.isDenied()) { return error(403, { message: "Forbidden" }); }
return json({ message: "Hello world" });}
import arcjet, { validateEmail } from "@arcjet/next";
const aj = arcjet({ key: process.env.ARCJET_KEY, rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), ],});
export default async function handler(req, res) { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. email: "test@0zc7eznv3rsiswlohu.tk", });
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes //return res.status(503).json({ error: "Service unavailable" }); } }
if (decision.isDenied()) { return res .status(403) .json({ error: "Forbidden", reason: decision.reason }); }
res.status(200).json({ name: "Hello world" });}
import arcjet, { validateEmail } from "@arcjet/next";import type { NextApiRequest, NextApiResponse } from "next";
const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), ],});
export default async function handler( req: NextApiRequest, res: NextApiResponse,) { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. // TypeScript will guide you based on the configured rules email: "test@0zc7eznv3rsiswlohu.tk", });
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes //return res.status(503).json({ error: "Service unavailable" }); } }
if (decision.isDenied()) { return res .status(403) .json({ error: "Forbidden", reason: decision.reason }); }
res.status(200).json({ name: "Hello world" });}
import arcjet, { validateEmail } from "@arcjet/next";import { NextResponse } from "next/server";
const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), ],});
export async function POST(req: Request) { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. // TypeScript will guide you based on the configured rules email: "test@0zc7eznv3rsiswlohu.tk", });
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes //return NextResponse.json({ error: "Service unavailable" }, { status: 503 }); } }
if (decision.isDenied()) { return NextResponse.json( { error: "Forbidden", }, { status: 403, }, ); }
return NextResponse.json({ message: "Hello world", });}
import arcjet, { validateEmail } from "@arcjet/next";import { NextResponse } from "next/server";
const aj = arcjet({ key: process.env.ARCJET_KEY, rules: [ validateEmail({ mode: "LIVE", deny: ["DISPOSABLE"], }), ],});
export async function POST(req) { const decision = await aj.protect(req, { // The email prop is required when a validateEmail rule is configured. email: "test@0zc7eznv3rsiswlohu.tk", });
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes //return NextResponse.json({ error: "Service unavailable" }, { status: 503 }); } }
if (decision.isDenied()) { return NextResponse.json( { error: "Forbidden", }, { status: 403, }, ); }
return NextResponse.json({ message: "Hello world", });}
import arcjet, { validateEmail } from "@arcjet/remix";import type { ActionFunctionArgs } from "@remix-run/node";
const aj = arcjet({ // Get your site key from https://app.arcjet.com and set it as an environment // variable rather than hard coding. key: process.env.ARCJET_KEY!, rules: [ validateEmail({ mode: "LIVE", // will block requests. Use "DRY_RUN" to log only // block disposable, invalid, and email addresses with no MX records deny: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"], }), ],});
// The action function is called for non-GET requests, which is where you// typically handle form submissions that might contain an email address.export async function action(args: ActionFunctionArgs) { // The request body is a FormData object const formData = await args.request.formData(); const email = formData.get("email") as string;
const decision = await aj.protect(args, { email }); console.log("Arcjet decision", decision);
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes // throw new Response("Service unavailable", { status: 503, statusText: "Service unavailable" }); } }
if (decision.isDenied()) { if (decision.reason.isEmail()) { return Response.json({ error: "Invalid email." }, { status: 400 }); } else { return Response.json({ error: "Forbidden" }, { status: 403 }); } }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}
import arcjet, { validateEmail } from "@arcjet/remix";
const aj = arcjet({ // Get your site key from https://app.arcjet.com and set it as an environment // variable rather than hard coding. key: process.env.ARCJET_KEY, rules: [ validateEmail({ mode: "LIVE", // will block requests. Use "DRY_RUN" to log only // block disposable, invalid, and email addresses with no MX records deny: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"], }), ],});
// The action function is called for non-GET requests, which is where you// typically handle form submissions that might contain an email address.export async function action(args) { // The request body is a FormData object const formData = await args.request.formData(); const email = formData.get("email");
const decision = await aj.protect(args, { email }); console.log("Arcjet decision", decision);
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes // throw new Response("Service unavailable", { status: 503, statusText: "Service unavailable" }); } }
if (decision.isDenied()) { if (decision.reason.isEmail()) { return Response.json({ error: "Invalid email." }, { status: 400 }); } else { return Response.json({ error: "Forbidden" }, { status: 403 }); } }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}
import arcjet, { validateEmail } from "@arcjet/node";import express from "express";
const app = express();const port = 3000;
app.use(express.urlencoded({ extended: false }));
const aj = arcjet({ // Get your site key from https://app.arcjet.com and set it as an environment // variable rather than hard coding. key: process.env.ARCJET_KEY!, rules: [ validateEmail({ mode: "LIVE", // will block requests. Use "DRY_RUN" to log only deny: ["NO_MX_RECORDS"], // block email addresses with no MX records }), ],});
app.post("/", async (req, res) => { console.log("Email received: ", req.body.email);
const decision = await aj.protect(req, { email: req.body.email, }); console.log("Arcjet decision", decision);
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes //res.writeHead(503, { "Content-Type": "application/json" }); //res.end(JSON.stringify({ error: "Service unavailable" })); } }
if (decision.isDenied()) { res.writeHead(403, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Forbidden" })); } else { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello World", email: req.body.email })); }});
app.listen(port, () => { console.log(`Example app listening on port ${port}`);});
import arcjet, { validateEmail } from "@arcjet/node";import express from "express";
const app = express();const port = 3000;
app.use(express.urlencoded({ extended: false }));
const aj = arcjet({ // Get your site key from https://app.arcjet.com and set it as an environment // variable rather than hard coding. key: process.env.ARCJET_KEY, rules: [ validateEmail({ mode: "LIVE", // will block requests. Use "DRY_RUN" to log only deny: ["NO_MX_RECORDS"], // block email addresses with no MX records }), ],});
app.post("/", async (req, res) => { console.log("Email received: ", req.body.email);
const decision = await aj.protect(req, { email: req.body.email, }); console.log("Arcjet decision", decision);
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes //res.writeHead(503, { "Content-Type": "application/json" }); //res.end(JSON.stringify({ error: "Service unavailable" })); } }
if (decision.isDenied()) { res.writeHead(403, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Forbidden" })); } else { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello World", email: req.body.email })); }});
app.listen(port, () => { console.log(`Example app listening on port ${port}`);});
import { ARCJET, type ArcjetNest, validateEmail } from "@arcjet/nest";import { Body, Controller, HttpException, HttpStatus, Inject, Injectable, Logger, Post, Req, UseInterceptors,} from "@nestjs/common";import { NoFilesInterceptor } from "@nestjs/platform-express";import { IsNotEmpty } from "class-validator";import type { Request } from "express";
// Validation class as described at// https://docs.nestjs.com/techniques/validation. We're not using the IsEmail// decorator here because Arcjet handles this for you.export class SignupDto { @IsNotEmpty() // @ts-ignore: This is a DTO class so ignore that it's not definitely assigned email: string;}
// This would normally go in your service file e.g.// src/signup/signup.service.ts@Injectable()export class SignupService { private readonly logger = new Logger(SignupService.name);
signup(email: string): { message: string } { this.logger.log(`Form submission: ${email}`);
return { message: "Hello world", }; }}
// This would normally go in your controller file e.g.// src/signup/signup.controller.ts@Controller("signup")export class SignupController { private readonly logger = new Logger(SignupController.name);
constructor( private readonly signupService: SignupService, @Inject(ARCJET) private readonly arcjet: ArcjetNest, ) { }
// Implement a form handler following // https://docs.nestjs.com/techniques/file-upload#no-files. Note this isn't // compatible with the NestJS Fastify adapter. @Post() @UseInterceptors(NoFilesInterceptor()) async index(@Req() req: Request, @Body() body: SignupDto) { const decision = await this.arcjet .withRule( validateEmail({ mode: "LIVE", // will block requests. Use "DRY_RUN" to log only // block disposable, invalid, and email addresses with no MX records deny: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"], }), ) .protect(req, { email: body.email });
this.logger.log(`Arcjet: id = ${decision.id}`); this.logger.log(`Arcjet: decision = ${decision.conclusion}`);
if (decision.isDenied()) { if (decision.reason.isEmail()) { this.logger.log(`Arcjet: email error = ${decision.reason.emailTypes}`);
let message: string;
// These are specific errors to help the user, but will also reveal the // validation to a spammer. if (decision.reason.emailTypes.includes("INVALID")) { message = "email address format is invalid. Is there a typo?"; } else if (decision.reason.emailTypes.includes("DISPOSABLE")) { message = "we do not allow disposable email addresses."; } else if (decision.reason.emailTypes.includes("NO_MX_RECORDS")) { message = "your email domain does not have an MX record. Is there a typo?"; } else { // This is a catch all, but the above should be exhaustive based on the // configured rules. message = "invalid email."; }
throw new HttpException(`Error: ${message}`, HttpStatus.BAD_REQUEST); } else { throw new HttpException("Forbidden", HttpStatus.FORBIDDEN); } } else if (decision.isErrored()) { if (decision.reason.message.includes("missing User-Agent header")) { // Requests without User-Agent headers can not be identified as any // particular bot and will be marked as an errored decision. Most // legitimate clients always send this header, so we recommend blocking // requests without it. this.logger.warn("User-Agent header is missing"); throw new HttpException("Bad request", HttpStatus.BAD_REQUEST); } else { // Fail open to prevent an Arcjet error from blocking all requests. You // may want to fail closed if this controller is very sensitive this.logger.error(`Arcjet error: ${decision.reason.message}`); } }
return this.signupService.signup(body.email); }}
Testing
Arcjet runs the same in any environment, including locally and in CI. You can
use the mode
set to DRY_RUN
to log the results of rule execution without
blocking any requests.
We have an example test framework you can use to automatically test your rules. Arcjet can also be triggered based using a sample of your traffic.
See the Testing section of the docs for details.