Plugin compromised: do I rotate my SMTP keys?

Yes. If code you didn’t write ran on your WordPress site, treat every mail credential the site holds as copied: the SMTP password, the email provider’s API key, and the Google or Microsoft connection your mailer plugin uses, including the ones it says it encrypts. Cut the old credentials off now; put new ones on the site only after it is clean, or the new ones leak too.

In October 2026, Rank Math PRO 3.0.123, if downloaded or updated between 20:02 UTC on 15 October and 07:12 UTC on 16 October, carried malware (3.0.124 is clean); its notice says to replace “SMTP credentials, API keys, and other secrets.” On 17-18 October, TeamUpdraft found unauthorised access to its website and believes its customer database was accessed, including UpdraftCentral keys that “can send commands to your connected sites” (one r/Wordpress owner reported a backdoor plugin arriving that way). Its checklist doesn’t mention mail.

Why the plugin’s encryption doesn’t protect the key

WordPress has no sandbox between plugins: any plugin can read any option in the database and any constant in wp-config.php. Core loads each active plugin with a plain include_once (in wp-settings.php) into one PHP process, after wp-config.php has run. It’s also why any plugin can read the emails your site sends.

Each mailer stores its credentials differently, which matters only against someone holding a copy of the database:

Plugin (version read) What protects the stored credentials
WP Mail SMTP 4.10.2 Only the SMTP password is encrypted (libsodium), keyed by WPMS_CRYPTO_KEY or, by default, a random key in the wp_mail_smtp_mail_key option of the same database. API keys, OAuth secrets and tokens are stored as entered.
FluentSMTP 2.4.1 SMTP password, API keys, client secrets and Gmail/Outlook tokens are AES-256 encrypted by default, with LOGGED_IN_KEY and LOGGED_IN_SALT from wp-config.php (or FluentSMTP’s own constants). Not when a connection is set to store readable text, for tokens saved by older versions until re-saved, or on servers without PHP’s openssl.
Post SMTP 4.0.2 Passwords and API keys are base64-encoded: encoding, not encryption. OAuth tokens are stored as issued.

Encryption at rest stops someone holding only a copy of the database. Code inside WordPress has the database and the key. A wp-config.php constant, or the Secrets API scheduled for WordPress 7.2 (December 2026), changes where the secret sits, not who can reach it.

What to replace, by credential type

What the plugin holds Cut off now Replace after clean-up
Mailbox password (your host, Zoho, Outlook.com) Change the password at the mail host. Enter it in the plugin. Other devices on that mailbox need it too.
API key or SMTP key (SendGrid, Mailgun, Brevo) Delete the key in the dashboard. On Mailgun, create the replacement first (kept off the site) and reset per-domain SMTP credentials. Brevo’s SMTP keys are separate from its API keys. Enter the new key.
Postmark server token (the default SMTP login) Generate a new token, keep it off the site, delete the old one (generating doesn’t remove it). If the plugin uses a per-stream SMTP Token (Outbound Stream > Settings), delete that too. Enter the new token.
Amazon SES SMTP credentials Delete the SMTP user in IAM. If the plugin uses the SES API, delete its IAM access key, which may allow far more than sending. Create new credentials.
Gmail app password (Google’s 16-digit passcode for one app) Remove it on the App Passwords page. A Google password change revokes all of them. Create a new app password.
Gmail or Workspace OAuth Remove the app at myaccount.google.com/permissions. A new client secret isn’t enough: Google lists it as optional on a refresh. Reconnect the plugin.
Microsoft 365 OAuth Revoke sessions on the mailbox user in the Entra admin center (or Revoke-MgUserSignInSession) and delete the client secret under Certificates & secrets. This signs them out of Outlook, Teams and every other Microsoft app; warn them. Add a new client secret and reconnect.

The Microsoft row has the trap. In Microsoft’s revocation table, a refresh token issued to a server-side app “stays alive” when the user changes their password, until an admin revokes it. Google is the opposite: a password change kills Gmail-scope refresh tokens. Deleting the client secret matters because a web app needs it to redeem a refresh token; access tokens already issued run out within 60 to 90 minutes by default.

A stolen token can do what its scope allows. FluentSMTP and Post SMTP ask for Gmail’s gmail.send (“Send email on your behalf”); FluentSMTP’s Outlook mailer asks only for Mail.Send and Mail.Send.Shared (older connections keep any wider scopes granted). WP Mail SMTP’s Gmail mailer asks for https://mail.google.com/: “Read, compose, send, and permanently delete all your email from Gmail.” If it was connected, assume the mailbox was read, reset links included. For the Pro-only Outlook mailers of WP Mail SMTP and Post SMTP, Entra’s API permissions page for the app shows what was granted.

The order: revoke now, reissue after the clean-up

Revoking needs nothing from the site, so do it the moment you know. The replacement waits until the backdoor is gone (Rank Math’s notice lists the clean-up steps); saved any earlier, it is stolen along with the old one.

Until then the site sends no mail at all: no order confirmations, password resets or form notifications.

What to check in the provider’s logs

Export the logs first, from the earliest date the malicious code could have run, because retention is short. Mailgun keeps logs 1 day on Free and Basic, 5 on Foundation, 30 on Scale; SendGrid’s Activity Feed reaches 30 days only with its paid history add-on; Postmark keeps 45 days by default; SES has no message log unless event publishing was set up beforehand. Microsoft 365 message trace reaches back 90 days, Google Workspace’s Email Log Search 30 days for open searches.

  • The activity log. Recipients, subjects or volumes your site doesn’t produce (not the Sent folder, which Graph’s sendMail can skip).
  • Account changes. Sending domains, sender signatures, API keys or users you didn’t add. On a mailbox, forwarding, filters and delegates.
  • Bounce and complaint numbers. A jump in either, or suppression entries you don’t recognise, fits someone else sending on your account.

Limiting what the next stolen key can do

Neither habit stops a compromised plugin reading the credential the site holds.

  • Use a send-only key, never a full-access one. Mailgun’s primary key can “perform all CRUD operations”, and SendGrid’s Full Access reaches the key-management endpoints: a stolen one can create another key before you revoke it. Mailgun’s Domain Sending Keys only post to /messages and /messages.mime for one domain; SendGrid grants scopes one by one, mail.send among them; SES SMTP users get a policy allowing only ses:SendRawEmail.
  • One key per site, named after the site, so revoking it leaves the others sending and the log shows which site sent what. On Postmark, one server per site.

Both also help when the leak is at the provider, the case in the Mandrill security question.