A client site running on an AWS EC2 instance lost every transactional email at once. Account verification emails never went out, password resets never arrived, notifications stopped cold. Everything died at the same time. And this was not the silent kind of failure: the moment I attached a logger to the sending path, wp_mail() threw a very explicit message: Could not instantiate mail function.
An honest error should be good news. But the message is still confusing the first time you meet it. Which mail function? Instantiate what? The code had not changed at all.
Dead end one: blaming my own code
My first reflex was the standard one: something must be wrong in the code. I re-read the email sending path in the theme, checked the wp_mail() arguments, checked headers, checked whether any plugin was touching the mailer. All clean. The theme code was correct, called with correct data, and the same error fired even for the most trivial test email. This was not an application-level bug.
Dead end two: classic SMTP with an App Password
If the default transport is broken, the classic move is obvious: point WordPress at Gmail SMTP with user and password auth, usually through an App Password. The client was on Google Workspace, so in theory it was just generate an App Password, drop in the credentials, done.
Except: no. In the client's Google Workspace organization, App Passwords were disabled at the admin level. The entire user-password authentication path for SMTP was effectively shut. The route every tutorial recommends first was locked tight by org policy.
The root cause: a raw EC2 has no mail carrier
This is where the error message finally made sense. In WordPress, wp_mail() delegates delivery to PHPMailer, and by default PHPMailer uses PHP's mail() function. mail() itself sends nothing to the internet; it just hands the message to a local mail transport on the server, typically a sendmail binary or postfix. Could not instantiate mail function means exactly that: PHPMailer tried to invoke the local transport, and the transport does not exist.
And it truly did not exist. A raw EC2 instance ships with no mail transport at all. No sendmail, no postfix, nothing that could accept mail from PHP.
which sendmail
# empty, no outputOn managed hosting you never think about this layer because the host ships a built-in relay, so email "just works". On EC2 you start from a bare server, and the ability to send email is infrastructure you have to provide yourself.
The fix: Google Workspace SMTP relay, authenticated by IP
With App Passwords dead, I needed a path that requires no password at all. Google Workspace has one: the SMTP relay service, which can authenticate by the sender's IP address instead of credentials.
The setup has two sides. Google side first: in the Admin console, open the Gmail settings, go to Routing, then SMTP relay service. There I added the EC2's static IP to the allowlist. From that point, Google trusts connections coming from that IP without any user or password.
WordPress side: point PHPMailer at the relay through the phpmailer_init hook:
add_action( 'phpmailer_init', function ( $mailer ) {
$mailer->isSMTP();
$mailer->Host = 'smtp-relay.gmail.com';
$mailer->Port = 587;
$mailer->SMTPAuth = false;
$mailer->SMTPSecure = 'tls';
$mailer->setFrom( 'no-reply@yourdomain.com' );
}, 5 );Note SMTPAuth = false: there are no credentials anywhere in this configuration. The connection goes to smtp-relay.gmail.com on port 587 with STARTTLS, and the sender's identity is the server's IP itself. The From address uses no-reply on the client's domain. Priority 5 makes the hook run early, before any other code gets a chance to touch the mailer configuration.
The test result: the relay answered 250 OK, and the email did not just send, it landed in the Primary inbox tab, not in spam. Account verification, password resets, and notifications all came back to life.
The takeaway
Could not instantiate mail functionalmost always means the server has no local mail transport. It is an infrastructure problem, not a bug in your code.- A raw EC2 provides nothing for email. If a site moves from managed hosting to EC2, outbound email belongs on the migration checklist.
- App Passwords can be disabled at the Google Workspace organization level. Do not build a plan on the assumption that the user-password path is always available.
- The Google Workspace SMTP relay with an IP allowlist is a clean passwordless path:
smtp-relay.gmail.com, port 587,SMTPAuthfalse, STARTTLS, wired throughphpmailer_init.
