Skip to content

EP Newsletter

EP Newsletter is a full newsletter system for PageMotor. Subscribers and lists, campaign composition with draft/test/send, batch delivery with configurable pace, open and click tracking, autoresponder sequences triggered by subscription, and automated content digests. Delivery routes through EP Email so SMTP, from-addresses, and deliverability are managed in one place.

Published by ElmsPark Studio.

Core features:

  • Subscriber management. Add, import from CSV, export, track source (which form they came from).
  • Lists. Named mailing lists with segmentation.
  • Campaigns. Compose, preview, test-send, schedule, send.
  • Batch delivery with configurable batch size and delay between batches, so you don’t overwhelm SMTP providers.
  • Open tracking via invisible pixel; click tracking via link rewriting.
  • Double opt-in with confirmation and welcome emails.
  • Autoresponders — timed sequences of emails triggered when someone subscribes.
  • Automated digests — daily, weekly, or monthly digests of new content on your site.
  • Delivery extensions. Swap in Mailgun (the recommended provider) or a custom driver.
  • Webhook handling for bounces and complaints from delivery services.
  • PM API control (new in 1.2.0) — manage lists, subscribers, campaigns and autoresponders over the PM API and MCP, no direct database access.
  • Unified send log (new in 1.2.0) — every send recorded in one table with a type/source column for deliverability diagnosis.
  • PageMotor 0.8.2b or later
  • EP Email (required)
  • EP Suite base class

Optional:

  • EP Newsletter Mailgun for Mailgun delivery (the recommended provider).
  • EP GDPR for consent logging on subscription.
  • EP Affiliate for affiliate-source tracking on subscription.
  1. Install EP Email.
  2. ep-newsletter.zip comes with an EP Suite licence — ElmsPark supplies it directly (see EP Suite plugins); after install it updates through your site’s Updates screen.
  3. Upload via Plugins → Manage Plugins. Activate.
  4. Work through the settings sections below.
ShortcodePurpose
[newsletter-form]Subscription form. Attributes: title, button, name_field=true/false, list=slug.
[newsletter-unsubscribe]Unsubscribe page with optional feedback form.
  • Double opt-in. Toggle. If on, subscribers get a confirmation email; only after clicking do they join the list. Strongly recommended.
  • Confirmation email subject and body.
  • Welcome email subject and body (sent after confirmation).
  • Confirmation redirect page. Where to send users after confirming.
  • Default list for forms without an explicit list attribute.
  • Enable. Toggle the shortcode.
  • Success and already-subscribed messages.
  • Rate limiting. Minutes between subscription attempts from the same IP. Catches lazy bots; does NOT stop attackers who rotate through Tor exits or proxy pools — see Abuse prevention below for that.

Four layers of defence against subscription form abuse, layered to catch different attack shapes:

LayerStopsBlind spot
Honeypot fieldNaive bots that fill every fieldSophisticated bots targeting only visible fields
Per-IP rate limitLazy bots hammering from one IPAttackers rotating through Tor exits or proxy pools
Per-domain frequency cap (new in 1.1.27)Subscription-bombing where one victim email domain is signed up to many newsletters at onceSingle-victim, single-mailbox attacks
Disposable email blocklist (new in 1.1.27)Sign-ups from throwaway inbox providers (mailinator, guerrillamail, 10minutemail, ~130 others)Custom-domain disposable services not yet on the list

Per-domain frequency cap. When too many sign-ups arrive for the same recipient email domain within a rolling window, further sign-ups to that domain are silently accepted but never stored. Designed to defeat subscription-bombing attacks: per-IP rate limiting does nothing against them because each request comes from a different IP. The cap watches the email domain instead. Defaults to 5 sign-ups per domain per 24 hours. Silent success (not visible failure) so attackers cannot probe for the threshold.

