> ## Documentation Index
> Fetch the complete documentation index at: https://docs-staging-quickstart-revamp.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Learn how to customize email flow and control when and how emails are sent.

# Customize Email Handling

<Warning>
  The End of Life (EOL) date of Rules and Hooks will be **November 18, 2026**, and they are no longer available to new tenants created as of **October 16, 2023**. Existing tenants with active Hooks will retain Hooks product access through end of life.

  We highly recommend that you use Actions to extend Auth0. With Actions, you have access to rich type information, inline documentation, and public `npm` packages, and can connect external integrations that enhance your overall extensibility experience. To learn more about what Actions offer, read [Understand How Auth0 Actions Work](/docs/customize/actions/actions-overview).

  To help with your migration, we offer guides that will help you [migrate from Rules to Actions](/docs/customize/actions/migrate/migrate-from-rules-to-actions) and [migrate from Hooks to Actions](/docs/customize/actions/migrate/migrate-from-hooks-to-actions). We also have a dedicated [Move to Actions](https://auth0.com/extensibility/movetoactions) page that highlights feature comparisons, [an Actions demo](https://www.youtube.com/watch?v=UesFSY1klrI), and other resources to help you on your migration journey.

  To read more about the Rules and Hooks deprecation, read our blog post: [Preparing for Rules and Hooks End of Life](https://auth0.com/blog/preparing-for-rules-and-hooks-end-of-life/).
</Warning>

<Warning>
  Because we plan to remove Rules and Hooks functions in 2026, you should create new Rules or Hooks only in your Development environment and only to test migration to Actions.

  To learn how to migrate your Rules to Actions, read [Migrate from Rules to Actions](/docs/customize/actions/migrate/migrate-from-rules-to-actions). To learn how to migrate your Hooks to Actions, read [Migrate from Hooks to Actions](/docs/customize/actions/migrate/migrate-from-hooks-to-actions).
</Warning>

Our default email flow can address the requirements of most applications. Sometimes, however, you may require more flexibility, such as when implementing:

* localization
* custom **Redirect To** URLs based on user or tenant
* different email templates per application or tenant

The Auth0 <Tooltip tip="Management API: A product to allow customers to perform administrative tasks." cta="View Glossary" href="/docs/glossary?term=Management+API">Management API</Tooltip> provides endpoints to help you manage your email flow to control when and how emails are sent. If necessary, you can also implement your own Custom Email endpoints and use the Auth0 Management API endpoints to help manage the rest of the flow.

## Verification emails

A verification email should be sent to every user for which the `email_verified` property is `false`. Typically, these are users in database connections or users authenticating with social providers that do not validate email addresses upon new user registration.

You can send verification emails in more than one way:

<Tabs>
  <Tab title="Management API">
    The [Send an email address verification email endpoint](https://auth0.com/docs/api/management/v2#!/Jobs/post_verification_email) sends the user an email prompting them to verify their email address.

    <CodeGroup>
      ```bash cURL lines
      curl --request POST \
        --url 'https://{yourDomain}/api/v2/jobs/verification-email' \
        --header 'authorization: Bearer {yourMgmtApiAccessToken}' \
        --header 'content-type: application/json' \
        --data '{ "user_id": "{userIdOfVerifyEmailRecipient}", "client_id": "{yourAppClientId}","identity": {"user_id": "5457edea1b8f22891a000004","provider": "google-oauth2"}, "organization_id": "{yourOrganizationId}" }'
      ```

      ```csharp C# lines
      var client = new RestClient("https://{yourDomain}/api/v2/jobs/verification-email");
      var request = new RestRequest(Method.POST);
      request.AddHeader("content-type", "application/json");
      request.AddHeader("authorization", "Bearer {yourMgmtApiAccessToken}");
      request.AddParameter("application/json", "{ \"user_id\": \"{userIdOfVerifyEmailRecipient}\", \"client_id\": \"{yourAppClientId}\",\"identity\": {\"user_id\": \"5457edea1b8f22891a000004\",\"provider\": \"google-oauth2\"}, \"organization_id\": \"{yourOrganizationId}\" }", ParameterType.RequestBody);
      IRestResponse response = client.Execute(request);
      ```

      ```go Go lines expandable
      package main

      import (
      	"fmt"
      	"strings"
      	"net/http"
      	"io/ioutil"
      )

      func main() {

      	url := "https://{yourDomain}/api/v2/jobs/verification-email"

      	payload := strings.NewReader("{ \"user_id\": \"{userIdOfVerifyEmailRecipient}\", \"client_id\": \"{yourAppClientId}\",\"identity\": {\"user_id\": \"5457edea1b8f22891a000004\",\"provider\": \"google-oauth2\"}, \"organization_id\": \"{yourOrganizationId}\" }")

      	req, _ := http.NewRequest("POST", url, payload)

      	req.Header.Add("content-type", "application/json")
      	req.Header.Add("authorization", "Bearer {yourMgmtApiAccessToken}")

      	res, _ := http.DefaultClient.Do(req)

      	defer res.Body.Close()
      	body, _ := ioutil.ReadAll(res.Body)

      	fmt.Println(res)
      	fmt.Println(string(body))

      }
      ```

      ```java Java lines
      HttpResponse<String> response = Unirest.post("https://{yourDomain}/api/v2/jobs/verification-email")
        .header("content-type", "application/json")
        .header("authorization", "Bearer {yourMgmtApiAccessToken}")
        .body("{ \"user_id\": \"{userIdOfVerifyEmailRecipient}\", \"client_id\": \"{yourAppClientId}\",\"identity\": {\"user_id\": \"5457edea1b8f22891a000004\",\"provider\": \"google-oauth2\"}, \"organization_id\": \"{yourOrganizationId}\" }")
        .asString();
      ```

      ```javascript Node.JS lines
      var axios = require("axios").default;

      var options = {
        method: 'POST',
        url: 'https://{yourDomain}/api/v2/jobs/verification-email',
        headers: {
          'content-type': 'application/json',
          authorization: 'Bearer {yourMgmtApiAccessToken}'
        },
        data: {
          user_id: '{userIdOfVerifyEmailRecipient}',
          client_id: '{yourAppClientId}',
          identity: {user_id: '5457edea1b8f22891a000004', provider: 'google-oauth2'},
          organization_id: '{yourOrganizationId}'
        }
      };

      axios.request(options).then(function (response) {
        console.log(response.data);
      }).catch(function (error) {
        console.error(error);
      });
      ```

      ```obj-c Obj-C lines expandable
      #import <Foundation/Foundation.h>

      NSDictionary *headers = @{ @"content-type": @"application/json",
                                 @"authorization": @"Bearer {yourMgmtApiAccessToken}" };
      NSDictionary *parameters = @{ @"user_id": @"{userIdOfVerifyEmailRecipient}",
                                    @"client_id": @"{yourAppClientId}",
                                    @"identity": @{ @"user_id": @"5457edea1b8f22891a000004", @"provider": @"google-oauth2" },
                                    @"organization_id": @"{yourOrganizationId}" };

      NSData *postData = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:nil];

      NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://{yourDomain}/api/v2/jobs/verification-email"]
                                                             cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                         timeoutInterval:10.0];
      [request setHTTPMethod:@"POST"];
      [request setAllHTTPHeaderFields:headers];
      [request setHTTPBody:postData];

      NSURLSession *session = [NSURLSession sharedSession];
      NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                                  completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                      if (error) {
                                                          NSLog(@"%@", error);
                                                      } else {
                                                          NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                                          NSLog(@"%@", httpResponse);
                                                      }
                                                  }];
      [dataTask resume];
      ```

      ```php PHP lines expandable
      $curl = curl_init();

      curl_setopt_array($curl, [
        CURLOPT_URL => "https://{yourDomain}/api/v2/jobs/verification-email",
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "POST",
        CURLOPT_POSTFIELDS => "{ \"user_id\": \"{userIdOfVerifyEmailRecipient}\", \"client_id\": \"{yourAppClientId}\",\"identity\": {\"user_id\": \"5457edea1b8f22891a000004\",\"provider\": \"google-oauth2\"}, \"organization_id\": \"{yourOrganizationId}\" }",
        CURLOPT_HTTPHEADER => [
          "authorization: Bearer {yourMgmtApiAccessToken}",
          "content-type: application/json"
        ],
      ]);

      $response = curl_exec($curl);
      $err = curl_error($curl);

      curl_close($curl);

      if ($err) {
        echo "cURL Error #:" . $err;
      } else {
        echo $response;
      }
      ```

      ```python Python lines
      import http.client

      conn = http.client.HTTPSConnection("")

      payload = "{ \"user_id\": \"{userIdOfVerifyEmailRecipient}\", \"client_id\": \"{yourAppClientId}\",\"identity\": {\"user_id\": \"5457edea1b8f22891a000004\",\"provider\": \"google-oauth2\"}, \"organization_id\": \"{yourOrganizationId}\" }"

      headers = {
          'content-type': "application/json",
          'authorization': "Bearer {yourMgmtApiAccessToken}"
          }

      conn.request("POST", "/{yourDomain}/api/v2/jobs/verification-email", payload, headers)

      res = conn.getresponse()
      data = res.read()

      print(data.decode("utf-8"))
      ```

      ```ruby Ruby lines
      require 'uri'
      require 'net/http'
      require 'openssl'

      url = URI("https://{yourDomain}/api/v2/jobs/verification-email")

      http = Net::HTTP.new(url.host, url.port)
      http.use_ssl = true
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE

      request = Net::HTTP::Post.new(url)
      request["content-type"] = 'application/json'
      request["authorization"] = 'Bearer {yourMgmtApiAccessToken}'
      request.body = "{ \"user_id\": \"{userIdOfVerifyEmailRecipient}\", \"client_id\": \"{yourAppClientId}\",\"identity\": {\"user_id\": \"5457edea1b8f22891a000004\",\"provider\": \"google-oauth2\"}, \"organization_id\": \"{yourOrganizationId}\" }"

      response = http.request(request)
      puts response.read_body
      ```

      ```swift Swift lines expandable
      import Foundation

      let headers = [
        "content-type": "application/json",
        "authorization": "Bearer {yourMgmtApiAccessToken}"
      ]
      let parameters = [
        "user_id": "{userIdOfVerifyEmailRecipient}",
        "client_id": "{yourAppClientId}",
        "identity": [
          "user_id": "5457edea1b8f22891a000004",
          "provider": "google-oauth2"
        ],
        "organization_id": "{yourOrganizationId}"
      ] as [String : Any]

      let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

      let request = NSMutableURLRequest(url: NSURL(string: "https://{yourDomain}/api/v2/jobs/verification-email")! as URL,
                                              cachePolicy: .useProtocolCachePolicy,
                                          timeoutInterval: 10.0)
      request.httpMethod = "POST"
      request.allHTTPHeaderFields = headers
      request.httpBody = postData as Data

      let session = URLSession.shared
      let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
        if (error != nil) {
          print(error)
        } else {
          let httpResponse = response as? HTTPURLResponse
          print(httpResponse)
        }
      })

      dataTask.resume()
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Rules">
    Using [Auth0 Rules](/docs/customize/rules), you can call your API when a user logs in for the first time with an email address that has not been verified. After calling your API, add a flag to the user's profile [metadata](/docs/manage-users/user-accounts/metadata) that indicates that the verification email has been sent:

    ```javascript lines expandable
    function (user, context, callback) {

      const request = require('request');

      user.user_metadata = user.user_metadata || {};
      if (user.email_verified || user.user_metadata.verification_email_sent) {
        return callback(null, user, context);
      }

      request.post({
        url: 'https://yourapi.yourcompany.com/mail/verification',
        json: {
          user: user,
          context: context,
          secretToken: configuration.MY_SECRET_TOKEN,
        },
        timeout: 5000
      }, function(err, response, body){
        if (err)
          return callback(new Error(err));

        // Email sent flag persisted in the user's profile.
        user.user_metadata.verification_email_sent = true;
        auth0.users.updateUserMetadata(user.user_id, user.user_metadata)
          .then(function() {
            callback(null, user, context);
          })
          .catch(function(err) {
            callback(err);
          });
        return callback(null, user, context);
      });
    }
    ```
  </Tab>
</Tabs>

### Require verified email for login

You can require users to verify their email before logging in with a rule:

```javascript lines
function (user, context, callback) {
  if (!user.email_verified) {
    return callback(new UnauthorizedError('Please verify your email before logging in.'));
  } else {
    return callback(null, user, context);
  }
}
```

### Custom redirects

A custom redirect is useful when you want to direct users to certain URLs based on user attributes or on the tenant. The Auth0 Management API provides a [Create Email Verification Ticket endpoint](https://auth0.com/docs/api/management/v2/#!/Tickets/post_email_verification) that generates the verification link for each user. This endpoint allows you to specify the `result_url` to which users will be redirected after they have validated their email address by clicking the link in the verification email.

We recommend AllowList the URL in the [Auth0 Dashboard](https://manage.auth0.com/#/templates/provider). For details, see [Add Addresses to AllowList](/docs/secure/security-guidance/data-security/allowlist).

## Welcome emails

A welcome email is sent to users once they have verified their email address.

### Send welcome email using your own API

Using a [rule](/docs/customize/rules), you can call your API to send a welcome email only if the user's email address has been verified and the email has not been sent previously.

```javascript lines
function (user, context, callback) {

  const request = require('request');

  if (!user.email_verified || user.welcome_email_sent) {
    return callback(null, user, context);
  }

  request.post({
    url: 'https://yourapi.yourcompany.com/mail/welcome',
    json: {
      user: user,
      context: context,
      secretToken: configuration.MY_SECRET_TOKEN,
    },
    timeout: 5000
  }, function(err, response, body){
    if (err)
      return callback(new Error(err));

    // Email sent flag persisted in the user's profile.
    user.app_metadata.welcome_email_sent = true;
    return callback(null, user, context);
  });
}
```

## Password reset emails

You can create a password change ticket using the Auth0 Management API [Create a Password Change Ticket](https://auth0.com/docs/api/management/v2/#!/Tickets/post_password_change) endpoint, and then send the password change ticket URL in an email to the user. When the user clicks the link, they will be prompted to reset their password through the <Tooltip tip="Universal Login: Your application redirects to Universal Login, hosted on Auth0's Authorization Server, to verify a user's identity." cta="View Glossary" href="/docs/glossary?term=Universal+Login">Universal Login</Tooltip> flow.

## Learn more

* [Customize Email Templates](/docs/customize/email/email-templates)
* [Configure External SMTP Email Providers](/docs/customize/email/smtp-email-providers)
