Back to blog
/7 min read

How to Send Transactional Email from WordPress with ZidiMail

Learn how to send transactional email from WordPress using the ZidiMail REST API. Covers wp_remote_post(), WooCommerce order emails, Contact Form 7 integration, and admin settings.

WordPress sends email via wp_mail(), which defaults to the PHP mail() function — the lowest-quality delivery path that exists. Messages routinely end up in spam or are rejected outright by modern email providers. Replacing it with the ZidiMail REST API takes about 20 minutes and immediately improves deliverability for every email your site sends.

This guide shows you how to call the ZidiMail API directly using wp_remote_post(), how to hook into WooCommerce order emails, and how to wire up Contact Form 7 — all without relying on a plugin that may change or become unmaintained.

Prerequisites

  • A ZidiMail account (free, no credit card)
  • A verified sending domain
  • An API key from the ZidiMail dashboard
  • WordPress 6.0 or later
  • For WooCommerce examples: WooCommerce 8.0 or later

1. Store your API key securely

Add to wp-config.php above the "stop editing" comment. Never hardcode the key in a theme or plugin file that might end up in a public repo.

// wp-config.php
define('ZIDIMAIL_API_KEY', 'zm_live_your_full_key');
define('ZIDIMAIL_FROM',    'hello@mail.yourdomain.com');

2. Create a helper function in your plugin or theme

<?php
// In your plugin file or functions.php

function zidimail_send( $to, $subject, $html, $text = '', $reply_to = '' ) {
    if ( ! defined( 'ZIDIMAIL_API_KEY' ) ) {
        return new WP_Error( 'no_key', 'ZIDIMAIL_API_KEY is not defined.' );
    }

    $payload = [
        'from'    => ZIDIMAIL_FROM,
        'to'      => (array) $to,
        'subject' => $subject,
        'html'    => $html,
    ];

    if ( $text )     $payload['text']     = $text;
    if ( $reply_to ) $payload['reply_to'] = $reply_to;

    $response = wp_remote_post( 'https://api.zidimails.com/v1/emails', [
        'method'  => 'POST',
        'timeout' => 20,
        'headers' => [
            'Authorization' => 'Bearer ' . ZIDIMAIL_API_KEY,
            'Content-Type'  => 'application/json',
        ],
        'body' => wp_json_encode( $payload ),
    ] );

    if ( is_wp_error( $response ) ) {
        error_log( 'ZidiMail WP_Error: ' . $response->get_error_message() );
        return $response;
    }

    $code = wp_remote_retrieve_response_code( $response );
    $body = json_decode( wp_remote_retrieve_body( $response ), true );

    if ( $code >= 400 ) {
        $msg = $body['error'] ?? "HTTP $code";
        error_log( "ZidiMail API error $code: $msg" );
        return new WP_Error( 'zidimail_error', $msg );
    }

    return $body; // [ 'id' => '...' ]
}

3. Send a contact form notification

This fires whenever a user submits a plain HTML form and calls the helper directly:

<?php
// Handle form submission
add_action( 'init', function () {
    if ( $_SERVER['REQUEST_METHOD'] !== 'POST' || empty( $_POST['contact_form'] ) ) return;

    $name    = sanitize_text_field( $_POST['name'] ?? '' );
    $email   = sanitize_email( $_POST['email'] ?? '' );
    $message = sanitize_textarea_field( $_POST['message'] ?? '' );

    if ( ! $email || ! $name ) return;

    zidimail_send(
        to:       get_option('admin_email'),
        subject:  "New contact from $name",
        html:     "<p><strong>From:</strong> $name &lt;$email&gt;</p><p>" . nl2br( esc_html( $message ) ) . '</p>',
        text:     "From: $name <$email>

$message",
        reply_to: $email,
    );

    // Auto-reply to the visitor
    zidimail_send(
        to:      $email,
        subject: 'We received your message',
        html:    "<p>Hi $name, thanks for reaching out. We will get back to you within one business day.</p>",
        text:    "Hi $name, thanks for reaching out. We will get back to you within one business day.",
    );
} );

4. WooCommerce — replace order confirmation emails

Hook into WooCommerce's order status transitions to send a custom confirmation via ZidiMail instead of the default wp_mail() route:

<?php
add_action( 'woocommerce_order_status_processing', 'zidimail_order_confirmation', 10, 2 );

function zidimail_order_confirmation( $order_id, $order ) {
    $billing_email = $order->get_billing_email();
    $first_name    = $order->get_billing_first_name();
    $order_number  = $order->get_order_number();
    $order_total   = $order->get_formatted_order_total();
    $order_url     = $order->get_view_order_url();

    $html = "
    <h2>Order #{$order_number} confirmed, {$first_name}!</h2>
    <p>Thank you for your purchase. Your order total is <strong>{$order_total}</strong>.</p>
    <p><a href='{$order_url}'
         style='display:inline-block;background:#2563eb;color:#fff;padding:10px 20px;
                border-radius:6px;text-decoration:none;font-weight:600;'>
        View your order
    </a></p>
    <p style='color:#6b7280;font-size:13px;'>
        Questions? Reply to this email or contact us at support@yourdomain.com.
    </p>";

    zidimail_send(
        to:       $billing_email,
        subject:  "Order #{$order_number} confirmed",
        html:     $html,
        text:     "Order #{$order_number} confirmed. Total: {$order_total}. View: {$order_url}",
        reply_to: 'support@yourdomain.com',
    );
}

5. Contact Form 7 integration

<?php
// fires after CF7 sends its own email — add your ZidiMail copy
add_action( 'wpcf7_mail_sent', function ( $contact_form ) {
    $submission = WPCF7_Submission::get_instance();
    if ( ! $submission ) return;

    $data    = $submission->get_posted_data();
    $name    = sanitize_text_field( $data['your-name']  ?? '' );
    $email   = sanitize_email(      $data['your-email'] ?? '' );
    $message = sanitize_textarea_field( $data['your-message'] ?? '' );

    if ( ! $email ) return;

    zidimail_send(
        to:       get_option('admin_email'),
        subject:  "CF7: New message from $name",
        html:     "<p><strong>$name</strong> &lt;$email&gt;</p><p>" . nl2br(esc_html($message)) . '</p>',
        reply_to: $email,
    );
} );

Common issues

  • "WP_Error: cURL error 28" — wp_remote_post timed out. Increase the timeout to 20 seconds or move the send to a background action using wp_schedule_single_event().
  • "422 domain not verified" — the ZIDIMAIL_FROM domain must be verified under ZidiMail Domains.
  • Emails still going through wp_mail() — make sure your custom function runs and is not overridden by a plugin. Deactivate SMTP plugins that might interfere.
  • WooCommerce sending duplicate emails — if WooCommerce also sends its default email, disable the corresponding WooCommerce email class under WooCommerce → Settings → Emails.

Stop letting WordPress emails land in spam

Verify your domain with ZidiMail in 5 minutes and every WordPress email immediately gets proper SPF, DKIM, and DMARC.

Start sending free