Guide

WordPress sending hundreds of emails per hour — detecting registration spam before the host suspends you

You wake up to a support thread: customers can't reset their passwords, the contact form throws a "we couldn't deliver your message" error, and your hosting provider's status page lists your account as "outbound mail temporarily restricted.

1. Problem

You wake up to a support thread: customers can't reset their passwords, the contact form throws a "we couldn't deliver your message" error, and your hosting provider's status page lists your account as "outbound mail temporarily restricted." Or worse: "account suspended pending review."

Five minutes ago everything worked. The WordPress dashboard shows nothing. Posts are publishing, plugins are active, no PHP errors on screen. The "wordpress sending too many emails registration spam" pattern is every wordpress hosting blocked outbound smtp incident — no admin notice, no red banner. The only outward sign is that wp_mail() is returning false everywhere it's called. This usually surfaces as a sustained spike in wp_emails_total 30 to 90 minutes before the host pulls the plug.

If you opened the WordPress users page now, you'd see rows and rows of new accounts created in the last hour. Different usernames, mostly freemail domains, all within seconds of each other. Each one triggered a "welcome" email, a password notification, or a confirmation link — and each wp_mail() call counts against the host's outbound SMTP quota that, on most shared and managed hosts, caps between 100 and 500 messages per hour before silent rate-limiting kicks in.

2. Impact

Hosting providers don't warn you before they throttle outbound mail. Most managed-WP and shared hosts (SiteGround, Bluehost, Hostinger, Kinsta on certain plans, GoDaddy) silently rate-limit accounts that exceed roughly 200 messages per hour. After the soft limit, wp_mail() returns false with no fatal — WordPress treats failed mail as non-fatal by design. After the hard limit, the host suspends outbound SMTP for a 24-hour cooldown, and many providers escalate to full account suspension for a 48–72 hour "policy review."

The concrete cost stack:

  • Real users locked out. Password reset emails fail silently. Members can't log in. WooCommerce order receipts never arrive — disputed-charge tickets follow within a week.
  • Lead pipeline dead. Contact-form submissions go nowhere. A B2B site can lose a full day of inbound leads — typical cost is $500–$3,000 in deferred pipeline per outage day.
  • Hosting suspension. A 48-hour suspension on a transactional site typically costs $5k–$20k in revenue plus migration time if you have to move providers under pressure.
  • Sender reputation. Even a few percent bounce rate drops your domain's sender reputation. Real emails to real customers land in spam folders for weeks afterward.

The reverse-causal trap: by the time the support tickets arrive, the original cause is already two hours old and the host is two hours into the throttle window.

3. Why It’s Hard to Spot

WordPress shows you nothing. No admin notice says "you've sent 387 emails in the last hour." wp_mail() returns a boolean, and most plugins ignore it — a failed send is a silent return. The "Site Health" panel doesn't track outbound mail volume.

The hosting dashboard is similarly blind. cPanel's "Email Deliverability" shows per-domain reputation, not per-site send rate. Managed-WP hosts surface outbound mail metrics on a 24-hour rollup — by the time the daily report says "you sent 4,200 emails yesterday," the throttle has already fired.

Spam registrations don't look like an attack. They look like users. Each request to /wp-login.php?action=register returns a 200, the database accepts the row, wp_mail() fires. There's no 5xx, no PHP fatal, no nginx error log entry. Uptime monitors stay green. The only place the truth lives is in the rate of change of wp_emails_total and wp_user_lifecycle_total together — and neither is something WordPress surfaces natively.

4. Cause

Open user registration is enabled (Settings → General → Anyone can register), or a membership plugin (BuddyPress, Ultimate Member, MemberPress, LearnDash) exposes a registration endpoint. A botnet finds it — usually within 24–72 hours of going live — and starts pushing rapid-fire registrations.

Each registration triggers wp_create_user()wp_new_user_notification(), which calls wp_mail() twice: once to the new user, once to the admin. At 200 spam registrations per hour, that's 400 wp_mail() invocations — already over most hosts' soft limit.

The Logystera WP plugin emits wp_emails_total from a hook on wp_mail (success and failure paths) and wp_user_lifecycle_total from user_register, password_reset, and delete_user. The signal you watch is rate(wp_emails_total[5m]). The textbook fingerprint of registration spam is the email spike correlated with wp_user_lifecycle_total{action="created"} — the metric definition's own explanation field calls this out: spike correlated with wp_user_lifecycle_total indicates registration spam.

If wp_emails_total spikes alone, you're looking at a different problem (newsletter blast, password-reset campaign, contact-form abuse). If they spike together, you have spam registrations.

5. Solution

5.1 Diagnose (logs first)

The diagnostic flow has three steps: confirm the email volume spike, confirm the registration spike happened in the same window, and find the entry point.

1. Confirm wp_emails_total is elevated. If you have access to PHP error logs and the plugin debug channel:

# wp-content/debug.log captures wp_mail attempts when WP_DEBUG_LOG is on
grep -c "wp_mail" /var/www/wp-content/debug.log
tail -n 200 /var/www/wp-content/debug.log | grep -E "wp_mail|user_register"

