Send email from Contact Form 7 reliably by treating the form submission as an event, then passing that event to a small webhook handler that calls Volanea’s REST API. This approach avoids exposing your sending credentials in WordPress while giving you precise control over recipients, content, retries, and delivery.

Contact Form 7 does not include a native Volanea integration, and Volanea does not currently provide a Contact Form 7 app or plugin. The honest implementation is a webhook pattern: Contact Form 7 sends a structured submission to an endpoint you control, and that endpoint turns the submission into a transactional message through Volanea.

This guide uses the CF7 to Webhook WordPress plugin as the form-to-webhook bridge and a Cloudflare Worker as the secure middle layer. You can apply the same request structure in another serverless function, a Node.js route, Laravel, or your own application server. The important architectural rule is the same in every case: keep the Volanea secret key off the WordPress site and out of browser-visible code.

What you are building

The completed flow looks like this:

  1. A visitor submits a Contact Form 7 form.
  2. CF7 to Webhook sends a JSON payload to your HTTPS endpoint.
  3. Your endpoint verifies that the request came from your WordPress site.
  4. The endpoint validates and normalizes the submitted values.
  5. It calls POST https://api.volanea.com/v1/send with your Volanea API key.
  6. Volanea accepts the transactional email and processes it through the sending pipeline.
  7. Your endpoint returns a success or failure response that you can monitor in logs.

This is different from changing Contact Form 7’s standard Mail settings or configuring WordPress SMTP. SMTP changes how WordPress sends the notification email that Contact Form 7 already generates. The webhook/API pattern gives you a separate transactional workflow: for example, a customer confirmation, a sales notification, a support ticket acknowledgement, or a routing message based on the form’s selected topic.

A webhook bridge is especially useful when the form needs logic that Contact Form 7’s Mail tab cannot safely handle on its own. You can reject malformed values, choose a recipient based on a dropdown, create a stable idempotency key, prevent duplicate acknowledgements, add internal identifiers, and avoid using a visitor’s email address as the authenticated sender.

Why use a webhook instead of sending directly from WordPress?

It can be tempting to place an API key in WordPress and call an email API directly from a theme function or a small plugin. That can work technically, but it creates operational risks.

A WordPress install usually has more administrators, plugins, backups, migration copies, and potential attack surface than a narrowly scoped serverless endpoint. A key stored in a WordPress option, plugin configuration screen, or source file can be exposed through a backup, an overly broad administrator account, a vulnerable plugin, or a staging environment copied from production.

A webhook handler reduces that exposure. WordPress gets one limited-purpose shared secret for authenticating requests to your handler. The handler stores the Volanea API key as an environment secret. WordPress never needs the sending credential.

The separation also makes it easier to maintain a clean responsibility boundary:

  • Contact Form 7 collects visitor input and performs form-level validation.
  • CF7 to Webhook forwards the accepted submission as JSON.
  • Your handler authenticates the source, validates data again, applies business rules, and creates a stable event identity.
  • Volanea sends the transactional message from an authenticated sending domain.

This is not needless complexity. It is a practical way to prevent a contact form from becoming an unrestricted email relay. A public form should never be allowed to submit arbitrary to, from, subject, or HTML values directly to an email provider.

The Contact Form 7 webhook option to use

Contact Form 7 itself does not ship with a general outbound webhook feature. For this workflow, install and activate CF7 to Webhook alongside Contact Form 7. The plugin is designed to use Contact Form 7 submissions as triggers for webhook endpoints and supports JSON payloads, request methods, templates, and headers.

Do not confuse the plugin name with a native integration. It is an independent WordPress plugin that sits between Contact Form 7 and your endpoint. That distinction matters because the data format is configurable: there is not one immutable Contact Form 7 webhook payload that every site sends.

For a predictable integration, explicitly define the JSON template rather than relying on automatic field discovery. That gives your handler a stable contract even if you later add form fields or rename labels in the Contact Form 7 editor.

Start with a simple form such as this in Contact Form 7:

<label>Your name
    [text* your-name autocomplete:name]
</label>

<label>Your email
    [email* your-email autocomplete:email]
</label>

<label>Subject
    [text* your-subject]
</label>

<label>Your message
    [textarea* your-message]
</label>

[submit "Send message"]

The names inside the form tags—your-name, your-email, your-subject, and your-message—are the mail tags you will map into the webhook payload. Keep them stable. Form labels can change for clarity without forcing an API contract change, but changing a field name should be treated like changing a request schema.

The actual JSON payload your form sends

Because CF7 to Webhook supports payload templates, the actual payload is the JSON object you configure. Use the following exact JSON template in the webhook configuration for this form:

