Skip to content
Plugin PantryPlugins

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

  1. Activate the plugin. Go to Plugin Pantry > Mail.
  2. Tick Enable SMTP.
  3. 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.
  4. Fill in Username and Password if the server requires authentication (leave Authentication ticked, which is the default).
  5. 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.
  6. 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 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

  1. wp_mail_failed fires at priority 20, after the fallback module has had its one attempt at priority 10.
  2. If neither an address nor a webhook is set, nothing happens.
  3. If the transient pantry_pro_mail_alert_sent exists, nothing happens. That is the hour.
  4. 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.
  5. The email goes out through wp_mail() and the webhook through wp_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 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 to wp_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 more wp_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

  1. wp_mail_failed fires. If a retry is already in flight, or the fallback is off, or it has no host, nothing happens.
  2. The module adds a phpmailer_init callback 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.
  3. wp_mail() is called again with the same recipients, subject, body, headers and attachments.
  4. The callback, the log filter and the PHPMailer instance are all removed again in a finally block, 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 to fallback for 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 to wp_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_failed is 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 on wp_mail_succeeded and wp_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|failed filters 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 (see class-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_page at position 31, one past Mail's own menu order, and the module adds its slug to the shared menu's children list 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

The plugin