If WP_DEBUG_LOG isn't on, fall back to the mail server's queue. Postfix, Exim, and sendmail all log every accepted message with a timestamp:

# Per-minute outbound count, last 60 minutes — surfaces wp_emails_total spike
journalctl --since "1 hour ago" -u postfix | \
  grep "from=<wordpress@" | awk '{print $1, $2, $3}' | \
  awk '{print $3}' | cut -d: -f1,2 | sort | uniq -c | sort -k1,1nr | head

A healthy WP site emits 1–10 emails per hour outside of newsletter blasts. If you see 80+ in a single 5-minute window, that's the wp_emails_total spike.

2. Cross-reference with wp_user_lifecycle_total. This is the diagnostic that distinguishes registration spam from every other email-volume issue:

-- Connect to WP DB. Grouped by minute, last 2 hours.
SELECT DATE_FORMAT(user_registered, '%Y-%m-%d %H:%i') AS minute,
       COUNT(*) AS new_users
  FROM wp_users
 WHERE user_registered > NOW() - INTERVAL 2 HOUR
 GROUP BY minute
 ORDER BY minute DESC;

If you see rows like 2026-04-27 14:03 | 47, 14:04 | 52, 14:05 | 38 — that's wp_user_lifecycle_total{action="created"} firing 40+ events per minute. Real human registrations almost never exceed 3–5 per minute even on a busy launch day. The minute-by-minute pattern correlated to the same window as the email spike is the signal.

3. Time-correlate with a real-world event. When did this start? Three common windows:

# Did the registration page get scraped recently? Check access logs for /wp-login.php?action=register
awk '$7 ~ /register/ {print $4}' /var/log/nginx/access.log | \
  cut -d: -f1-2 | sort | uniq -c | tail -n 30

# Did anyone disable a CAPTCHA/anti-spam plugin in the last 48h?
grep -E "deactivated|activated" /var/www/wp-content/debug.log | tail -n 50

# Was there a published blog post or social mention that sent referrer traffic?
awk '$11 ~ /facebook|twitter|hn|reddit/ {print $4}' /var/log/nginx/access.log | tail -n 100

The most common trigger pattern: someone updated or deactivated an anti-spam plugin (Akismet, Wordfence, hCaptcha for WP) within the previous 24 hours. The bots that have been probing your registration endpoint daily are now getting through. If the first wp_user_lifecycle_total spike lines up with a plugin deactivation timestamp, that correlation is the story.

5.2 Root Causes

Each cause maps to a specific mechanism and a specific signal pattern. Prioritized by frequency.

  • Open registration with no CAPTCHASettings → General → Anyone can register is enabled, default role is Subscriber, no challenge on the registration form. Produces a sustained wp_emails_total spike (50–500/hour) tightly correlated with wp_user_lifecycle_total{action="created"}. The smoking-gun pattern is the correlation coefficient — both signals rise and fall together, minute-by-minute.
  • Anti-spam plugin disabled or misconfigured — Akismet API key expired, Wordfence updated and the registration challenge module reset to off, hCaptcha deactivated for performance reasons. Produces the same correlated wp_emails_total + wp_user_lifecycle_total spike, but with a recent plugin state change preceding it.
  • Membership plugin endpoint exposed — BuddyPress, Ultimate Member, MemberPress register a custom URL like /register/ or /sign-up/ that the bots find independently of /wp-login.php?action=register. Produces the correlated spike plus a clean signature in the access log on the custom path.
  • wp_mail() triggered by something else, not registrations — newsletter plugin misfire, password-reset attack against existing users, contact-form storm. Produces a wp_emails_total spike without a matching wp_user_lifecycle_total rise. If wp_auth_attempts_total{result="failure"} is also spiking, you're seeing a credential-stuffing campaign hitting password-reset, not registration spam — different fix.
  • REST API registration endpoint/wp-json/wp/v2/users was inadvertently left open to anonymous POSTs (some custom code paths do this). Produces the same metric pattern but with a totally different access-log fingerprint, hitting /wp-json/wp/v2/users instead of /wp-login.php.
  • wp_email_by_type_total{type="new_user"} is the discriminator — if this label dominates the email volume during the spike, you're confirmed on registration; if password_changed or comment_notification dominates, the cause is different.

5.3 Fix

Order matters: stop the bleeding, clean up the data, harden the endpoint.

Stop the bleeding (5 minutes):

  1. Turn off open registration. Settings → General → Anyone can register → uncheck. For membership plugins, find the equivalent toggle.
  2. If registration is required for the business: add a CAPTCHA now. hCaptcha free tier or Cloudflare Turnstile installs in under 5 minutes. Pick the fastest to deploy, not the "best."
  3. Add manual approval as a stopgap (User Approve plugin or a 5-line pre_user_registered filter setting user_status = 2). Keeps the form working while breaking the auto-email path.

Clean up the spam users (10 minutes):

# WP-CLI cascades through wp_usermeta and reassigns content cleanly
wp user delete $(wp user list --role=subscriber \
  --registered_after="2026-04-27 14:00" \
  --registered_before="2026-04-27 16:00" --field=ID) --yes --reassign=1