{
  "event": "contact_form_submission",
  "form": "website-contact",
  "submissionId": "[ _serial_number ]",
  "submittedAt": "[ _date ] [ _time ]",
  "name": "[your-name]",
  "email": "[your-email]",
  "subject": "[your-subject]",
  "message": "[your-message]"
}

That template produces a request body shaped like this example:

{
  "event": "contact_form_submission",
  "form": "website-contact",
  "submissionId": "cf7-81d6e5b0",
  "submittedAt": "2026-08-24 14:37:51",
  "name": "Maya Chen",
  "email": "maya@example.com",
  "subject": "Need help with an order",
  "message": "Could you tell me when order 10482 will ship?"
}

This is the shape your handler should expect. It is intentionally small and explicit. The receiving endpoint does not need Contact Form 7 internals, browser metadata, WordPress cookies, or every field that might exist on a future version of the form.

The _serial_number, _date, and _time values are Contact Form 7 special mail tags. Use the serial number as an upstream submission identifier only if it is available and consistently populated in your setup. The Worker below still creates a deterministic fallback idempotency key from the payload if no serial number is present.

Do not put an email recipient into this public JSON payload. A malicious visitor can modify a browser request or exploit an unprotected form endpoint. The recipient should be configured in your server-side handler, where it cannot be changed by form input.

Configure the Contact Form 7 webhook safely

Create a public HTTPS endpoint for the webhook before configuring the WordPress plugin. The Cloudflare Worker example later in this guide uses a route such as:

https://forms.example.com/contact-form

In the CF7 to Webhook settings for the specific form, configure a POST request to that address and select JSON content. Add the JSON payload template from the preceding section.

Also configure a custom request header that the Worker can check:

X-CF7-Webhook-Secret: replace-with-a-long-random-secret

Use a long, random value generated by a password manager or secrets tool. Do not use your WordPress admin password, your Volanea API key, a domain name, or a guessable phrase. Store the same value in the Worker as CF7_WEBHOOK_SECRET.

The header is important because the Worker URL is public by design. HTTPS encrypts the request in transit, but it does not prove that a request originated from your WordPress site. The shared secret is the basic source-authentication check that stops arbitrary internet users from sending requests to your email workflow.

Before publishing the form, send a test submission. Confirm that the handler receives valid JSON, that special mail tags resolve as expected, and that blank optional fields do not cause your template to become invalid JSON. If a message field can contain quotation marks or line breaks, the webhook plugin must JSON-encode the substituted value correctly; test this with realistic content rather than only a one-word submission.

Create and authenticate your Volanea sending setup

Before code can send production email, create a Volanea API key and verify the sending domain you plan to use. The from address in the API request should belong to that verified domain.

For example, if your website runs on example.com, a sensible sender might be:

Website Team <forms@example.com>

The visitor’s address should not be used as the from address. That commonly creates authentication and alignment problems because your domain cannot legitimately authenticate mail claiming to originate from maya@example.com. Instead, set the visitor’s address as replyTo. Your team receives a properly authenticated message from your domain and can reply directly to the visitor.

You will need two Volanea values in the Worker environment:

VOLANEA_API_KEY=sk_...
VOLANEA_FROM=Website Team <forms@example.com>

Use a production secret key only in the production environment. Keep a separate test key and test sender for development where available. Review the email API reference and setup guides when you need to confirm API behavior, domain authentication, sending events, or future endpoint changes.

For contact forms, send two messages only if they serve distinct purposes:

  1. An internal notification to a fixed support or sales inbox.
  2. A customer acknowledgement to the visitor’s submitted address.

Do not automatically add form submitters to marketing campaigns merely because they requested support or asked a question. A transactional acknowledgement confirms their request; marketing consent is a separate concern that should be collected and recorded explicitly.

Working Cloudflare Worker code: webhook to Volanea

The following Worker accepts the JSON payload shown above, checks the shared secret, validates the visitor email, escapes visitor-controlled text before placing it in HTML, and sends an internal notification through Volanea.

Set these Worker secrets and variables before deploying:

CF7_WEBHOOK_SECRET=the-same-long-random-value-used-in-wordpress
VOLANEA_API_KEY=sk_your_volanea_secret_key
VOLANEA_FROM=Website Team <forms@example.com>
INTERNAL_CONTACT_RECIPIENT=support@example.com

Use this code:

export default {
  async fetch(request, env) {
    if (request.method !== "POST") {
      return new Response("Method Not Allowed", {
        status: 405,
        headers: { Allow: "POST" }
      });
    }

    const providedSecret = request.headers.get("X-CF7-Webhook-Secret");
    if (!providedSecret || providedSecret !== env.CF7_WEBHOOK_SECRET) {
      return new Response("Unauthorized", { status: 401 });
    }

    const contentType = request.headers.get("content-type") || "";
    if (!contentType.includes("application/json")) {
      return new Response("Expected application/json", { status: 415 });
    }

    let submission;
    try {
      submission = await request.json();
    } catch {
      return new Response("Invalid JSON", { status: 400 });
    }

    const name = clean(submission.name, 120);
    const email = clean(submission.email, 254).toLowerCase();
    const subject = clean(submission.subject, 160);
    const message = clean(submission.message, 5000);
    const form = clean(submission.form, 80) || "contact-form";
    const submissionId = clean(submission.submissionId, 160);

    if (!name || !email || !subject || !message) {
      return json({ error: "Missing required submission fields" }, 422);
    }

    if (!isEmail(email)) {
      return json({ error: "Invalid visitor email address" }, 422);
    }

    const logicalEvent = submissionId || await sha256(
      `${form}|${email}|${subject}|${message}`
    );

    const emailPayload = {
      from: env.VOLANEA_FROM,
      to: [env.INTERNAL_CONTACT_RECIPIENT],
      replyTo: [email],
      subject: `[Website contact] ${subject}`,
      text: [
        `New contact form submission from ${name}.`,
        "",
        `Email: ${email}`,
        `Form: ${form}`,
        `Submission ID: ${submissionId || "not provided"}`,
        "",
        "Message:",
        message
      ].join("\n"),
      html: `
        <h1>New contact form submission</h1>
        <p><strong>Name:</strong> ${escapeHtml(name)}</p>
        <p><strong>Email:</strong> ${escapeHtml(email)}</p>
        <p><strong>Form:</strong> ${escapeHtml(form)}</p>
        <p><strong>Submission ID:</strong> ${escapeHtml(submissionId || "not provided")}</p>
        <hr>
        <p>${escapeHtml(message).replace(/\n/g, "<br>")}</p>
      `
    };

    const volaneaResponse = await fetch("https://api.volanea.com/v1/send", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${env.VOLANEA_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": `cf7-contact:${logicalEvent}`
      },
      body: JSON.stringify(emailPayload)
    });

    const responseText = await volaneaResponse.text();

    if (!volaneaResponse.ok) {
      console.error("Volanea send failed", {
        status: volaneaResponse.status,
        response: responseText.slice(0, 1000),
        event: logicalEvent
      });

      return json({ error: "Email delivery request failed" }, 502);
    }

    console.log("Volanea send accepted", { event: logicalEvent });
    return json({ ok: true, event: logicalEvent }, 202);
  }
};

function clean(value, maxLength) {
  return typeof value === "string"
    ? value.trim().slice(0, maxLength)
    : "";
}

function isEmail(value) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}

function escapeHtml(value) {
  return value.replace(/[&<>'"]/g, character => ({
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    "'": "&#39;",
    "\"": "&quot;"
  }[character]));
}

async function sha256(value) {
  const bytes = new TextEncoder().encode(value);
  const digest = await crypto.subtle.digest("SHA-256", bytes);
  return [...new Uint8Array(digest)]
    .map(byte => byte.toString(16).padStart(2, "0"))
    .join("");
}

function json(body, status) {
  return new Response(JSON.stringify(body), {
    status,
    headers: { "Content-Type": "application/json" }
  });
}

The call that actually sends the email is this portion:

await fetch("https://api.volanea.com/v1/send", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${env.VOLANEA_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": `cf7-contact:${logicalEvent}`
  },
  body: JSON.stringify(emailPayload)
});

Volanea’s single-message send endpoint accepts a JSON message with sender, recipient, subject, text, and HTML content. The Idempotency-Key header is particularly valuable for forms because retries happen: a webhook plugin may retry after a temporary timeout, a hosting layer may repeat a request, or someone may submit again after an unclear browser response.

Why idempotency matters for contact forms

An idempotency key tells the API that multiple requests represent the same logical send. Instead of producing a second internal alert whenever the same webhook request is retried, the sending service can recognize the repeated request as the same event.

The Worker uses this priority order:

  1. Use Contact Form 7’s serial number when the payload has one.
  2. Otherwise calculate a SHA-256 value from the form name, visitor email, subject, and message.

The serial number is better because it is an event identifier. The hash fallback is useful but imperfect: two truly separate submissions with exactly the same values would create the same fallback value. For most contact forms that is rare, but a high-volume or business-critical workflow should persist a server-generated submission ID in a database or queue.

