emailverifier.dev
Posts

Auth0 Signup Email Validation Action Tutorial

| 4 min read | Usama Ejaz
Auth0 signup action cover with an email inside an authentication ring and a check mark.

This tutorial creates an Auth0 Pre User Registration Action that checks an email before Auth0 creates a Database or Passwordless user. You will add the dependency and secret, deploy the Action, attach it to the flow, run the Action test, and confirm the behavior through Universal Login.

The trigger does not run for social connections. That limitation is part of the implementation, not an edge case to discover after deployment.

Create the Pre User Registration Action

In the Auth0 Dashboard, open Actions → Library, select Build Custom, and create an Action with the Pre User Registration trigger. Name it Validate signup email.

Auth0 runs this trigger synchronously before creating Database and Passwordless users. The official Pre User Registration documentation lists the supported connection types and event object.

Add the dependency and project secret

In the Action editor, open Modules and add axios. Then open Secrets and add:

EMAILVERIFIER_API_KEY

Create an emailverifier.dev project and copy its API key into that secret. The value is available to the Action through event.secrets and never needs to appear in source.

Paste the complete Action

const axios = require('axios')

exports.onExecutePreUserRegistration =
  async (event, api) => {
    const email = event.user.email?.trim()

    if (!email) {
      return
    }

    let result
    try {
      const response = await axios.post(
        'https://emailverifier.dev/api/v1/verify',
        { email },
        {
          headers: {
            'Content-Type': 'application/json',
            'X-API-Key':
              event.secrets.EMAILVERIFIER_API_KEY
          },
          timeout: 4000
        }
      )

      result = response.data
    } catch {
      api.access.deny(
        'email_check_unavailable',
        'Email verification is temporarily unavailable.'
      )
      return
    }

    if (result.action !== 'block') {
      return
    }

    if (
      result.signals.includes(
        'disposable_address'
      )
    ) {
      api.access.deny(
        'disposable_email',
        'Use a permanent email address.'
      )
      return
    }

    api.access.deny(
      'email_not_accepted',
      result.suggestion
        ? 'Check the email address and use the suggested correction.'
        : 'Check the email address and try again.'
    )
  }

The Action performs one external request with a four-second timeout. Returning allows registration to continue. api.access.deny stops user creation and sends the supplied message back through the signup flow.

Run the built-in Action test

Open the editor's test panel and use a Database-connection event with an email. The configured secret is injected by Auth0; it should not be pasted into the event JSON.

Run at least these test cases:

  • A normal email reaches the allow or review return path.
  • A mocked disposable result calls api.access.deny with disposable_email.
  • A non-200 response or timeout calls api.access.deny with email_check_unavailable.
  • An event without an email returns without making the request.

The Action editor records the call and result. Auth0's Action workflow guide covers modules, secrets, testing, and deployment.

Deploy and attach the Action

Select Deploy. Then open Actions → Flows → Pre User Registration, drag Validate signup email into the flow, and select Apply.

Deployment alone is not enough. The Action runs only after it has been added to the flow and the flow has been applied.

Test Universal Login

Open the application's Universal Login signup page and create a user through every enabled Database and Passwordless connection. Check the Auth0 user list after each attempt:

  • An allowed or review result creates the user.
  • A blocking result shows the Action message and leaves no new user.
  • An unavailable verification request follows the failure policy in the catch block.

Run one social login as well. The user will bypass this Pre User Registration trigger, which confirms the documented connection limitation. Apply equivalent policy after login if social identities must be checked.

Choose fail-closed or fail-open behavior

The sample fails closed during an emailverifier.dev outage. That protects a valuable trial from bypass, but it also pauses legitimate Database and Passwordless signups.

For a low-risk account, replace the catch block's deny call with return. Then require Auth0 email verification or restricted access and record that the external check did not finish. Do not describe an HTTP failure as an invalid mailbox.

Keep the four outcomes intact

Result Action behavior
deliverable Return and continue, while preserving any separate role or catch-all review policy.
risky Deny disposable addresses; allow a review result into confirmation.
undeliverable Deny after a confirmed syntax, routing, or recipient failure.
unknown Continue through confirmation or restricted access because recipient evidence was inconclusive.

The REST API reference defines the result and error contract. The tutorial is complete when the Action test passes, the Action is attached to the flow, a blocked Database signup creates no user, and a social test confirms the trigger boundary.

The boundary between this Action and Auth0's ownership step is explained in Email Validation vs Verification vs Confirmation.

Email verification evaluates address evidence. Keep Auth0's confirmation step when password recovery, security messages, billing, or sensitive access depends on proof of inbox control.

Continue reading