Sample 10 rows manually first — different names, no posts authored, no comments. Keep a CSV export in case a real customer was caught.

Harden (do this same day):

  1. Real anti-spam: Akismet (paid for commercial) or hCaptcha Pro on the registration form.
  2. Rate-limit /wp-login.php?action=register at the edge (Cloudflare Rate Limiting or fail2ban). One registration per IP per hour is reasonable.
  3. Add a server-side honeypot field — a hidden input real browsers leave empty and most bots fill in.
  4. Configure a transactional email provider (Postmark, SendGrid, Resend) via SMTP plugin. Improves deliverability AND moves outbound out of the host's quota so the next incident doesn't suspend you.

5.4 Verify

Two things must hold simultaneously: wp_emails_total returns to baseline AND wp_user_lifecycle_total{action="created"} returns to baseline. Both, not either.

# Outbound mail rate should drop to baseline within 15 minutes of the fix
journalctl --since "15 minutes ago" -u postfix | grep -c "from=<wordpress@"

# New-user creation rate should be near zero
mysql -e "SELECT COUNT(*) FROM wp_users WHERE user_registered > NOW() - INTERVAL 15 MINUTE;"

Healthy baseline for wp_emails_total on a typical small-to-mid WP site is 5–20 emails per hour (password resets, contact-form submits, comment notifications). Larger sites with active newsletter or e-commerce traffic can sit at 50–200 per hour as normal — know your own number before declaring "fixed."

Healthy baseline for wp_user_lifecycle_total{action="created"} is 0–5/hour for B2B/community sites, 5–30/hour for fast-growing membership sites. Sustained over 50/hour is anomalous outside of a launch event.

If wp_emails_total drops but wp_user_lifecycle_total is still firing 20+ per minute, the spam is still landing — your CAPTCHA isn't blocking the bots, the email path is just broken (host throttle still active). Don't declare victory until both signals are at baseline for 30 minutes under normal traffic.

6. How to Catch This Early

Fixing it is straightforward once you know the cause. The hard part is knowing it happened at all.

This issue surfaces as wp_emails_total.

Everything you just did manually — count wp_mail invocations in the mail queue, cross-reference with wp_users.user_registered, time-correlate with the most recent plugin change — Logystera does automatically. The WP plugin emits wp_emails_total on every wp_mail and wp_user_lifecycle_total on every user_register event, both delivered to the gateway in real time. The processor evaluates a rate-of-change rule on wp_emails_total AND a correlation rule against wp_user_lifecycle_total{action="created"} — both signals rising together within a 5-minute window fires the alert.

!Logystera dashboard — wp_emails_total over time wp_emails_total rate, last 6h — sustained spike at 14:03 UTC, immediately after the hCaptcha plugin was deactivated for a "performance test."

The rule that fires is id 217 — WordPress email rate anomaly with registration correlation, severity warning at 50/hour, escalates to critical at 150/hour. Threshold tuned to fire before the typical hosting provider's soft throttle (200/hour) so you have a 30–60 minute window to act before outbound mail starts failing for real users.

!Logystera alert — WordPress registration spam suspected Critical alert fires within 60s of correlated spike: wp_emails_total at 187/hour, wp_user_lifecycle_total{action="created"} at 92/hour.

The alert payload includes the rate of wp_emails_total, the rate of wp_user_lifecycle_total, the dominant wp_email_by_type_total label (so you know whether new-user emails or something else is driving the spike), and the affected entity. That's enough to decide the root cause from §5.2 — registration spam vs. newsletter misfire vs. password-reset attack — from the alert body alone, before you open a terminal.

The fix is simple once you know the problem. The hard part is knowing it happened at all. Logystera turns this kind of failure from a hosting-suspension emergency at 04:30 into a 60-second notification at 03:00 — with the email rate, the registration rate, and the time-window that proves it.

7. Related Silent Failures

  • wp_auth_attempts_total{result="failure"} spike — credential stuffing against /wp-login.php. Different signal, same family. Often precedes registration spam by a few hours as bots probe defenses.
  • wp_email_by_type_total{type="password_changed"} spike — password-reset abuse, distinct from registration spam. Bots trigger lostpassword for every email in a list to confirm which addresses exist on your site.
  • wp_emails_total spike WITHOUT wp_user_lifecycle_total correlation — newsletter plugin misfire, contact-form storm, or transactional email loop. Same throttle risk, different fix.
  • wp_state_change on plugin deactivation — the upstream cause for most "anti-spam plugin disabled" incidents. Watching wp_state_change for unexpected deactivations of Akismet/Wordfence/hCaptcha catches the cause an hour before the email spike.
  • Outbound SMTP failures from host throttlewp_emails_total{result="failed"} rising while wp_emails_total{result="sent"} falls is the signature of the host's throttle kicking in. By that point, you're already 30+ minutes into the incident.

See what's actually happening in your WordPress system

Connect your site. Logystera starts monitoring within minutes.

Logystera Logystera
Monitoring for WordPress and Drupal sites. Install a plugin or module to catch silent failures — cron stalls, failed emails, login attacks, PHP errors — before users report them.
Company
Copyright © 2026 Logystera. All rights reserved.