---
title: "wpcf7_mail_sent Hook: Contact Form 7 to Webhook Guide"
description: "How wpcf7_mail_sent fires, what get_posted_data() returns, and the reliable way to send Contact Form 7 submissions to any webhook endpoint."
url: "https://wpwebhooks.org/blog/wordpress-wpcf7-mail-sent-hook/"
date: "2026-05-30"
---

# wpcf7_mail_sent Hook: Contact Form 7 to Webhook Guide

**TL;DR**

-   `wpcf7_mail_sent` fires after CF7 successfully sends its notification email — use it when you need confirmed delivery before dispatching a webhook
-   Access field values via `WPCF7_Submission::get_instance()->get_posted_data()`
-   Calling `wp_remote_post` inside the hook is synchronous — it blocks PHP until your endpoint responds
-   Webhook Actions ships a built-in CF7 integration with async queue, retry, and delivery logs — no custom PHP needed

/ What is wpcf7\_mail\_sent

## What is **wpcf7\_mail\_sent** and when does it fire?

`wpcf7_mail_sent` is a WordPress action hook provided by [Contact Form 7](https://contactform7.com/) (CF7) — with over 10 million active installs¹, the most widely deployed form plugin in the WordPress ecosystem. The hook fires at the end of CF7's form processing pipeline, specifically after the plugin has successfully delivered its configured notification email.

The practical implication: if you hook into `wpcf7_mail_sent`, you know the form passed validation, cleared spam checks, and that CF7's own mailer completed without error. This makes it the right attachment point for downstream automations — CRM entries, Slack notifications, and webhook dispatches — where you want confirmation the form fully succeeded before triggering side effects.

FIG 01 — CF7 form submission and hook lifecycle

/ Before vs After

## How does **wpcf7\_mail\_sent** differ from **wpcf7\_before\_send\_mail**?

Contact Form 7 exposes two hooks around its mail-sending step. `wpcf7_before_send_mail` fires immediately before CF7 attempts to send the email and receives a reference to the `$abort` flag — setting it to `true` prevents both the email send and any `wpcf7_mail_sent` callback. This makes `wpcf7_before_send_mail` the right hook for altering payload data or implementing custom spam filtering.

`wpcf7_mail_sent` fires only after the mail send completes successfully. You cannot abort or modify the submission at this point — the hook is purely for reacting to a confirmed submission. Use `wpcf7_mail_sent` when your downstream system should only receive data that CF7 itself treated as fully processed.

A third hook, `wpcf7_submit`, fires after validation but before the mail step. If you need to capture every validated submission regardless of email delivery status, `wpcf7_submit` is the hook to use — but for webhook dispatch, the later `wpcf7_mail_sent` is usually preferable because it filters out aborted or failed submissions automatically.

/ Hook parameters

## What parameters does **wpcf7\_mail\_sent** pass to your callback?

The hook passes a single argument: `$contact_form`, an instance of `WPCF7_ContactForm`. This object exposes the form configuration — its ID, title, and properties — but not the submitted field values. Submitted values live on the _submission_ object, which is accessed separately.

PHP — hook registration

```
add_action( 'wpcf7_mail_sent', function( $contact_form ) {
    // $contact_form is WPCF7_ContactForm
    $form_id   = $contact_form->id();       // integer
    $form_name = $contact_form->title();    // string — the form name from CF7 editor

    // Submitted values are NOT on $contact_form — see next section
} );
```

/ Accessing field values

## How do you access submitted field values inside **wpcf7\_mail\_sent**?

Submitted data is available on the `WPCF7_Submission` singleton. Call `WPCF7_Submission::get_instance()` — this returns the submission object for the current request, or `null` outside a CF7 processing context. Always null-check before proceeding.

The `get_posted_data()` method returns an associative array keyed by the `name` attributes set in the CF7 form editor (the values inside square brackets in CF7 shortcodes, e.g. `[text your-name]`). All values are run through CF7's sanitization pipeline before being available here.

PHP — reading submitted field values

```
add_action( 'wpcf7_mail_sent', function( $contact_form ) {
    $submission = WPCF7_Submission::get_instance();
    if ( ! $submission ) {
        return;
    }

    $data = $submission->get_posted_data();
    // $data is an associative array:
    // [ 'your-name' => 'Jane Smith', 'your-email' => 'jane@example.com', ... ]

    $payload = [
        'form_id'   => $contact_form->id(),
        'form_name' => $contact_form->title(),
        'name'      => $data['your-name']    ?? '',
        'email'     => $data['your-email']   ?? '',
        'message'   => $data['your-message'] ?? '',
    ];
} );
```

/ Webhook dispatch

## How do you dispatch a webhook from **wpcf7\_mail\_sent**?

Once you have the payload array, pass it to `wp_remote_post` with a JSON body. The implementation is a direct HTTP call: serialise the payload with `wp_json_encode`, set `Content-Type: application/json`, and send to your endpoint URL.

PHP — wp\_remote\_post dispatch inside wpcf7\_mail\_sent

```
add_action( 'wpcf7_mail_sent', function( $contact_form ) {
    $submission = WPCF7_Submission::get_instance();
    if ( ! $submission ) {
        return;
    }

    $data    = $submission->get_posted_data();
    $payload = [
        'form_id' => $contact_form->id(),
        'name'    => $data['your-name']    ?? '',
        'email'   => $data['your-email']   ?? '',
        'message' => $data['your-message'] ?? '',
    ];

    wp_remote_post( 'https://your-endpoint.com/webhook', [
        'headers'     => [ 'Content-Type' => 'application/json' ],
        'body'        => wp_json_encode( $payload ),
        'timeout'     => 5,
        'blocking'    => true,   // ← PHP waits here
        'data_format' => 'body',
    ] );
} );
```

/ Reliability risks

## What are the reliability risks of synchronous dispatch inside a hook?

The code above works — but it is synchronous. When PHP executes `wp_remote_post` with `'blocking' => true`, the process stalls until your webhook endpoint sends a response or the timeout elapses (5 seconds in the example). During that window, the user's browser is waiting for the CF7 success response, the PHP-FPM worker is occupied, and any slow or unavailable endpoint directly degrades your form's perceived performance.

There is also no retry. If your endpoint returns a 5xx error or times out, the delivery is silently lost — no log entry, no re-attempt, no alert. On high-traffic forms this adds up quickly: a downstream endpoint that experiences 2% transient failures across 500 daily submissions means 10 missed integrations per day with no visibility.

> Calling `wp_remote_post` synchronously inside a form hook means every visitor who submits your form waits for your webhook endpoint — not just for WordPress.

/ Webhook Actions

## How does Webhook Actions handle CF7 webhook delivery without custom code?

The [Webhook Actions plugin](https://wpwebhooks.org/wordpress-webhook-plugin/) includes a built-in Contact Form 7 integration. When CF7 is active alongside Webhook Actions, CF7 form submissions appear as native trigger types in the plugin's admin UI. You select the form, map fields to your payload using the visual mapper, and configure the endpoint URL — no PHP code required.

Delivery is handled asynchronously: Webhook Actions enqueues each submission via [Action Scheduler](https://actionscheduler.org/) when available, which means the form response returns immediately and the HTTP dispatch happens in the next queue cycle — typically within seconds but always outside the visitor's request. Failed deliveries retry automatically with exponential backoff (1 min → 2 min → 4 min → 8 min, capped at 1 hour, default 5 attempts) and are logged with the full request body and HTTP response for inspection in wp-admin.

| Capability | Raw wpcf7\_mail\_sent + wp\_remote\_post | Webhook Actions (built-in CF7) |
| --- | --- | --- |
| Setup | Custom PHP in functions.php or plugin | Visual admin UI — no code |
| Delivery | Synchronous — blocks form response | Async via Action Scheduler queue |
| Retry | None — one attempt only | Exponential backoff, 5 attempts |
| Delivery log | None — failures are silent | Per-attempt log with request and response in wp-admin |
| Field mapping | Manual array construction in PHP | GUI mapper — rename, restructure, exclude fields |
| Multiple webhooks | One add\_action per endpoint | Unlimited webhooks per form trigger |

/ Testing and monitoring

## How do you test and monitor CF7 webhook delivery in production?

The quickest way to validate the raw `wpcf7_mail_sent` approach is to point the endpoint URL at a request inspector like [webhook.site](https://webhook.site) — a temporary URL that logs every inbound request with full headers and body. Submit the form, then inspect the payload on webhook.site to confirm field values and structure before switching to the real destination.

For production monitoring without a plugin, [`is_wp_error`](https://developer.wordpress.org/reference/functions/is_wp_error/) on the `wp_remote_post` return value surfaces transport errors (timeouts, DNS failures), and `wp_remote_retrieve_response_code` gives the HTTP status. Log both to `error_log` or a custom table for visibility. With Webhook Actions, delivery logs are built-in: every attempt records the timestamp, HTTP status code, full response body, and retry count — visible from the Webhook Actions → Logs screen.

The [Contact Form 7 to Webhook overview](https://wpwebhooks.org/blog/contact-form-7-webhook/) covers the full Webhook Actions setup flow including test delivery from the plugin admin.

/Footnotes

¹ Contact Form 7 active install count from the [WordPress.org plugin directory](https://wordpress.org/plugins/contact-form-7/).

² [Contact Form 7 documentation](https://contactform7.com/docs/) — official hook and API reference.

³ [Action Scheduler usage guide](https://actionscheduler.org/usage/) — async job scheduling for WordPress.

## Structured data

```json
{"@context":"https://schema.org","@type":"Article","headline":"wpcf7_mail_sent Hook: Contact Form 7 to Webhook Guide","description":"How wpcf7_mail_sent fires, what get_posted_data() returns, and the reliable way to send Contact Form 7 submissions to any webhook endpoint.","datePublished":"2026-05-30","dateModified":"2026-05-30","author":{"@type":"Person","name":"Mateusz Skorupa","url":"https://wpwebhooks.org/about/"},"publisher":{"@type":"Organization","name":"WP Webhooks","url":"https://wpwebhooks.org"},"url":"https://wpwebhooks.org/blog/wordpress-wpcf7-mail-sent-hook/","image":"https://wpwebhooks.org/og_image.jpg","keywords":["wpcf7 mail sent","contact form 7 webhook","cf7 webhook","contact form 7 to webhook plugin","cf7 form submission webhook","contact form 7 webhooks plugin","wpcf7 mail sent hook"]}

{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"WP Webhooks","item":"https://wpwebhooks.org/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://wpwebhooks.org/blog/"},{"@type":"ListItem","position":3,"name":"wpcf7_mail_sent Hook: Contact Form 7 to Webhook Guide","item":"https://wpwebhooks.org/blog/wordpress-wpcf7-mail-sent-hook/"}]}

{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"What is the difference between wpcf7_mail_sent and wpcf7_before_send_mail?","acceptedAnswer":{"@type":"Answer","text":"wpcf7_before_send_mail fires before CF7 sends its notification email and can abort the send by setting $abort to true. wpcf7_mail_sent fires after the email has been sent successfully. For webhook dispatch where you want confirmation the form was fully processed, wpcf7_mail_sent is the right hook."}},{"@type":"Question","name":"How do you access Contact Form 7 field values inside wpcf7_mail_sent?","acceptedAnswer":{"@type":"Answer","text":"Call WPCF7_Submission::get_instance() to retrieve the active submission object, then call $submission->get_posted_data() to get an associative array of field names to submitted values. Field names match the name attributes in the CF7 form editor."}},{"@type":"Question","name":"Does wpcf7_mail_sent fire if the CF7 email fails to send?","acceptedAnswer":{"@type":"Answer","text":"No. wpcf7_mail_sent fires only after a successful mail delivery. If you need to catch all valid submissions regardless of email status, use wpcf7_submit, which fires after validation passes but before the mail send attempt."}},{"@type":"Question","name":"Is it safe to call wp_remote_post inside wpcf7_mail_sent?","acceptedAnswer":{"@type":"Answer","text":"It works but blocks PHP execution until the remote endpoint responds. On a slow or unavailable endpoint this delays the success response seen by the visitor. The safer pattern is to enqueue the delivery via Action Scheduler or use Webhook Actions, which handles async delivery with automatic retry."}},{"@type":"Question","name":"Does Webhook Actions require a separate CF7 webhook plugin?","acceptedAnswer":{"@type":"Answer","text":"No. Webhook Actions has a built-in CF7 integration. When Contact Form 7 is active, submissions appear as triggers in the webhook configuration screen with structured, pre-mapped field payloads — no additional plugin or custom PHP code needed."}}]}
```