Disposable email blocklist. Sign-ups from known disposable / throwaway providers are rejected with an honest “please use your regular email address” message. Built-in list covers ~130 providers including the major families (mailinator, guerrillamail, 10minutemail, yopmail, temp-mail, trash). Add your own domains via the Additional Blocked Domains textarea (one per line or comma-separated, # starts a comment). For developers, the ep_newsletter_disposable_domains filter accepts a richer list at runtime.

Scope. Both new checks only fire on the public [newsletter-form] shortcode. Admin-side Add Subscriber, CSV Import, and the subscribe_external() public API (used by EP Booking and similar plugins for legitimate checkout-time sign-ups) are intentionally unaffected.

  • Default template. Base HTML wrapper for campaigns.
  • Batch size and delay. E.g. 100 emails per batch, 30-second delay between. Tune to your SMTP provider’s rate limits.
  • Open tracking and click tracking toggles.
  • Frequency. Daily, weekly, monthly.
  • Send day and time. Read in your site’s timezone — see Timezone.
  • Target list.
  • Subject template. Supports placeholders for date ranges.
  • Email template. Structure of the digest.
  • Page URL. Where [newsletter-unsubscribe] lives.
  • Success message.
  • Feedback collection. Optional — ask why they unsubscribed to improve future content.
  • Driver. EP Email built-in (uses your configured SMTP), Mailgun (via EP Newsletter Mailgun, the recommended provider), or any registered third-party driver.
  • Test connection button.
  • Check bounces button — polls the driver’s bounce API and marks bad addresses.
  1. Compose. Write the subject and body using the campaign editor. Use merge tags like {first_name} for personalisation.
  2. Preview. Render the campaign as it will appear in common mail clients.
  3. Test send. Send just to your own address to check.
  4. Choose recipients. Pick one or more lists, or a segment.
  5. Schedule or send now. A scheduled time is read in your site’s timezone — see Timezone.
  6. Monitor. Open and click rates, bounce count, complaint count all update as the campaign progresses.

An autoresponder sequence is a series of timed emails. Example welcome sequence:

  • Immediately: “Welcome! Here’s what to expect.”
  • +2 days: “Our 5 most useful articles.”
  • +5 days: “A case study you might like.”
  • +10 days: “Ready to go deeper?”

Each email has its own subject, body, and delay after subscription. Created once, runs forever.

Set up an automated weekly digest:

  • “Blog posts published this week” gathers the last 7 days of new content.
  • “New products in stock” pulls from EP Ecommerce Products.
  • Custom query for bespoke content types.

Digests run on the schedule you set and only send if there’s new content to include.

Scheduled campaigns, autoresponder sequences, and automated digests are all delivered by a background queue runner. It fires automatically whenever an admin page loads (throttled to once a minute), which is fine for a busy site. On a quiet site where nobody is signed into the admin, that trigger never runs and scheduled email stalls silently.

(new in 1.1.29) To drive the queue on a quiet site, EP Newsletter exposes a secret-token cron URL:

https://your-site.com/?ep_newsletter_cron=<secret>

The ready-made URL, with a crontab example, is under Settings → Background Processing. Point a system cron job or uptime monitor at it, for example every five minutes:

*/5 * * * * curl -s "https://your-site.com/?ep_newsletter_cron=<secret>" >/dev/null 2>&1

The token is generated once and checked with a timing-safe compare; a wrong or missing token returns HTTP 403 and does no work. Keep the URL private (anyone with it can trigger a send, though it exposes no subscriber data). The admin-page-load trigger still works as a fallback, and the process-queue API action below does the same job for an authenticated caller.

(new in 1.8.0) The times you enter — a campaign’s schedule, the automated digest’s send time and weekly send day — are read in your site’s timezone, set under PageMotor’s Site Settings → Time Zone. The schedule field on the campaign composer names that zone underneath it, so you can always see which clock you are typing in. Type 09:00 and the campaign goes out at 09:00 your time, whatever timezone the server happens to be on.

Stored times are UTC underneath, converted back to your zone whenever they are shown to you. That is what makes the server’s own setting irrelevant: you no longer need to ask your host to change date.timezone, and a server left on UTC is no longer a problem. Some hosting panels, CloudPanel among them, default new sites to UTC — that used to send a Central European site’s campaigns two hours early. It no longer does.

On PageMotor 0.10.3b, which has no Site Settings → Time Zone yet, times are read in the server’s PHP timezone exactly as before. Nothing about what you type changes, and upgrading the core later moves you onto the site setting.

Times already in your database were stored on the server’s local clock. The first admin page load, or the first background queue run, after the update converts them once, automatically. You do not need to do anything, and none of your existing dates should change on screen.

If you have campaigns scheduled or autoresponder sequences in flight across the upgrade, glance at the campaign list afterwards and confirm the scheduled times still read as you intended.

(new in 1.2.0) EP Newsletter registers PM API actions under the EP_Newsletter class, so an agent or script can drive the whole content-to-email pipeline through the PM API and MCP without writing to the database directly. Browse them with list-actions, inspect one with describe-action, then invoke with call-action. Each handler returns the standard result envelope ({success, data} on success, {success:false, reason} on a problem).

Access tiers follow the data each action touches: reads are read-only so an agent can report without write access, writes are admin, and subscribe is producer (a trusted caller).

1.2.1 adds autoresponder edit and delete actions, read actions for the queue and the send log, and a delay_minutes option on email steps so sequences can run sub-hour.

ActionAccessPurpose
list-listsread-onlyAll mailing lists with subscriber counts.
list-subscribersadminSubscribers, filterable by list_id and status, paginated.
list-campaignsread-onlyCampaigns with open and click counts.
list-autorespondersread-onlyAutoresponder series with their email steps.
get-campaign-statsread-onlyOpen and click stats for one campaign.
subscribeproducerSubscribe a contact. Default: trusted caller, active immediately (skips double opt-in and the public abuse checks). Pass double_optin: true (new in 1.5.3) to create the contact as pending and send the confirmation email instead.
unsubscribeadminUnsubscribe by email and cancel pending autoresponder emails.
create-listadminCreate a mailing list.
delete-listadminDelete a list. Subscribers are kept.
create-campaignadminCreate a draft campaign.
update-campaignadminUpdate a campaign’s subject, body, list, or template.
send-campaignadminQueue recipients and start sending now.
schedule-campaignadminSchedule a draft to send at a future time.
create-autoresponderadminCreate an autoresponder series.
add-emailadminAdd a timed email step to a series (delay in days, hours, and minutes).
update-autoresponderadminUpdate a series (name, list binding, or active/paused status).
delete-autoresponderadminDelete a series and all its email steps and pending queue items.
update-emailadminUpdate one email step (subject, body, delays, template).
delete-emailadminDelete one email step and its pending queue items.
get-autoresponder-queueadminRead the autoresponder queue (pending, sent, failed, cancelled) with scheduled times, filterable by subscriber or status.
process-queueadminRun the background queue now: due campaign batches, autoresponder emails, scheduled campaigns, and due digests.
list-send-logadminRead the unified send log, newest first, filterable by send_type.

The API subscribe deliberately bypasses the per-domain cap and disposable-email blocklist that guard the public [newsletter-form], because an authenticated API caller is trusted. This is the same reasoning as the subscribe_external() PHP path that EP Booking uses for checkout-time sign-ups.

(new in 1.5.3) When your integration captures its own opt-in and needs the consent trail, pass double_optin: true to subscribe: the contact is created (or moved back to) pending with a fresh token, the confirmation email is sent through the standard flow, and they only become active when they click the confirmation link — which also fires the welcome email and autoresponders, exactly like a form signup. An already-active contact is never demoted to pending. The response reports status and confirmation_sent alongside subscriber_id; if the confirmation email fails to send, the action returns the failure with the pending subscriber_id so you can retry.

(new in 1.2.0) Every newsletter send is recorded in a single table, ep_newsletter_send_log, whichever delivery route it took (an external driver, EP Email, EP Email’s SMTP class, or the PHP mail fallback). Each row carries a send_type column (campaign, autoresponder, welcome, confirmation, transactional), the recipient, status, the driver used, the provider message id, and the related campaign and subscriber ids. One place to diagnose deliverability, instead of piecing it together from the queue tables. Read it over the API with list-send-log (new in 1.2.1).

“Scheduled campaigns go out at the wrong time”

Section titled ““Scheduled campaigns go out at the wrong time””

On 1.8.0 and later, check Site Settings → Time Zone is your zone. That is the clock the schedule field uses, and the field names the zone underneath it so you can confirm it at a glance.

Before 1.8.0 the schedule was read on the server’s clock, so a server left on UTC fired a Central European site’s campaigns two hours late. If you are still on 1.7.x, updating is the fix. Changing date.timezone on the server was the old workaround and is no longer needed.

If the times are right but the send is simply late, that is the queue runner rather than the clock — see Background processing.

“Campaign paused partway through sending”

Section titled ““Campaign paused partway through sending””

Usually an SMTP error that tripped the driver. Check EP Email’s delivery log. If SMTP is the problem, fix it and click Resume on the campaign.

Open tracking uses an invisible pixel. Gmail, Apple Mail’s privacy protection, and most modern clients proxy images so tracking pixels don’t load per-user. Open rates have become unreliable metrics regardless of plugin.

“Subscribers complain about not receiving confirmation emails”

Section titled ““Subscribers complain about not receiving confirmation emails””

EP Email SMTP config is wrong, or emails are going to spam. Check EP Email’s delivery log. Test with a Gmail address you control.

“I’m hitting my SMTP provider’s rate limit”

Section titled ““I’m hitting my SMTP provider’s rate limit””

Drop batch size, increase batch delay. If you’re regularly sending thousands, consider EP Newsletter Mailgun which handles higher volumes more smoothly.

“Bounce addresses keep receiving campaigns”

Section titled ““Bounce addresses keep receiving campaigns””

Check the bounce check. Click Check bounces manually. If bounces aren’t being flagged, the delivery driver’s bounce webhook isn’t configured.

“Content digest doesn’t include my new page”

Section titled ““Content digest doesn’t include my new page””

Digests only include content matching their query. Check the digest’s content type filter. Drafts and scheduled-but-not-yet-published pages don’t count until they’re actually live.

“Subscription form being subscription-bombed”

Section titled ““Subscription form being subscription-bombed””

Dozens of pending subscribers with random-alphanumeric names and IPs from Tor exit ranges or commercial proxy pools. Your form is on a publicly-harvested list of newsletter forms and a bot is signing one or more victim addresses up to all of them at once. Per-IP rate limiting does nothing here because each request comes from a different IP. Confirm Abuse prevention → Per-Domain Frequency Cap is on (lower the count to 3 or 1 if the attack is concentrated on a single recipient domain) and Disposable Email Blocklist is on. Clean up the pending spam in Subscribers. The frequency cap is a rolling window so once an attack subsides the cap auto-lifts.

“Legitimate user with a temp email is being rejected”

Section titled ““Legitimate user with a temp email is being rejected””

The disposable blocklist working as intended. If you want to allow one specific provider, add it as an exception via the ep_newsletter_disposable_domains filter in site code. To disable the whole blocklist, untick Abuse prevention → Disposable Email Blocklist.

For a quick question about this plugin, EP Support inside your admin is the fastest option. The chat widget sits on every EP plugin settings page and knows which one you’re on, with starter questions and links preloaded for that exact screen.

For anything bigger — a bug report, a feature request, or a “how do I…” that needs a real reply — open a ticket at help.elmspark.com. A real person, helped by AI, writes the reply. Usually within a few hours. Tickets don’t disappear into the void.

  • Settings language menu. The language menu in this plugin’s settings now lists only the languages it is actually translated into, plus English, so you can no longer pick a language that changes nothing.
  • Danish. Adds a Danish translation.
  • Section status. Each settings section shows whether that feature is switched on. On a site that also runs an older EP plugin, the section shows its plain title instead.
  • Branding. When EP Newsletter is the plugin that holds your brand colour, its settings gain a Branding section: one colour every EP form shares for its buttons.
  • Fixes a crash on the newsletter signup form when an older EP plugin is installed on the same site. Submitting the form returned an internal error and nothing was saved or sent. EP plugins share one common code library, and whichever copy loads first is the one every EP plugin on that site uses, so a single out-of-date plugin could leave this one calling a spam check its copy did not have. The check now carries its own fallback and no longer depends on another plugin being up to date.
  • No change on a site where this never happened: the same spam check runs, and nothing else changed.
  • Makes the API’s double opt-in option visible where people actually look for it. subscribe has been able to run the full double opt-in flow since 1.5.3, by passing double_optin: true, but PageMotor’s action menu shows only the first sentence of an action’s description and ours was the four-word “Subscribe a contact.” Anyone browsing the API to find a confirmation-email trigger saw nothing, and reasonably concluded there wasn’t one. The first sentence now names the option. No behaviour has changed.
  • Corrects the PageMotor 0.11.3 preparation shipped in 1.8.3. That release grouped this plugin’s API and MCP actions into families, but in a shape PageMotor 0.11.3 does not accept. On 0.11.3 the plugin would have registered none of its actions, and nothing on screen would have said so.
  • This version uses the shape 0.11.3 expects, and keeps the earlier shape for sites still on an older PageMotor. One build serves both, so there is no order you have to update things in.
  • Safe to install now. Nothing you can see changes.