Send email with Auth0 Actions when a user registers, logs in, changes a password, or reaches another identity event in your application. This guide uses an Auth0 Action, Axios, and Volanea’s REST API to send one copy-pasteable transactional welcome email.
What you will build
You will create an Auth0 Post User Registration Action that calls Volanea after a user is created through a Database or Passwordless connection. The Action reads a Volanea API key from an Auth0 Action secret, builds a message from the Auth0 event object, and sends the message through POST /v1/send.
The completed integration sends a welcome email with:
- a verified sender address on your domain;
- the newly registered user’s email address as the recipient;
- both HTML and plain-text content;
- an idempotency key so a retry does not create a duplicate logical send; and
- error logging that is useful during setup without printing your API key.
Volanea’s single-message endpoint is POST /v1/send, uses the https://api.volanea.com base URL, accepts a secret key, and supports an Idempotency-Key header for safe retries. (volanea.com)
This is an Action-based integration, not an Auth0 built-in email-provider setup. Use it for product emails you want to send in response to an Auth0 event, such as a welcome message, security notification, account-owner alert, or onboarding reminder. Do not replace Auth0’s own verification, password-reset, or MFA-provider configuration with an Action unless you have deliberately designed and tested that flow.
Why use an Auth0 Action for transactional email
Auth0 Actions are tenant-specific Node.js functions that run at defined points in an Auth0 flow. They use an asynchronous, promise-based programming model and can call external HTTP APIs. (auth0.com) That makes an Action a practical place to notify a user or an internal system after an identity event.
For this guide, the trigger is Post User Registration. Auth0 runs that trigger after a user is added through a Database or Passwordless connection. It is specifically intended for follow-up tasks such as notifying another system that a user registered. (auth0.com)
A welcome email is a good fit because it is supplementary. If the email service is temporarily unavailable, the signup itself should still be allowed to complete. Auth0 documents Post User Registration Actions as non-blocking: the pipeline continues without waiting for the Action’s outcome, so an Action failure does not alter the registration transaction. (auth0.com)
Choose the trigger intentionally
Before copying the sample, decide which event should create the email:
| Goal | Suggested Auth0 trigger | Important behavior |
|---|---|---|
| Send a welcome email once after signup | Post User Registration | Non-blocking; best for supplementary onboarding messages |
| Send a login-security notice | Post Login | Blocking; keep outbound work quick and fail safely |
| Block signup until an external policy check completes | Pre User Registration | Blocking; not appropriate for ordinary welcome email sending |
| Send Auth0 system emails such as password resets | Auth0 email-provider configuration | Use the provider setup intended for Auth0-managed templates |
A Post Login Action is not interchangeable with a registration Action. It runs after successful authentication and can also execute when a refresh token is exchanged, so naïvely sending an email from it can create unexpected repeat sends. Auth0 documents Post Login as a blocking flow, which is another reason to keep external calls minimal and carefully scoped. (auth0.com)
Before you start
You need the following before deploying the Action:
- An Auth0 tenant with a Database or Passwordless connection that can create users.
- A Volanea secret API key. Keep it server-side; never put it in browser code, a mobile app, or a public repository.
- A verified sending domain in Volanea and a From address that belongs to that domain, such as
welcome@updates.example.com. - A test recipient address you can access.
- Permission to create an Auth0 Action and bind it to the Post User Registration flow.
Use a real domain you control in the From address. Replace every example.com value in this guide with your own authenticated domain before testing. Authentication-related messages are especially sensitive to sender alignment and reputation: a valid API request only means the provider accepted the message, not that a recipient mailbox will place it in the inbox.
Keep the API key in an Auth0 secret
The Volanea key is a credential, not application configuration. Store it in the Auth0 Action’s Secrets area under this exact name:
VOLANEA_API_KEY
The code accesses it through event.secrets.VOLANEA_API_KEY. Do not hard-code an sk_... key in the Action source. Auth0’s own guidance for Actions uses secrets to keep tokens out of source code, and the Action editor provides a dedicated Secrets control for this purpose. (auth0.com)
Use separate secrets or separate Actions for test and production environments. A clean separation prevents an Action in a development tenant from accidentally sending production-branded messages, and it makes key rotation less risky.
Decide on a sender identity
This tutorial uses:
Acme <welcome@updates.example.com>
Change both the display name and address. The mailbox domain must be one that you have authenticated in Volanea. Keep the sender stable for a given class of mail. For example, send welcome messages from welcome@, receipts from receipts@, and security alerts from security@ only if those addresses are part of a coherent and authenticated sending policy.
Install the dependency for Auth0 Actions
The sample uses Axios because it provides a concise HTTP client and exposes helpful response data when Volanea returns an API error.
Install Axios in a local Action project with:
npm install axios
Auth0 Actions execute in Auth0’s managed runtime, so you do not upload a local node_modules directory. In the Auth0 Dashboard, add the same dependency to the Action:
- Go to Actions > Library.
- Create or open your Action.
- In the Action code editor, select Modules using the cube icon.
- Select Add Module.
- Enter
axiosand add it.
Auth0 supports public npm registry dependencies in Actions. Its documented example uses Axios with const axios = require('axios');; native modules and private registry packages are not supported. (auth0.com)
Why both the command and dashboard step matter
The npm install axios command is useful if you keep the Action source in a local repository, lint it, or test it before pasting or deploying it. The Auth0 Action runtime, however, resolves dependencies from the module list configured for that Action. If you run the command locally but do not add Axios in the Auth0 editor, the deployed Action will fail with a module-resolution error.
For a small Action, avoid adding a dependency merely for convenience. Every dependency adds code, upgrade responsibility, and potential supply-chain risk. Axios is used here because it is explicitly supported in Auth0’s documented dependency workflow and makes error handling easy to read. For a larger identity integration, pin and review dependency versions according to your organization’s dependency policy.
Configure the Action
Create the Action before pasting code so you can add the module and secret in the same place.
Create an Action in the Auth0 Dashboard
In Auth0:
- Open Actions > Library.
- Select Build Custom.
- Give the Action a clear name, such as
Send Volanea Welcome Email. - Choose the Post User Registration trigger.
- Create the Action.
- Add the
axiosmodule as described above. - Open Secrets using the key icon.
- Add
VOLANEA_API_KEYand paste your Volanea secret key as its value. - Replace the starter code with the full sample in the next section.
The Action must later be deployed and bound to its trigger before it runs. Creating or editing source code alone is not enough to make it active. (auth0.com)
Use a verified From address
Set FROM_EMAIL in the sample to an address on your Volanea-verified domain. Do not use the user’s email address as from. If you need replies to go somewhere else, configure that only through documented API fields after confirming the field in the email API reference and setup guides. The minimal request below deliberately uses only standard transactional fields: sender, recipient, subject, HTML, plain text, authorization, and JSON content type.
Complete Auth0 Action code sample
Paste this entire sample into an Action configured for the Post User Registration trigger. It requires the axios module and a secret named VOLANEA_API_KEY.
const axios = require('axios');
const crypto = require('crypto');
const VOLANEA_SEND_URL = 'https://api.volanea.com/v1/send';
const FROM_EMAIL = 'Acme <welcome@updates.example.com>';
exports.onExecutePostUserRegistration = async (event) => {
const apiKey = event.secrets.VOLANEA_API_KEY;
const recipient = event.user && event.user.email;
if (!apiKey) {
console.log('Volanea email not sent: VOLANEA_API_KEY is missing.');
return;
}
if (!recipient) {
console.log('Volanea email not sent: registered user has no email address.');
return;
}
const firstName = event.user.given_name || event.user.name || 'there';
const userId = event.user.user_id || recipient;
const message = {
from: FROM_EMAIL,
to: recipient,
subject: 'Welcome to Acme',
text: `Hi ${firstName},\n\nThanks for creating your Acme account. You can now sign in and get started.\n\nIf you did not create this account, contact support.`,
html: `<p>Hi ${escapeHtml(firstName)},</p><p>Thanks for creating your Acme account. You can now sign in and get started.</p><p>If you did not create this account, contact support.</p>`
};
const idempotencyKey = crypto
.createHash('sha256')
.update(`welcome-email:${userId}`)
.digest('hex');
try {
const response = await axios.post(VOLANEA_SEND_URL, message, {
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey
},
timeout: 8000,
validateStatus: () => true
});
if (response.status < 200 || response.status >= 300) {
console.log(
`Volanea email request failed with status ${response.status}: ${safeErrorBody(response.data)}`
);
return;
}
console.log(`Volanea welcome email accepted for ${recipient}.`);
} catch (error) {
if (error.code === 'ECONNABORTED') {
console.log('Volanea email request timed out.');
return;
}
console.log(`Volanea email request could not be completed: ${error.message}`);
}
};
function escapeHtml(value) {
return String(value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/'/g, ''')
.replace(/\"/g, '"');
}
function safeErrorBody(data) {
if (!data) return 'No response body.';
try {
return JSON.stringify(data).slice(0, 1000);
} catch (_) {
return 'Response body could not be serialized.';
}
}
What the code does
The code uses CommonJS because Auth0 Actions run Node.js code and Auth0’s dependency examples use require. It exports the function name required by the Post User Registration trigger: onExecutePostUserRegistration.
It first checks for the API key and recipient. A user created in an unusual flow may not have an email address available, and sending a request with an empty recipient would only create noise in your logs. Returning early is safer than constructing an invalid transactional send.
The message includes both text and html. HTML supplies the branded experience, while plain text gives recipients and mail clients a usable alternative. Keep the two versions semantically equivalent: do not place a critical link, expiry time, or security instruction only in HTML.
The escapeHtml function is important because profile data is external input. A name containing characters such as < or & should be encoded before interpolation into HTML. Escaping does not sanitize every possible piece of arbitrary markup for every use case, but it is appropriate for placing a user name into a simple HTML text node.
The request includes Content-Type: application/json because the message is a JSON object. It also includes Authorization: Bearer ... with the Volanea secret key and sends to Volanea’s documented REST API base URL and send endpoint. (volanea.com)
Prevent duplicate welcome emails with idempotency
Network delivery is not binary from your application’s perspective. An HTTP request can reach Volanea and be accepted, while the connection back to Auth0 times out. If code retries the request with no deduplication mechanism, the user may receive two welcome messages.
Volanea supports the Idempotency-Key header for its single-message send endpoint. (volanea.com) The sample derives a deterministic SHA-256 value from the user identity and the message purpose:
const idempotencyKey = crypto
.createHash('sha256')
.update(`welcome-email:${userId}`)
.digest('hex');
That means every retry for the same user and same welcome-email purpose uses the same key. A different message type should use a different prefix, for example security-alert:${event.user.user_id}:${loginId}. The prefix matters because an idempotency key identifies one logical operation, not an entire user forever.
Use a stable event identifier when available
For this welcome-email case, the user ID is appropriate because a Post User Registration Action runs after user creation and the intention is one welcome email per user. For repeatable events, such as a login alert or invoice receipt, derive the key from the unique event or invoice ID instead.
Do not generate a new random key inside retry logic. A new key tells the email API that the request is a new operation, which defeats deduplication. Generate one key for the logical email, persist or deterministically reproduce it, and reuse that same value only when retrying that one logical send.
Deploy, bind, and test the Action
After saving the source, select Deploy in the Auth0 Action editor. Deployment creates an immutable Action version. If the Action is already bound, Auth0 will execute the newly deployed version; otherwise, bind it to the flow after deployment. (auth0.com)
Then bind the Action:
- Go to Actions > Triggers.
- Open the Post User Registration flow.
- Drag
Send Volanea Welcome Emailinto the flow. - Apply the flow changes.
- Create a new user through the intended Database or Passwordless signup path.
Use a new test user each time. Existing users do not trigger Post User Registration merely because they log in again.
Verify the result in the right order
When a test does not arrive, separate the problem into stages:
- Did Auth0 invoke the Action? Check the Action execution logs for the registration event and any message logged by the code.
- Did the Action call Volanea? Look for
Volanea welcome email acceptedor an HTTP status error in the Action logs. - Did Volanea accept the message? Check the send activity in Volanea using the message details and timestamps.
- Did the recipient mailbox receive it? Check inbox, spam, mail rules, and the full recipient address.
- Was it delivered or suppressed later? Review Volanea delivery events and suppression information before attempting blind retries.
This ordering prevents a common debugging mistake: changing DNS or rewriting the email body when the Action was never bound to the trigger in the first place.
Production considerations for Auth0 email sending
The sample is deliberately small, but production email behavior deserves more thought than a successful HTTP response.
Keep the Action fast
Auth0 recommends minimizing HTTP requests and using a reasonable timeout below 10 seconds in Actions to avoid accumulated delays. (dev.auth0.com) The sample uses one request with an 8-second timeout. Do not call Volanea once to look up a user, again to render content, and again to send if your application can assemble the necessary content locally.
For a Post User Registration Action, the flow is non-blocking, but a hung outbound dependency still creates operational noise and makes debugging harder. For a blocking trigger such as Post Login, slow external calls can directly affect authentication latency. Avoid sending email from a blocking trigger unless the notification is worth the latency and you have a clear failure policy.
Log safely
The sample logs status codes and a truncated response body on non-success responses. It intentionally does not log the API key. In sensitive identity flows, avoid logging password-reset URLs, access tokens, authentication assertions, or full user-profile objects.
A recipient email address is personal data. Decide whether your organization permits it in Auth0 logs, how long those logs are retained, and who can access them. If you need a correlation value, log a hashed user ID or a provider message identifier rather than the entire event object.
Treat acceptance and delivery as distinct states
A successful API response tells you that the sending service accepted the request. It does not prove that the recipient’s server accepted the message, that it reached the inbox, or that a human read it. Build any downstream business process around the correct state.
For example, do not mark onboarding complete simply because the welcome email API request returned success. The user may already be signed in, the email may bounce, or the provider may suppress it due to a prior unsubscribe or complaint. Use delivery-event data where it matters, and keep your product flow functional even when email is delayed.
Keep security-critical communication separate
A welcome email is informational. A password reset, email verification, or MFA challenge is part of an authentication process and has a different reliability and security profile. Ensure these messages have short-lived, single-use links where applicable, clear sender identities, and correct Auth0 provider configuration.
Do not put secrets, password-reset tokens, or raw identity-provider data into custom HTML. If your Action needs to include a link to your product, construct it from a trusted configured base URL rather than from request-controlled headers or profile metadata.
Common errors
Cannot find module 'axios'
Cause: Axios was installed locally but was not added to the Action in Auth0, or the module name was entered incorrectly.
Fix: In Actions > Library, open the Action, select Modules, choose Add Module, and add axios. The Action runtime does not use your laptop’s node_modules folder. Auth0 Actions can use public npm packages configured in the Action, but native modules and private registry modules are unsupported. (auth0.com)
Authentication failure or HTTP 401/403
Cause: VOLANEA_API_KEY is absent, pasted with extra whitespace, revoked, from the wrong environment, or sent without the expected Bearer authorization format.
Fix: Open the Action’s Secrets panel and confirm the secret is named exactly VOLANEA_API_KEY. Replace the value with an active Volanea secret key, redeploy the Action, and ensure the request header remains:
Authorization: `Bearer ${apiKey}`
Never paste the key into Action code to test it. That exposes it to source viewers and version history.
HTTP 400 or 415 caused by wrong content type
Cause: The request body is sent as form data, a string with malformed JSON, or without Content-Type: application/json.
Fix: Pass the JavaScript message object directly as Axios’s second argument and set the content type explicitly:
await axios.post(VOLANEA_SEND_URL, message, {
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
Do not use URLSearchParams or application/x-www-form-urlencoded for this JSON send request.
The Action finishes before the send completes
Cause: The HTTP request was started without await, or a promise callback was used without returning or awaiting it.
Fix: Keep the Action handler async and await the send:
const response = await axios.post(VOLANEA_SEND_URL, message, options);
This is especially important for readable error handling. Without await, a rejected promise may not be handled where you expect, and the Action can report confusing behavior.
The Action never runs
Cause: The Action was saved but not deployed, deployed but not bound to the Post User Registration flow, or tested by logging in as an existing user.
Fix: Deploy the Action, bind it under Actions > Triggers > Post User Registration, apply the flow changes, and create a new user through a Database or Passwordless connection. Auth0 requires an Action to be deployed and bound before it executes as part of a flow. (auth0.com)
The recipient is blank or undefined
Cause: The selected event does not provide event.user.email, or the user profile was created without an email address.
Fix: Preserve the early-return guard in the sample. If your identity design supports users without email addresses, choose another notification channel or collect and verify email before attempting to send.
The API accepts the send but no message arrives
Cause: The From domain is not fully authenticated, the From address does not align with the authenticated domain, the recipient address is invalid, the message was filtered, or the recipient is suppressed.
Fix: Confirm the exact sender domain in Volanea, inspect send and delivery events, check the recipient mailbox’s spam and rules, and do not continually retry the same message. Repeated retries to an invalid or suppressed recipient make deliverability worse rather than better.
Duplicate messages after a retry
Cause: Retries use a new idempotency key, or the message is sent from more than one trigger or Action.
Fix: Reuse the same idempotency key for the same logical welcome email and audit the bound Actions. The sample’s deterministic hash uses the Auth0 user ID so repeats for that welcome-email purpose share one key.
Next steps: webhooks and templates
Once the basic send works, add operational visibility before adding more message types.
Process email webhooks
Webhooks let your application receive event notifications after a message is accepted and processed, such as delivery outcomes, bounces, complaints, or other provider events supported by your configuration. Build a small server endpoint that verifies incoming webhook authenticity, records the event, and updates only the business state that truly depends on that event.
For example, webhook processing can help you stop sending onboarding nudges to a hard-bounced address, create an internal support task for a complaint, or investigate a recurring delivery failure. Make your webhook handler idempotent too: providers can retry delivery of a webhook when your endpoint is unavailable.
Do not route webhooks to an Auth0 Action. Use an application endpoint you control, acknowledge requests quickly, and process heavier work asynchronously.
Move repeated content into templates
The sample keeps content in code so you can validate the full HTTP request quickly. As the number of emails grows, reusable templates reduce duplicated HTML and make content changes safer. Volanea supports reusable templates addressed by templateId, allowing a send to reference template content instead of carrying all markup in every request. (volanea.com)
Use templates for messages with stable layouts, such as welcome emails, receipts, password-change notices, and organization invitations. Keep transactional decisions in the Action or application code: who should receive a message, whether they are eligible, and what event is being communicated. Keep presentation in the template where practical.
Before adding templates or webhooks, review the Volanea API documentation for the current request schemas, authentication requirements, event payloads, and verification guidance.
FAQ
Can I send an email with Auth0 Actions without Axios?
Yes. An Action can call an external REST API using an available HTTP client, but this guide uses Axios because Auth0 documents it as an Action dependency example and it keeps the request and error handling concise. If you remove Axios, update both the code and dependency configuration together.
Is Post User Registration the right trigger for a welcome email?
Usually, yes. It runs after a Database or Passwordless user is created and is non-blocking, which suits a supplementary welcome message. It will not run just because an existing user logs in later. (auth0.com)
Should I use an Action for Auth0 password-reset emails?
Generally, configure Auth0’s email-provider and email-template settings for Auth0-managed password resets and verification messages. Use Actions for custom transactional messages that complement your identity flow, rather than trying to recreate security-critical system emails.
Why does this example include an idempotency key?
A timeout can leave your code uncertain whether the provider accepted a request. An idempotency key identifies one logical email operation so a retry can be handled safely instead of creating an accidental duplicate. Volanea documents support for Idempotency-Key on the single-message send endpoint. (volanea.com)
Can I put the Volanea API key in frontend code?
No. The key authorizes email sending and must remain on trusted server-side infrastructure. Auth0 Actions are server-side tenant functions, and an Action secret keeps the value out of your source code and client bundle.