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.
Configuration
Email validation is configured by specifying the email types you wish to block
and whether you wish to modify certain validation options.
The configuration definition is:
mode ?: " LIVE " | " DRY_RUN " ;
block ?: ArcjetEmailType [] ;
requireTopLevelDomain ?: boolean ; // default: true
allowDomainLiteral ?: boolean ; // default: false
The arcjet
client is configured with one or many validateEmail
rules which
take EmailOptions
.
Which email types to block is configured by listing the types in the
configuration block.
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 to
true
. Changing to false
means that foo@bar
would be allowed.
allowDomainLiteral
: Whether or not to allow email addresses with domain
literals. Defaults to false
. Changing to true
means that
foo@[123.456.789.0]
would be allowed.
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.
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 with req_
for decisions
involving the Arcjet cloud API. For decisions taken locally, the prefix is
lreq_
.
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 of ArcjetRuleResult
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 " ;
key : env . ARCJET_KEY ! , // Get your site key from https://app.arcjet.com
allow : [] , // "allow none" will block all detected bots
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 " ;
key : env . ARCJET_KEY , // Get your site key from https://app.arcjet.com
allow : [] , // "allow none" will block all detected bots
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 " ;
key : process . env . ARCJET_KEY ! ,
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 ( {
import arcjet , { validateEmail , detectBot } from " @arcjet/next " ;
import { NextResponse } from " next/server " ;
key : process . env . ARCJET_KEY ,
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 ( {
import arcjet , { detectBot , validateEmail } from " @arcjet/next " ;
key : process . env . ARCJET_KEY ,
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 ()) {
. 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 " ;
key : process . env . ARCJET_KEY ! ,
allow : [] , // "allow none" will block all detected bots
export default async function handler (
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 ()) {
. 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 " ;
key : env . ARCJET_KEY ! , // Get your site key from https://app.arcjet.com
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 " ;
key : env . ARCJET_KEY , // Get your site key from https://app.arcjet.com
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 , { detectBot , validateEmail } from " @arcjet/node " ;
import express from " express " ;
app . use ( express . urlencoded ( { extended : false } )) ;
// 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 ! ,
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 " } )) ;
res . writeHead ( 200 , { " Content-Type " : " application/json " } ) ;
res . end ( JSON . stringify ( { message : " Hello World " , email } )) ;
console . log ( ` Example app listening on port ${ port }` ) ;
import arcjet , { detectBot , validateEmail } from " @arcjet/node " ;
import express from " express " ;
app . use ( express . urlencoded ( { extended : false } )) ;
// 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 ,
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 " } )) ;
res . writeHead ( 200 , { " Content-Type " : " application/json " } ) ;
res . end ( JSON . stringify ( { message : " Hello World " , email } )) ;
console . log ( ` Example app listening on port ${ port }` ) ;
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-types
| " 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
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, Arcjet will return an ERROR
conclusion
.
import arcjet , { validateEmail } from " @arcjet/bun " ;
import { env } from " bun " ;
key : env . ARCJET_KEY ! , // Get your site key from https://app.arcjet.com
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 " ,
if ( decision . isErrored ()) {
// Fail open by logging the error and continuing
console . warn ( " Arcjet error " , decision . 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 " ;
key : env . ARCJET_KEY , // Get your site key from https://app.arcjet.com
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 " ,
if ( decision . isErrored ()) {
// Fail open by logging the error and continuing
console . warn ( " Arcjet error " , decision . 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 " ;
key : env . ARCJET_KEY ! , // Get your site key from https://app.arcjet.com
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 " ,
if ( decision . isErrored ()) {
// Fail open by logging the error and continuing
console . warn ( " Arcjet error " , decision . 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 " ;
key : env . ARCJET_KEY , // Get your site key from https://app.arcjet.com
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 " ,
if ( decision . isErrored ()) {
// Fail open by logging the error and continuing
console . warn ( " Arcjet error " , decision . 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 " ;
key : process . env . ARCJET_KEY ,
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 " ,
if ( decision . isErrored ()) {
// Fail open by logging the error and continuing
console . warn ( " Arcjet error " , decision . reason . message ) ;
// You could also fail closed here for very sensitive routes
//return res.status(503).json({ error: "Service unavailable" });
if ( decision . isDenied ()) {
. 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 " ;
key : process . env . ARCJET_KEY ! ,
export default async function handler (
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 " ,
if ( decision . isErrored ()) {
// Fail open by logging the error and continuing
console . warn ( " Arcjet error " , decision . reason . message ) ;
// You could also fail closed here for very sensitive routes
//return res.status(503).json({ error: "Service unavailable" });
if ( decision . isDenied ()) {
. json ( { error : " Forbidden " , reason : decision . reason } ) ;
res . status ( 200 ) . json ( { name : " Hello world " } ) ;
import arcjet , { validateEmail } from " @arcjet/next " ;
import { NextResponse } from " next/server " ;
key : process . env . ARCJET_KEY ! ,
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 " ,
if ( decision . isErrored ()) {
// Fail open by logging the error and continuing
console . warn ( " Arcjet error " , decision . 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 (
return NextResponse . json ( {
import arcjet , { validateEmail } from " @arcjet/next " ;
import { NextResponse } from " next/server " ;
key : process . env . ARCJET_KEY ,
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 " ,
if ( decision . isErrored ()) {
// Fail open by logging the error and continuing
console . warn ( " Arcjet error " , decision . 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 (
return NextResponse . json ( {
import arcjet , { validateEmail } from " @arcjet/node " ;
import express from " express " ;
app . use ( express . urlencoded ( { extended : false } )) ;
// 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 ! ,
mode : " LIVE " , // will block requests. Use "DRY_RUN" to log only
block : [ " 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 , {
console . log ( " Arcjet decision " , decision ) ;
if ( decision . isErrored ()) {
// Fail open by logging the error and continuing
console . warn ( " Arcjet error " , decision . 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 " } )) ;
res . writeHead ( 200 , { " Content-Type " : " application/json " } ) ;
res . end ( JSON . stringify ( { message : " Hello World " , email : req . body . email } )) ;
console . log ( ` Example app listening on port ${ port }` ) ;
import arcjet , { validateEmail } from " @arcjet/node " ;
import express from " express " ;
app . use ( express . urlencoded ( { extended : false } )) ;
// 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 ,
mode : " LIVE " , // will block requests. Use "DRY_RUN" to log only
block : [ " 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 , {
console . log ( " Arcjet decision " , decision ) ;
if ( decision . isErrored ()) {
// Fail open by logging the error and continuing
console . warn ( " Arcjet error " , decision . 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 " } )) ;
res . writeHead ( 200 , { " Content-Type " : " application/json " } ) ;
res . end ( JSON . stringify ( { message : " Hello World " , email : req . body . email } )) ;
console . log ( ` Example app listening on port ${ port }` ) ;
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.
Discussion