Do not generate a random UUID for every request and call it an idempotency key. A random value is unique per HTTP attempt, not per form submission. If the webhook plugin retries, the retry would receive a new random key and could send a duplicate message.

Add a visitor acknowledgement safely

Many sites want to notify the internal team and immediately confirm receipt to the visitor. Add a second Volanea request only after the internal message is accepted, and only send it to the validated email value.

For example, after the first fetch succeeds, use a distinct idempotency key and a fixed, non-promotional acknowledgement:

const acknowledgementPayload = {
  from: env.VOLANEA_FROM,
  to: [email],
  replyTo: [env.INTERNAL_CONTACT_RECIPIENT],
  subject: "We received your message",
  text: `Hi ${name},\n\nThanks for contacting us. We received your message and will reply as soon as we can.\n\nFor reference, your submission ID is ${submissionId || logicalEvent}.`,
  html: `<p>Hi ${escapeHtml(name)},</p><p>Thanks for contacting us. We received your message and will reply as soon as we can.</p><p>For reference, your submission ID is <strong>${escapeHtml(submissionId || logicalEvent)}</strong>.</p>`
};

await fetch("https://api.volanea.com/v1/send", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${env.VOLANEA_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": `cf7-contact-ack:${logicalEvent}`
  },
  body: JSON.stringify(acknowledgementPayload)
});

Keep the acknowledgement factual. Do not promise a response time unless your team can meet it. Do not include the visitor’s original message in the acknowledgement if it may contain sensitive details, and do not turn the acknowledgement into a newsletter unless the form includes a separate, unselected-by-default consent field.

Deliverability rules for Contact Form 7 messages

A contact form is a common place for accidental email spoofing. The problematic pattern is setting the sender to the visitor’s submitted address. If a visitor enters a Gmail, Outlook, or corporate address, your mail system is now trying to send an email that claims to be from a domain you do not control.

Use these rules instead:

  • Send from an address on a verified domain you control.
  • Put the visitor’s email in replyTo for internal notifications.
  • Use a recognizable sender name, such as Website Team or Acme Support.
  • Include a plain-text version alongside HTML.
  • Keep subjects descriptive and stable.
  • Authenticate the sending domain before relying on production delivery.
  • Do not use form input as raw HTML, recipient addresses, or email headers.

The example Worker escapes every visitor-controlled value before inserting it into the HTML body. That is not only an email-rendering detail. Without output encoding, a visitor could submit HTML that changes the appearance of the internal notification, inserts misleading links, or interferes with your support workflow.

You may also want to validate form addresses before sending an acknowledgement. A basic syntax check is useful for catching obvious mistakes, but it cannot prove that an inbox exists or can receive mail. For more stringent intake flows, use an email address verification tool before triggering an automated confirmation, while remembering that verification is a risk signal rather than a guarantee of delivery.

Testing the full workflow

Test the workflow in layers. Do not start by assuming a failed browser submission is a Volanea API problem.

1. Test the Worker without WordPress

Use a REST client to send the sample JSON directly to the Worker. Include the exact X-CF7-Webhook-Secret header. A successful response should be HTTP 202 with an ok value in the JSON body.

This verifies the Worker route, secret configuration, JSON parsing, Volanea credentials, sender identity, and recipient configuration without involving Contact Form 7.

2. Test the Contact Form 7 webhook payload

Submit the form with a real test address and a message containing punctuation, quotation marks, ampersands, HTML-looking characters, and multiple lines. Check Worker logs for the parsed values. If the payload cannot be parsed, inspect the JSON template and confirm that every mail tag matches a field name in the form.

3. Test the mailbox behavior

Confirm that the internal notification arrives with the correct From address and the visitor’s email as the reply-to address. Reply to it and confirm the response targets the visitor rather than your own sender inbox.

Then test the visitor acknowledgement. Look for accurate content, a valid sender, expected reply handling, and a plain-text alternative. Test at least one mailbox provider outside your own organization if possible.

4. Test duplicate protection

Submit a form once, then replay the same HTTP request with the same serial number. You should not receive two copies of the same internal notification. This is the practical proof that your idempotency strategy is tied to the logical form event rather than the request attempt.

Common implementation mistakes

Putting the Volanea API key in a WordPress form setting

A public form endpoint, browser JavaScript, or widely accessible WordPress configuration is the wrong place for a sending credential. Use a Worker, serverless function, or backend route with environment secrets.

Setting from to [your-email]

That makes your infrastructure send mail as though it came from the visitor’s domain. Use your verified sender domain and add [your-email] only as reply-to data.

