If you need to send email from Netlify, do not put an email API key in browser code or assume a client-side form can safely call an email provider. A reliable implementation uses Netlify Forms as the trigger, an outgoing webhook as the handoff, and a Netlify Function to make the authenticated Volanea API request.
This pattern is useful for contact-form acknowledgements, lead notifications, application confirmations, download links, and other transactional messages that should be sent immediately after a visitor submits a form. It deliberately does not rely on a native Netlify app or marketplace integration: Volanea does not ship one. Instead, Netlify's Forms webhook capability supplies the event and a server-side function supplies the protected HTTP client.
The architecture: Forms submission to email API
The route has three distinct jobs. Keeping them separate is what prevents a convenient form integration from becoming an API-key leak or a duplicate-email machine.
- A visitor submits a form that Netlify has detected and is handling as a Netlify Form.
- Netlify records the submission and posts its submission payload to the outgoing-webhook URL configured for that form.
- A Netlify Function receives that webhook, validates and maps the submission fields, then calls Volanea over HTTPS using an environment variable stored only on the server.
In shorthand, the flow is:
Visitor -> Netlify Form -> Netlify outgoing webhook -> Netlify Function -> Volanea REST API -> recipient inbox
The concrete trigger is a Netlify Forms submission. It is not a deploy event, and it is not a browser-side API call. Netlify first accepts a successful form submission; its configured outgoing webhook notification then delivers data about that submission to your function endpoint.
That distinction matters. If a visitor closes the tab after submitting, Netlify can still process the submission and initiate the notification. Conversely, if the Volanea call fails, the original form record remains useful for investigation and replay rather than disappearing with a failed browser request.
What you need before connecting Netlify and Volanea
Prepare the sending side before you configure the event. An integration is only as dependable as the sender identity and data contract behind it.
A verified sender and a clear message purpose
Use a From address on a domain you control and have authenticated for Volanea. For example, Acme Support <support@updates.example.com> is a better operational sender than a personal mailbox or an address copied from a visitor's form input.
The visitor's address belongs in the recipient field for an acknowledgement or in reply_to when your team is receiving an internal notification. Do not use an untrusted submitted address as the From address. That can create spoofing problems, fail alignment checks, and make replies behave unpredictably.
Before adding code, decide which message you are sending:
- Submitter acknowledgement: sent to
data.email, confirming the submission and setting expectations for a reply. - Internal alert: sent to a fixed support or sales inbox, containing the submitted details.
- Both messages: a confirmation to the submitter and a separate internal notification, preferably treated as two independently idempotent sends.
For a production sender, authenticate the sending domain with its required SPF and DKIM DNS records and consider DMARC policy and alignment. The API call can return success while a weakly authenticated sender later has inbox-placement problems.
A Netlify Form with stable field names
Netlify identifies form data using the names in your HTML inputs. Field names are an API contract in this setup, so choose durable lowercase names such as name, email, company, and message rather than presentation-oriented names that may change during a redesign.
A minimal static form can look like this:
<form name="contact" method="POST" data-netlify="true">
<input type="hidden" name="form-name" value="contact" />
<label>
Name
<input name="name" type="text" autocomplete="name" required />
</label>
<label>
Email
<input name="email" type="email" autocomplete="email" required />
</label>
<label>
Message
<textarea name="message" required></textarea>
</label>
<button type="submit">Send message</button>
</form>
Netlify needs to detect the form during the site build. Framework-specific rendering can affect detection, particularly when a form exists only after client-side JavaScript runs. Check that the form appears in the deployed HTML or follow Netlify's framework guidance for form detection before debugging the webhook.
A Volanea API key with a narrow operational role
Create a Volanea API key for the environment that will send these messages. Use separate keys for preview, staging, and production where your operational model permits it. That makes it possible to disable a test integration without interrupting production mail.
Keep the Volanea key in Netlify environment variables, not in a repository file and not in a public build variable. The function reads it at runtime through process.env; browser JavaScript never receives it.
For endpoint details, authentication conventions, sender requirements, and the current email request schema, consult the Volanea API reference and setup guides as you implement the request below.
Configure the Netlify Forms outgoing webhook
Netlify Forms can send an outgoing webhook notification after a form submission. Configure that notification for the specific contact form and use the public URL of your deployed function as the destination.
For a function file named netlify/functions/form-submission-email.mjs, Netlify exposes the default function route at:
https://your-site.netlify.app/.netlify/functions/form-submission-email
A custom domain can be used too, as long as it resolves to the deployed site and HTTPS is working. Set that URL as the outgoing-webhook target in the form's notification configuration.
Do this only after the function has been deployed at least once. A common setup failure is entering a local URL, a branch URL that later disappears, or a route that is protected by browser-oriented authentication. Netlify's notification service needs to reach the endpoint over the public internet.
Add a webhook secret to the URL
A webhook endpoint is public by necessity, but it should not accept arbitrary posts as legitimate form events. If your notification configuration does not provide a request-signing mechanism you can verify, append an unpredictable secret as a query parameter:
https://your-site.netlify.app/.netlify/functions/form-submission-email?token=LONG_RANDOM_VALUE
Store the same value as NETLIFY_FORM_WEBHOOK_TOKEN in Netlify environment variables. This is not a substitute for transport security—HTTPS is still essential—but it stops casual unsolicited requests from triggering mail.
Treat the complete webhook URL as sensitive configuration. A secret in a query string can be exposed in configuration exports, request logs, or support screenshots. Rotate it if it is disclosed, and do not place it in client-side source code.
Test with a real submission
Use the deployed form to create a genuine submission. A direct curl request can verify the function code, but it does not prove that Netlify detected the form, created the submission, or sent the notification payload you expect.
During the first test, inspect function logs and compare the received JSON against your mappings. Log the submission ID and form name, but avoid logging the full message body or email address in long-lived production logs unless you have a defined privacy and retention policy.
The payload Netlify delivers for a form submission
Netlify Forms webhook notifications use a JSON submission representation. The useful submission fields are the submission ID, site and form identifiers, creation timestamp, and the data object containing fields posted by the visitor. In webhook deliveries, that submission is commonly carried beneath a payload wrapper.
A representative delivery for the HTML form above is:
{
"payload": {
"id": "65c5d31b-9e6a-4ff3-b95e-45d4ac18ca21",
"site_url": "https://your-site.netlify.app",
"form_id": "c4c07c1c-83e3-4d41-a1c1-9a91d9b90f55",
"form_name": "contact",
"data": {
"name": "Avery Chen",
"email": "avery@example.net",
"message": "Could you send a quote for a team plan?"
},
"created_at": "2026-09-22T14:03:19.000Z"
}
}
Do not treat every field beyond the core submission information as guaranteed business data. data reflects your actual inputs, so an optional input may be absent, blank, or an array depending on the control type. File-upload and spam-protection behavior should also be handled according to the form features you have enabled rather than assumed to behave like an ordinary text field.
The example function below accepts both the wrapped form (event.payload) and an unwrapped submission object. That small amount of defensive parsing makes local tests simpler while preserving the production mapping.
Map fields deliberately rather than forwarding everything
The mapping for an acknowledgement is straightforward:
| Netlify submission path | Volanea email role |
|---|---|
payload.data.email | recipient address |
payload.data.name | recipient display name and safe greeting text |
payload.data.message | escaped message excerpt in the acknowledgement, if needed |
payload.id | idempotency key and diagnostic reference |
payload.form_name | route guard and message context |
payload.created_at | optional confirmation detail or diagnostic data |
The key rule is that form text is untrusted input. Escape it before inserting it into HTML. A form submission is not a trusted CMS record merely because Netlify accepted it.
Build the Netlify Function that calls Volanea
Create netlify/functions/form-submission-email.mjs. Netlify Functions run on the server, so this is the appropriate place to use the Volanea credential and perform the outbound HTTPS request.
The following example sends an acknowledgement to the submitter. It verifies the URL token, accepts only the intended form, validates the email field, escapes submitted text for HTML, and maps the Netlify payload to a Volanea email-send request.
// netlify/functions/form-submission-email.mjs
function escapeHtml(value = "") {
return String(value)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function isPlausibleEmail(value) {
return typeof value === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
export default async (request) => {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const url = new URL(request.url);
if (url.searchParams.get("token") !== process.env.NETLIFY_FORM_WEBHOOK_TOKEN) {
return new Response("Unauthorized", { status: 401 });
}
let event;
try {
event = await request.json();
} catch {
return new Response("Expected JSON", { status: 400 });
}
// Netlify Forms outgoing-webhook deliveries carry the submission in payload.
const submission = event.payload ?? event;
const data = submission.data ?? {};
if (submission.form_name !== "contact") {
return new Response("Ignored form", { status: 202 });
}
const recipient = typeof data.email === "string" ? data.email.trim() : "";
if (!isPlausibleEmail(recipient)) {
return new Response("Submission has no valid email", { status: 422 });
}
const name = typeof data.name === "string" ? data.name.trim() : "there";
const message = typeof data.message === "string" ? data.message.trim() : "";
const submissionId = String(submission.id ?? "unknown-submission");
const emailPayload = {
from: "Acme Support <support@updates.example.com>",
to: [recipient],
reply_to: "support@example.com",
subject: "We received your message",
html: `
<p>Hi ${escapeHtml(name)},</p>
<p>Thanks for contacting Acme. Our team received your message and will reply soon.</p>
${message ? `<p><strong>Your message:</strong><br>${escapeHtml(message).replace(/\n/g, "<br>")}</p>` : ""}
<p>Reference: ${escapeHtml(submissionId)}</p>
`,
text: `Hi ${name},\n\nThanks for contacting Acme. Our team received your message and will reply soon.${message ? `\n\nYour message:\n${message}` : ""}\n\nReference: ${submissionId}`
};
const volaneaResponse = await fetch("https://api.volanea.com/v1/email/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.VOLANEA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `netlify-form-${submissionId}`
},
body: JSON.stringify(emailPayload)
});
const responseText = await volaneaResponse.text();
if (!volaneaResponse.ok) {
console.error("Volanea send failed", {
status: volaneaResponse.status,
submissionId
});
return new Response("Email provider request failed", { status: 502 });
}
console.log("Volanea email accepted", { submissionId, responseText });
return new Response("Accepted", { status: 202 });
};
The request maps data.email to to, uses a domain-controlled sender in from, and converts the remaining fields into both HTML and plain text. The Idempotency-Key derives from Netlify's submission ID so a repeated delivery can be recognized as the same logical message.
Confirm the exact Volanea endpoint, request fields, and idempotency behavior against the current API documentation before deploying. API payloads should be treated as versioned contracts, not copied indefinitely from an integration example.
Keep the Volanea API key out of the browser
Set these environment variables in the Netlify environment used by the deployed function:
VOLANEA_API_KEY=your_server_only_volanea_key
NETLIFY_FORM_WEBHOOK_TOKEN=a_long_random_webhook_secret
A Netlify Function can access those values with process.env. Your site bundle must not. In particular, do not put the key in a JavaScript file under src, do not commit it to .env without ignoring that file, and do not expose it through a build-time variable convention intended for browser code.
A public API key leak is more than an inconvenience. An attacker could send mail through your account, consume sending capacity, damage the reputation of your authenticated domain, or probe your account's API permissions. Revocation and key rotation are recoveries, not preventive controls.
Separate client configuration from server secrets
A public value such as a site URL may be safe for client configuration. A Volanea API key is fundamentally different because it authorizes sending. The fact that an app is deployed on Netlify does not turn its frontend environment into a secure runtime.
The browser should know only where to submit the form. Netlify's Forms infrastructure and the function should own the provider credentials. This design also means you can change email providers, sender addresses, templates, or routing rules without shipping a new frontend bundle.
Deliverability and data-quality considerations
A working HTTP response is not the same as a successful inbox experience. Transactional confirmation mail should be recognizable, expected, and sent from an authenticated identity that matches the user-facing product.
Use a subject such as “We received your message” rather than promotional language. Keep the confirmation focused on the action the person just took, identify the organization, and include a realistic reply expectation. If messages are sent to a team inbox instead, make the subject useful for triage, for example New contact form: Avery Chen.
Validate before sending, but do not over-trust validation
HTML type="email" is a user-experience aid, not a deliverability check. The function's basic validation prevents obvious malformed data from reaching the API, but it cannot establish that a mailbox exists or is safe to contact.
For workflows with high lead volume, account invitations, or expensive downstream fulfillment, consider checking submitted addresses with an email address verification tool before using them in an automated sequence. Verification reduces obvious mistakes; consent, relevance, and message expectations still determine whether the email is appropriate.
Avoid reflecting sensitive form content
The sample acknowledgement repeats the visitor's message. That can be useful for reassurance, but it may be inappropriate for forms containing medical details, passwords, account information, attachments, or other sensitive content. In those cases, send a short confirmation without reproducing the content and provide only a reference ID.
Likewise, avoid placing personal data in URL parameters, subject lines, or unnecessarily broad logs. Every additional system that receives a submission payload expands the privacy review surface.
When this breaks: failures at the Netlify-to-Volanea hop
The most important failures in this integration happen after the form has been accepted but before a provider has accepted one and only one email. Design for that gap rather than assuming a webhook behaves like a single, atomic transaction.
Retries can create duplicate sends
Webhook systems may retry when the destination does not produce a timely successful response. A function can also successfully submit an email to Volanea and then fail before it returns its own response to Netlify because of a network interruption or runtime problem. From Netlify's point of view, that delivery may be retryable; from Volanea's point of view, the first email may already have been accepted.
Use the stable Netlify submission ID as the logical event identifier. Pass it as an idempotency key if the Volanea endpoint and account support idempotent requests, as in the example. For stricter control, persist a record keyed by submission.id before or alongside sending, using a durable store rather than in-memory function state.
Do not solve duplicate sends by returning a success response before the provider request is complete. That prevents retries but can silently lose mail. The correct choice is a durable idempotency strategy plus explicit monitoring of failed provider responses.
Webhook and function timeouts
An outgoing webhook expects a prompt HTTP response. Slow template rendering, a slow third-party API, connection trouble, or work unrelated to the email send can cause the function to exceed the delivery window. Keep the request path narrow: parse, validate, map, call Volanea, and return.
Do not fetch large files, enrich a lead from multiple external services, generate a PDF, and send email all in the synchronous webhook handler. Put slower work behind a queue or a separate asynchronous workflow. The acknowledgement path should have a predictable upper bound and minimal dependencies.
When investigating timeout symptoms, compare three timestamps: the Netlify form submission creation time, the function invocation time, and the Volanea acceptance time. That sequence reveals whether Netlify never reached the function, the function did not reach Volanea, or Volanea accepted mail but the function failed before acknowledging the webhook.
Missing or changed payload fields
Do not assume a field exists merely because it existed in a sample submission. Optional form inputs can be absent. Multi-select controls can produce shapes that need normalization. A form rename changes form_name, and an HTML refactor can alter field names without changing the visual form.
Feature availability and submission metadata can also differ with the Netlify Forms configuration and plan in use. Verify the fields delivered by your own form and avoid business logic that depends on optional metadata unless you have confirmed it for the environment where it runs.
The sample function explicitly rejects a missing or invalid data.email with a 422 response and ignores unexpected form names. Add similar guards for required workflow fields such as product, locale, or request_type. A clear controlled failure is easier to repair than a malformed email sent to the wrong person.
Authentication failures and sender failures
A 401 or 403 response from Volanea usually points to a missing, revoked, incorrectly scoped, or incorrectly loaded API key. Check the function environment, not the frontend application configuration. A sender-related error usually means the From domain or address is not ready for use in the relevant Volanea environment.
Do not log the Authorization header while diagnosing either condition. Log only response status, provider request identifiers when available, and the Netlify submission ID.
Spam and abuse submissions
A contact form is a public input endpoint. Bots can create submissions that turn into outbound mail, even if no API key is exposed. Use the anti-spam protections available for the form, validate the expected form name, rate-limit or add additional checks where appropriate, and avoid sending detailed automatic replies to suspicious submissions.
An internal alert can be safer than an external auto-reply for high-abuse forms. If an acknowledgement is essential, make its content brief and avoid including links or rich content that could be manipulated through submitted values.
Alternatives when Forms webhooks are not the right trigger
The Forms-webhook pattern is best when Netlify Forms is already the source of truth. It is not mandatory for every Netlify application.
If your application uses a custom React, Next.js, or API-driven form that posts JSON directly to a Netlify Function, the function itself can be the trigger and can call Volanea after authenticating the user or validating anti-spam controls. In that design there is no Netlify Forms submission payload; you define the request schema yourself.
If the business event happens in a CRM, payment processor, database, or automation product, use that system's webhook or automation event to call a protected integration endpoint. Zapier or Make can be a middleware option when you need no-code routing, but it adds another credential store, another retry model, latency, and another place where personal data can be retained.
Choose the direct Netlify Forms webhook route when you want fewer moving parts and the event is genuinely “a visitor submitted this Netlify form.” Choose middleware when the event requires branching across multiple systems that you do not want to maintain in a function.
Operational checklist before production
Use this checklist for the first deployment and whenever you change the form or email template:
- Confirm the deployed HTML contains a Netlify-detected form with the intended
nameand named inputs. - Submit a test form on the production-like deployed URL, not only localhost.
- Confirm the outgoing webhook targets the correct deployed function URL.
- Store
VOLANEA_API_KEYand the webhook token only in Netlify server-side environment configuration. - Verify that the From domain and address are authorized for the relevant Volanea environment.
- Confirm the function rejects unexpected form names and invalid recipient values.
- Test one successful acknowledgement, one missing-email case, and one simulated provider failure.
- Verify idempotency using the same submission ID or your chosen durable event record.
- Review logs for submission IDs and status codes without retaining unnecessary message content.
- Check the received email in a real inbox for sender display, reply behavior, HTML rendering, and plain-text fallback.
Once the basic path is stable, add observability. Track submission count, function invocation count, provider acceptance count, provider errors, and duplicate-suppression count. Those five measures make it much easier to see whether a decline in confirmations is caused by form detection, webhook delivery, function execution, or the email API.
Conclusion
To send email from Netlify securely, use Netlify Forms as the submission trigger, an outgoing webhook notification to invoke a server-side Netlify Function, and Volanea as the authenticated sending service. The function is the boundary that turns untrusted form fields into a controlled email request while keeping the API key out of the browser.
The details that make this production-ready are not glamorous: a verified sender, stable form field names, payload validation, short response times, and idempotency keyed to Netlify's submission ID. Build those into the first version and you will have an integration that is easier to monitor, safer to operate, and less likely to send duplicate mail during transient failures.
FAQ
Can I call Volanea directly from a Netlify frontend?
No. A direct browser call would expose the API key to every visitor. Call Volanea from a Netlify Function or another trusted server-side component, with the key stored in server-side environment variables.
What triggers the Volanea email in this setup?
A successful Netlify Forms submission triggers Netlify's configured outgoing webhook notification. That notification posts the submission payload to the Netlify Function, and the function sends the Volanea API request.
Does this require a native Volanea Netlify plugin?
No. Volanea does not provide a native Netlify marketplace app or plugin for this flow. The integration uses Netlify Forms, an outgoing webhook, a Netlify Function, and the Volanea REST API.
How do I stop a webhook retry from sending two emails?
Use Netlify's submission ID as the idempotency identifier. Pass it through an API-supported idempotency mechanism and, for stronger guarantees, store processed submission IDs in durable storage before allowing a second send.
Can I send an internal notification instead of an acknowledgement?
Yes. Map a fixed internal address to the recipient field, put the submitter's address in reply_to, and escape submitted values before including them in the HTML or text body. Keep a separate idempotency key for each distinct message type if you send both an alert and a confirmation.