How does WordPress send emails?

WordPress sends every email through a single function, wp_mail(), defined in wp-includes/pluggable.php. wp_mail() builds a PHPMailer instance, populates it with the arguments the caller passed (to, subject, body, headers, attachments), and calls its send() method. PHPMailer’s default transport is mail, PHP’s built-in mail() function, which hands the message to the local MTA (sendmail, postfix, or whatever the host has installed). That is the sending pipeline out of the box: WordPress → PHPMailer → PHP mail() → the server’s mail transport agent → the recipient.

The default path is unreliable on almost every modern host and unusable on a growing number of them. Nearly every WordPress site of any size replaces it with an SMTP plugin that intercepts wp_mail() and routes the message through an authenticated connection to a transactional email provider instead.

The default path, in detail

Every plugin, theme, and core feature that sends email funnels through wp_mail(). That includes password resets, comment notifications, WooCommerce order confirmations, contact form submissions, and admin alerts. It is the single chokepoint. The function is declared if ( ! function_exists( 'wp_mail' ) ) inside pluggable.php, which is what allows an SMTP plugin to redefine it wholesale by loading before pluggable.php runs.

Absent that intervention, the sequence is:

  1. A caller (WooCommerce, WordPress core, a plugin) invokes wp_mail( $to, $subject, $message, $headers, $attachments ).
  2. wp_mail() constructs a fresh PHPMailer object, applies the wp_mail_from and wp_mail_from_name filters to set the default From: header (usually wordpress@<sitename>), and populates the message body.
  3. PHPMailer’s send() method routes to whichever transport is configured. The default is mail.
  4. PHP’s mail() function calls out to the operating system’s mail transport agent using the sendmail_path php.ini setting (typically /usr/sbin/sendmail -t -i).
  5. The MTA on the server queues the message and attempts delivery via SMTP to the recipient domain’s MX host.

Two problems live in this default path. The first is technical: PHP’s mail() function is disabled at the server level on much of the modern shared-hosting market, blocked by security policy on managed WordPress hosts, and rate-limited to the point of near-uselessness where it is available. The second is authentication: even when mail() works, the message leaves the host’s IP address with no SPF alignment against the WordPress site’s domain and no DKIM signature. Gmail rejects it. Microsoft 365 downgrades it to Junk. See Form not sending mail to Gmail (but sending to Office 365) for what that split enforcement looks like in practice.

The path most sites actually use

An SMTP plugin, WP Mail SMTP, FluentSMTP, or Post SMTP, replaces the wp_mail() function with its own version, or filters PHPMailer’s transport, so that PHPMailer::send() opens an authenticated connection to a transactional provider (Postmark, SMTP2GO, Amazon SES, Brevo, Mailgun, and so on) instead of calling PHP’s mail(). The message body, subject, and headers built by the original caller are unchanged; only the transport underneath is swapped.

The rewritten pipeline is: WordPress → wp_mail() (plugin-provided) → provider SMTP or API → recipient. The site’s own DNS carries the SPF record authorising the provider and the DKIM public key the provider uses to sign outgoing messages, so mail arriving at Gmail or Microsoft 365 authenticates as the WordPress site’s domain and lands in the inbox.

Some plugins skip SMTP entirely for supported providers and use the provider’s HTTP API instead (Postmark, Mailgun, SendGrid, Amazon SES all expose one). From the WordPress side the behaviour is identical: wp_mail() is called, the plugin intercepts, the message goes out authenticated. The transport underneath is a POST request rather than an SMTP conversation.

What determines whether default sending works at all

Three variables decide whether the default mail() path produces any deliverable email on a given install:

  • Whether PHP’s mail() is enabled on the host. Managed WordPress hosts (Kinsta, WP Engine, Flywheel, Pressable) disable it entirely and require an SMTP plugin. Shared hosts (SiteGround, Bluehost, GoDaddy) leave it available but with per-hour throttles and unauthenticated origination. See WordPress hosts and email for the per-host picture.
  • Whether the sending domain has SPF and DKIM published. Without both, Gmail’s post-February-2024 sender requirements treat the mail as spam or reject it outright. Microsoft 365 downgrades to Junk. See How to set up DNS for WordPress email: SPF, DKIM, DMARC for the records that need to be published.
  • Whether any plugin has overridden wp_mail() or set a custom From: address that doesn’t align with the DNS above. WooCommerce, membership plugins, and contact form plugins routinely set their own From: headers, which can misalign against DKIM or SPF and produce silent authentication failures.

The WordPress email test suite is the canonical procedure for confirming which path the site is on and whether outgoing mail authenticates correctly at the major mailbox providers.

The full setup, from scratch

For a site starting from the default configuration and needing a reliable sending pipeline, see WordPress email setup: the complete guide. Three steps (choose a transactional provider, configure an SMTP plugin against it, publish SPF and DKIM on the domain) and the pipeline goes from unreliable default to authenticated production-grade.