Forwarding all fields blindly

A webhook payload should be a contract, not a database dump. Map only values the workflow needs. Avoid forwarding passwords, payment data, unneeded hidden fields, or personal data that your support team does not need to process the request.

Treating the form as an open email relay

Never allow a public value to define Volanea’s to, from, arbitrary headers, or raw HTML. Recipients and sender identity should be fixed server-side or selected from a strict allowlist.

Using one random idempotency key per request

That does not prevent duplicates during retries. Derive the key from a submission ID or another stable event identifier.

Assuming a successful HTTP response means inbox placement

A send API accepting a message means the request passed API-level validation and entered processing. It does not guarantee that a particular mailbox will place the email in the inbox. Domain authentication, content, recipient policy, and mailbox-provider filtering remain relevant.

When Zapier or Make makes sense instead

You can put Zapier or Make between Contact Form 7 and Volanea, particularly when the form needs CRM updates, spreadsheet records, Slack alerts, lead scoring, or branching without code. Both services can receive webhook-style form events and make outbound HTTP API requests.

However, use an automation platform deliberately. If your only goal is “send one internal notification when a form is submitted,” a direct webhook handler has fewer moving parts, better secret isolation, and less payload transformation to debug. An automation layer becomes more compelling when one submission must trigger multiple operational systems.

If you do use Zapier or Make, keep the same safety rules:

  • Receive a controlled JSON payload from Contact Form 7.
  • Do not map visitor input into sender or recipient fields without allowlists.
  • Store the Volanea API key in the platform’s protected credential mechanism, not inside a visible field value.
  • Send Authorization: Bearer ..., Content-Type: application/json, and a stable Idempotency-Key with the Volanea request.
  • Preserve a unique submission ID from the webhook for de-duplication.

The Volanea request body remains substantially the same whether it comes from a Worker, Zapier, Make, or a custom backend. That portability is the point of using a REST API rather than depending on an unverified native app connection.

Operational considerations as form volume grows

At low volume, an internal mailbox may be enough. As submissions grow, treat your contact form as an operational intake system rather than merely an email generator.

Add a form field to distinguish contact, quote, demo, partnership, and support forms. Add an internal category or route based on server-side rules. For example, a selected value of support can notify a support inbox, while sales can notify a sales inbox. Do not let the browser submit arbitrary destination addresses.

You may also want to add these capabilities over time:

  • A database or queue for durable submission records.
  • A support ticket or CRM creation step before sending acknowledgements.
  • Rate limiting by IP address or fingerprint to reduce spam floods.
  • CAPTCHA or anti-spam checks before the webhook is dispatched.
  • Alerting when Volanea send requests fail repeatedly.
  • A dead-letter process for submissions that cannot be delivered after retries.
  • Event correlation using the Contact Form 7 submission ID in logs and internal email content.

These are second-order benefits of the webhook approach. Once the form submission is represented as structured data, email is one action in a reliable workflow rather than the only place the information exists.

Conclusion

To send email from Contact Form 7 using Volanea today, use a webhook bridge—not a nonexistent native integration. CF7 to Webhook forwards an explicitly defined JSON payload, your secure endpoint validates it, and the endpoint calls Volanea’s POST /v1/send REST API using a verified sender, a protected API key, and a stable idempotency key.

The result is safer than exposing an email credential in WordPress and more flexible than relying only on Contact Form 7’s default notification settings. You can send authenticated internal alerts, customer acknowledgements, or routed transactional messages while keeping visitor input separate from sender identity and delivery controls.

FAQ

Does Volanea have a native Contact Form 7 plugin?

No. Volanea does not currently provide a native Contact Form 7 integration or app. Use a webhook bridge such as CF7 to Webhook and call Volanea’s REST API from a secure backend or serverless handler.

Can I send directly from Contact Form 7 to Volanea’s API?

Do not send directly from the public form or expose the Volanea API key in WordPress. Send Contact Form 7 data to your own authenticated webhook endpoint, then have that endpoint call Volanea.

What should I use as the email sender address?

Use an address on a domain you have verified in Volanea, such as forms@example.com. Put the visitor’s submitted address in replyTo for the internal notification instead of using it as from.

Why does the example include both HTML and text email content?

The HTML version provides a structured internal notification, while the text version improves accessibility and gives recipients a robust fallback when HTML is unavailable or undesirable.

How do I stop duplicate Contact Form 7 notifications?

Pass a stable Idempotency-Key with the Volanea send request. Base it on a Contact Form 7 submission identifier, such as the serial number, rather than a new random value for every HTTP attempt.