Documentation / Pantry Mail
Pantry Mail
Contents: SMTP delivery for wp_mail, with a test button.
SMTP delivery for wp_mail, with a test button.
Point WordPress email at a real SMTP server and confirm it works with one test message. Provider presets fill in the host and port. The last twenty sends are listed with their result so you can see what went wrong.
Built to the Pantry Standard: one settings screen, one option for settings plus one bounded option for the log, no telemetry, no external requests other than the SMTP connection you configure.
Usage
- Activate the plugin. Go to Plugin Pantry > Mail.
- Tick Enable SMTP.
- Choose a Provider preset to fill in the host, port and encryption, or leave it on Custom / other and type them in yourself. You can still edit any of the filled-in fields by hand.
- Fill in Username and Password if the server requires authentication (leave Authentication ticked, which is the default).
- Optionally set a From name and From email under From address. These are used whenever a message would otherwise show WordPress's own default sender; tick Force on every message to override even a from address that another plugin sets explicitly.
- Save changes, then use Send a test email to confirm delivery. The result appears as a notice at the top of the screen, and every attempt (test or otherwise) is added to the Recent mail log below.
Storing the password outside the database
Add this to wp-config.php instead of typing the password into the settings
screen:
define( 'PANTRY_MAIL_PASSWORD', 'your-smtp-password' );
When this constant is defined, it is always used for the SMTP password and the Password field on the settings screen is ignored. The screen shows a notice when the constant is active.
Settings reference
All settings are stored in one option, pantry_mail_settings.
| Setting | Type | Default | Notes |
|---|---|---|---|
| Enable SMTP | checkbox | off | When off, WordPress sends mail with PHP's built-in mail() as usual. |
| Provider preset | select | Custom / other | UI convenience only; only the fields below are actually used to send mail. |
| SMTP host | text | (empty) | Required for SMTP to activate; if empty, the plugin makes no changes to PHPMailer. |
| SMTP port | number | 587 | 1-65535. |
| Encryption | select | TLS | None, SSL or TLS. Maps to PHPMailer's SMTPSecure. |
| Authentication | checkbox | on | When off, Username/Password are still set on PHPMailer but SMTPAuth is false. |
| Username | text | (empty) | |
| Password | password | (empty) | Ignored when PANTRY_MAIL_PASSWORD is defined; see above. |
| From name | text | (empty) | Applied when a message would otherwise use WordPress's own default name, or always when Force is on. |
| From email | (empty) | Applied when a message would otherwise use WordPress's own default wordpress@... address, or always when Force is on. |
|
| Force on every message | checkbox | off | Makes the From name/email above unconditional. |
The mail log
Every message WordPress attempts to send through wp_mail() is recorded,
whether or not SMTP is enabled, so the log is also useful for diagnosing
plain mail() delivery. Stored in its own bounded option, pantry_mail_log,
holding at most the 20 most recent attempts, newest first. Each entry has a
time, recipient, subject, result (sent or failed) and, for failures, the raw
PHPMailer error text. There is no separate log setting to turn this off.
Filters and actions
pantry_mail_settings_after
Fires at the end of the settings screen, after the test panel and the mail log, and outside this plugin's own settings form. The settings screen instance is passed as the only argument. Added in 1.1.0 so an add-on can put a panel of its own on this screen without the plugin knowing anything about it.
add_action(
'pantry_mail_settings_after',
function ( $settings ) {
echo '<section class="pantry-panel"><h2>My panel</h2></section>';
}
);
Because the action fires after the form is closed, a panel that needs to save something must render its own form and register its own settings group. Adding an option to this plugin's group without putting the field inside this plugin's form would make one save overwrite the other.
Apart from that action, the plugin hooks into five WordPress core hooks:
phpmailer_init— sets the SMTP connection details on PHPMailer.wp_mail_from/wp_mail_from_name— applies the From address override.wp_mail_succeeded/wp_mail_failed— writes the log entry.
The Send a test email button posts to admin-post.php with
action=pantry_mail_send_test, a nonce, and a pantry_mail_test_to field. On
completion it redirects back to the settings screen with pantry-mail-test
set to sent or failed in the URL.
What it does not do
See does_not in registry/plugins.json for the published list: no OAuth
flows, no API-based sending (SMTP only), no sending queue or retries, no open
or click tracking, no full searchable log, no external requests other than
the SMTP connection itself.
Note for reviewers
The WordPress.org Plugin Check tool reports the Amazon SES preset's hostname (email-smtp.us-east-1.amazonaws.com) as "offloaded content". It is a configuration value used to pre-fill the SMTP host field, not an asset loaded from a remote server. The plugin loads no scripts, styles or images from anywhere but its own folder.
Pro modules
What Pantry Pro adds to this plugin. None of it is inside the free plugin, and a module only runs when the free plugin is active.
Mail: Failure alerts
Pantry\Pro\Modules\Mail\Alerts — adds to Pantry Mail (1.1.0 or newer).
Tell someone when WordPress cannot send a message, without turning a broken mail server into a flood.
What it adds
- An email, a webhook call, or both, on
wp_mail_failed, carrying the site, the time, the recipients, the subject and the text the mail server gave. - A rate limit of one alert an hour, held in a transient.
- A flag that stops the alert email's own failure from starting another alert.
How it behaves
wp_mail_failedfires at priority 20, after the fallback module has had its one attempt at priority 10.- If neither an address nor a webhook is set, nothing happens.
- If the transient
pantry_pro_mail_alert_sentexists, nothing happens. That is the hour. - The transient is set before anything is sent, so a burst of failures cannot become a burst of alerts even if the sending itself fails.
- The email goes out through
wp_mail()and the webhook throughwp_remote_post(). For the whole of that, a static flag is up: the alert email will very likely fail too on a site whose mail is broken, and that failure is logged like any other but never alerted about.
Settings
On Plugin Pantry > Mail, in the Pantry Pro panel, under Failure alerts.
Stored in the option pantry_pro_mail_alerts, saved by that panel's own
Save Pro settings button.
| Setting | Type | Default | Notes |
|---|---|---|---|
| Alert email | (empty) | Where to write. Use an address on a different mail account from the one that is failing, or the alert fails with everything else. | |
| Webhook URL | url | (empty) | Optional. Posted as JSON. |
Both blank means no alerts. There is no separate on and off switch.
The webhook
POST with Content-Type: application/json and this body:
{
"site": "Example Site",
"url": "https://example.com/",
"time": "2026-09-12T19:17:46+00:00",
"to": "someone@example.com",
"subject": "Password reset",
"error": "SMTP Error: Could not connect to SMTP host."
}
time is UTC in ISO 8601. The request has a ten second timeout and its answer is
not read; a webhook that is down costs one request and nothing else. This is the
only external request the module makes, and it only happens when a URL is set.
The email
Subject: [Site name] A message could not be sent. The body carries the same
facts as the webhook, in plain text, and one line saying no further alert will be
sent for an hour.
Hooks
wp_mail_failed(priority 20) — sends the alert.wp_mail— used to remember the message as it was handed towp_mail(), so the alert names the right recipients.
The module adds no hooks of its own.
What it stores
| Thing | Name |
|---|---|
| Option | pantry_pro_mail_alerts |
| Transient | pantry_pro_mail_alert_sent, one hour |
No table, no cron. install() writes the defaults without overwriting an
existing value. uninstall() deletes the option and the transient.
Notes
- The alert email is itself sent with
wp_mail(), so it appears in both mail logs like any other message, and the fallback account will try to deliver it if one is configured. That is deliberate: on a site with a fallback, the alert is the message most likely to get through. - The hour is per site, not per recipient or per error. One alert an hour, then silence until the transient expires.
Mail: Fallback account
Pantry\Pro\Modules\Mail\Fallback — adds to Pantry Mail (1.1.0 or newer).
A second SMTP account. When the primary one reports a failure, the message goes out once more through the second server. Once, and only once.
What it adds
- A second set of SMTP fields on the Pantry Mail screen: host, port, encryption, authentication, username and password.
- On
wp_mail_failed, one morewp_mail()call with the same message, with PHPMailer pointed at the fallback server for that call alone. - Which account delivered the message, recorded in the Pro mail log.
How the retry works
wp_mail_failedfires. If a retry is already in flight, or the fallback is off, or it has no host, nothing happens.- The module adds a
phpmailer_initcallback at priority 20, above the free plugin's own callback at 10, so it wins for this one send, and drops the PHPMailer instance WordPress keeps in a global so the retry is built fresh rather than on top of the attempt that just failed. wp_mail()is called again with the same recipients, subject, body, headers and attachments.- The callback, the log filter and the PHPMailer instance are all removed again
in a
finallyblock, whatever happened.
A static flag is set for the whole of step 3, so if the fallback fails too, that failure is logged and left alone. There is no second retry and no loop.
wp_mail() still returns false to the code that called it: the fallback is a
second send, not a rewritten answer to the first.
Settings
On Plugin Pantry > Mail, in the Pantry Pro panel, under Fallback account.
Stored in the option pantry_pro_mail_fallback, saved by that panel's own
Save Pro settings button.
| Setting | Type | Default | Notes |
|---|---|---|---|
| Use a fallback | checkbox | off | Nothing is retried while this is off. |
| Fallback SMTP host | text | (empty) | Required. With no host the module does nothing, switch or no switch. |
| Fallback SMTP port | number | 587 | 1-65535. |
| Encryption | select | TLS | None, SSL or TLS. PHPMailer's SMTPSecure. |
| Authentication | checkbox | on | Sets SMTPAuth. |
| Username | text | (empty) | |
| Password | password | (empty) | Ignored when PANTRY_MAIL_FALLBACK_PASSWORD is defined; see below. |
Keeping the password out of the database
The same convention the free plugin uses for the primary account:
define( 'PANTRY_MAIL_FALLBACK_PASSWORD', 'your-fallback-password' );
When that constant is defined in wp-config.php it is always used, and the
Password field says so.
Hooks
wp_mail_failed(priority 10) — starts the one retry.phpmailer_init(priority 20) — added only for the duration of that retry.pantry_pro_mail_log_provider— filtered tofallbackfor the duration of that retry, so the log row says which account took the message. The filter is the full log module's; without that module the fallback still works, there is just nowhere for it to be recorded.wp_mail— used to remember the message as it was handed towp_mail(), so the retry keeps the Cc, Reply-To and Content-Type headers that WordPress has already taken out of the array the failure hook receives.
The module adds no hooks of its own.
What it stores
| Thing | Name |
|---|---|
| Option | pantry_pro_mail_fallback |
No table, no cron, no transients. install() writes the defaults without
overwriting an existing value. uninstall() deletes the option.
Notes
- PHPMailer's timeout is set to 20 seconds for the retry. An unreachable fallback server costs that much on top of whatever the primary cost.
- Every
wp_mail_failedis retried, including a failure that no second server can fix, such as an invalid address. The retry is bounded at one, so the cost of that is one extra attempt.
Mail: Full log
Pantry\Pro\Modules\Mail\Full_Log — adds to Pantry Mail (1.1.0 or newer).
The free plugin keeps the last twenty attempts in an option: time, recipient, subject, result, error. This module keeps every attempt in a table of its own, with the headers and the body, on a screen you can search and filter, and it can send any stored message again.
What it adds
- A table,
{prefix}pantry_pro_mail_log, written onwp_mail_succeededandwp_mail_failed: time, recipients, subject, headers, body, result, error and which account sent it. - A Mail log screen under Plugin Pantry, directly after Mail, with a search box (recipients, subject, body and error text), a sent-or-failed filter, and twenty rows a page.
- A view of one whole message: every field, the headers and the body as they were, and the error the server gave.
- Send again on every row and on the message view. It calls
wp_mail()with the stored recipients, subject, body and headers, and the new attempt is logged like any other. - A retention setting and a daily purge that honours it.
The free plugin's own twenty-row log is left exactly as it is. Both are written; neither knows about the other.
Settings
On Plugin Pantry > Mail, in the Pantry Pro panel, under Full mail log.
Stored in the option pantry_pro_mail_full_log, saved by that panel's own
Save Pro settings button.
| Setting | Type | Default | Notes |
|---|---|---|---|
| Keep messages for | number | 30 | Days. The daily purge deletes rows older than this. Zero keeps every message until you delete it. Maximum 3650. |
The screen
wp-admin/admin.php?page=pantry-pro-mail-log, capability manage_options.
?s=searches recipients, subject, body and error text.?pantry_pro_mail_result=sent|failedfilters by result.?message=<id>shows one message whole.
Resend posts to admin-post.php?action=pantry_pro_mail_resend&id=<id> with a
nonce tied to that id (pantry_pro_mail_resend_<id>), and checks
manage_options as well. It redirects back to the log with
pantry-pro-mail=resent, failed or not-found.
Hooks
pantry_pro_mail_log_provider (filter)
Filters the account recorded against a row before it is written.
add_filter(
'pantry_pro_mail_log_provider',
function ( $provider, $mail_data, $result ) {
return 'my-api';
},
10,
3
);
The module itself passes smtp when Pantry Mail's SMTP is switched on and has a
host, and mail otherwise. The fallback module filters it to fallback for the
one send it makes. Anything else is shown as stored.
pantry_mail_settings_after (used, not added)
The panel the settings live in is hooked onto this action, which Pantry Mail 1.1.0 added to its settings screen.
What it stores
| Thing | Name |
|---|---|
| Table | {prefix}pantry_pro_mail_log |
| Option | pantry_pro_mail_full_log |
| Cron event | pantry_pro_mail_log_purge, daily |
install() creates the table with dbDelta, writes the default setting without
overwriting an existing one, and schedules the purge if it is not already
scheduled. It is safe to run repeatedly. uninstall() drops the table, deletes
the option and clears the cron event.
Notes
- The table holds message bodies. On a site that mails a lot, keep the retention short; the setting is the only brake and the purge is the only deletion.
- Recipients, headers and bodies are recorded from what
wp_mail()was actually asked to send (seeclass-message.php), not from the part-processed array the hooks receive, so a resent message keeps its Cc, Reply-To and Content-Type. - The log screen is registered with
add_submenu_pageat position 31, one past Mail's own menu order, and the module adds its slug to the shared menu'schildrenlist so the menu's own sort keeps it directly after Mail.
Comparison
How Pantry Mail compares with the popular plugins that do the same job, and when one of them is the better choice.
Pantry Mail compared with WP Mail SMTP, FluentSMTP, Post SMTP and Easy WP SMTP