EP Email
EP Email routes your PageMotor site’s outgoing mail through a proper SMTP server, provides JSON-defined contact forms with conditional logic and file uploads, logs every message, and exposes an extension API for building premium add-ons.
Published by ElmsPark Studio.
Overview
Section titled “Overview”By default, PageMotor sends email through your server’s built-in mail() function. That works, but messages are frequently blocked, delayed, or marked as spam, because a raw PHP server is not a trusted sender to the big mail providers.
EP Email fixes this by routing outgoing email through a proper SMTP server, the same way Gmail, Outlook and every professional email provider delivers mail.
What you get:
- SMTP delivery. Route all outgoing mail through your SMTP provider. Supports SSL/TLS, authentication and custom ports.
- Contact forms. Define forms in JSON, render with a shortcode. Conditional logic, custom field types and validation built in.
- Email logging. Every email is logged with recipient, subject, status and timestamp.
- Email queue. Emails can be queued and sent every minute through EP Cron, with automatic retry. An email still waiting after 24 hours is not sent.
- Extension API. Build add-ons that register custom field types, act on submissions, add settings, and extend the frontend.
Installation
Section titled “Installation”EP Email installs like any PageMotor plugin. Under a minute end to end.
- Download. Grab
ep-email.zipfrom your ElmsPark account or the link provided with your purchase. - Sign in to your PageMotor admin at
yourdomain.com/admin/. - Go to Plugins, then Manage Plugins.
- Upload the zip. Use the plugin upload interface to upload
ep-email.zip. PageMotor extracts it to the correct location automatically. - Activate in your Theme. Enable EP Email in your active Theme’s plugin configuration. Database tables are created on first load.
How to verify: after activation, go to Plugins then Plugin Settings. You should see EP Email Settings with SMTP configuration, contact form settings, and the delivery log. If it appears, you are installed and ready to configure SMTP.
SMTP Configuration
Section titled “SMTP Configuration”SMTP is the industry standard for sending email reliably. EP Email connects to your SMTP provider so messages actually reach inboxes.
Configure it
Section titled “Configure it”- Open Admin then Plugins then EP Email (Settings).
- Under SMTP Settings, enter the host, port, encryption, username and password for your mail provider.
- Click Test Connection to verify credentials without sending an email.
- Click Send Test Email to confirm end-to-end delivery to a real inbox.
SMTP Settings Reference
Section titled “SMTP Settings Reference”| Setting | Description |
|---|---|
| SMTP Host | Mail server hostname, for example smtp.gmail.com or smtp.office365.com |
| SMTP Port | Usually 587 (TLS) or 465 (SSL) |
| Encryption | TLS (recommended), SSL, or None |
| Username | SMTP authentication username |
| Password | SMTP authentication password |
| From Name | Sender name for outgoing emails |
| From Email | Sender email address |
Gmail users: you will need an App Password. Regular passwords no longer work with SMTP. Go to your Google Account, then Security, then 2-Step Verification, then App Passwords.
VPS users: most providers block outbound port 25, so direct server-to-server SMTP is not an option anyway. A dedicated provider like Mailgun (our recommended email delivery agent) is the right answer.
Contact Forms
Section titled “Contact Forms”EP Email includes a contact form engine. Define forms in JSON, render them anywhere with a shortcode. Conditional logic, custom field types and validation are built in.
Adding a form
Section titled “Adding a form”- Go to EP Email Settings then Contact Forms and enter your form definition in the Forms JSON field.
- Place
[ep-email name="form-name"]on any page to render it.
JSON structure
Section titled “JSON structure”Each form is a named object with a recipient, subject, success message, button text, and an array of fields.
{ "contact": { "subject": "Contact from {name}", "success": "Thanks! We'll reply within 24 hours.", "button": "Send Message", "fields": [ {"type": "text", "name": "name", "label": "Name", "required": true}, {"type": "email", "name": "email", "label": "Email", "required": true}, {"type": "textarea", "name": "message", "label": "Message", "rows": 5, "required": true} ] }}Render it with:
[ep-email name="contact"]Form properties
Section titled “Form properties”| Property | Description |
|---|---|
| recipient | Email address that receives form submissions (required) |
| subject | Email subject line. Use {field_name} for placeholder substitution. |
| success | Message shown after successful submission |
| button | Submit button text. Default: “Send Message” |
| fields | Array of field definitions |
| messages | Custom validation and UI messages (optional) |
Field types
Section titled “Field types”| Type | Description |
|---|---|
| text | Single-line text input |
| Email address with validation | |
| textarea | Multi-line text area |
| select | Dropdown menu |
| checkbox | Single checkbox or checkbox group |
| radio | Radio button group |
| hidden | Hidden field (not visible to user) |
| file | File upload (requires the File Uploads extension) |
Field properties
Section titled “Field properties”| Property | Type | Description |
|---|---|---|
| type | string | Field type (required) |
| name | string | Form field name (required) |
| label | string | Display label |
| required | boolean | Make field required |
| placeholder | string | Placeholder text |
| rows | integer | Textarea rows |
| options | array | Options for select, checkbox, radio |
| conditional | object | Show or hide based on another field |
Conditional logic
Section titled “Conditional logic”Show or hide fields based on another field’s value:
{ "type": "select", "name": "department", "label": "Department", "options": ["Sales", "Support", "Billing"]},{ "type": "text", "name": "order_number", "label": "Order Number", "conditional": {"field": "department", "value": "Billing"}}The order_number field only appears when “Billing” is selected. Multiple values are supported: "value": ["Support", "Billing"].
Custom messages
Section titled “Custom messages”Override default UI messages on a per-form basis:
{ "contact": { "messages": { "sending": "Submitting your enquiry...", "error": "Something went wrong. Please try again.", "network_error": "Connection failed. Check your internet.", "required": "{label} is required.", "invalid_email": "Please enter a valid email for {label}.", "success": "Thanks! We'll be in touch soon." }, "fields": [ ] }}Messages reference
Section titled “Messages reference”| Key | Default | When shown |
|---|---|---|
| sending | ”Sending…” | Button text while submitting |
| error | ”Failed to send message. Please try again.” | Server error |
| network_error | ”An error occurred. Please try again.” | Network failure |
| required | ”{label} is required.” | Missing required field |
| invalid_email | ”{label}: Please enter a valid email address.” | Bad email format |
Subject placeholders: use {field_name} in the subject property to insert submitted values. “Contact from {name}” becomes “Contact from John Smith”. Placeholder names are case-sensitive and must match a field name exactly.
Spam Protection
Section titled “Spam Protection”Contact forms attract spam. EP Email ships with a layered defence so most of the noise never reaches your inbox without you having to think about it.
The defence runs in this order on every submission. Cheap layers first; expensive layers only on what survives.
- IP blocklist. Submissions from listed IPs are silently dropped before any parsing or filtering. Banned IPs consume zero classifier or DB resources.
- Central blocklist (optional, opt-in). Same as above but the list is shared across every EP Email install that opts in. Network-effect threat intelligence.
- CSRF check. Confirms the submission came from a real page load on your site, not a script POSTing directly to your form endpoint.
- Honeypot. A hidden field invisible to humans but tempting to naive bots. Anything that fills it gets a fake success response and is silently dropped.
- Time trap. A signed timestamp embedded when the form is rendered. Submissions arriving faster than a real human could fill the form are treated as bots and silently dropped.
- URL-count filter. Submissions whose combined fields contain too many
http://orhttps://links are silently dropped. SEO and link-building spam is structurally URL-heavy. - Keyword blocklist. Plain text phrases you list, one per line. Any submission containing one is silently dropped.
- Gibberish-name filter. Detects bot-generated random alphanumeric strings (
WwmDtQvvtgDtOaUwucR-style names) in name-shaped fields. Real names score 0 on the heuristic; bot strings score 5-7. - Rate limit. Repeat submissions from the same IP within a configurable window are rejected with a “please wait” message.
Silent drops are deliberate. Every spam-class block returns the same friendly success message (“Thank you for your submission”) and quietly bins the message. Spammers cannot tell whether their submission landed in your inbox or hit a filter, so they cannot probe thresholds or tune around the rules.
Settings reference
Section titled “Settings reference”All spam-protection settings live under EP Email Settings then Contact Forms.
| Setting | Default | What it does |
|---|---|---|
| Enable Honeypot | On | Adds a hidden field naive bots fill in. |
| Enable Time Trap | On | Rejects forms submitted faster than a human could fill them. |
| Minimum Fill Time (seconds) | 3 | Floor on how fast a human can plausibly submit. Raise to 5 for short forms, lower to 2 only if you see legitimate complaints. |
| Maximum Links per Message | 2 | Reject submissions with more than this many http(s):// URLs across all fields. Set to -1 to disable. |
| Spam Keyword Blocklist | (empty) | One phrase per line. Case-insensitive substring match. Suggested starters: bitcoin, crypto, casino, seo services, guest post, backlink, link building, rank higher, viagra, cialis. |
| Enable Gibberish-Name Filter | On | Reject submissions with random bot-generated names. |
| Gibberish-Filter Fields | (defaults) | Field names to scan. Defaults cover name, first_name, last_name, full_name, your_name, fullname, firstname, lastname, fname, lname. Add custom names if your forms use them. |
| Enable IP Blocklist | On | Drop submissions from any IP on the list below. |
| Auto-Ban Gibberish IPs | On | Auto-append the submitter IP to the blocklist when the gibberish filter triggers. Self-improving against persistent bots. |
| IP Blocklist | (empty) | One IP per line. Edit freely to add or remove entries. |
| Enable Central Blocklist | Off | Pull from and contribute to the shared ElmsPark blocklist. See Central Blocklist below. |
| Central Blocklist Token | — | Per-site auth token. Request one from ElmsPark for each site. |
| Central Blocklist URL | https://blocklist.elmspark.com/ | API base URL. Advanced users can self-host. |
| Minimum Severity to Block | 2 | 1-5. Raise for conservative, lower for aggressive. See severity model below. |
| Enable Rate Limiting | On | Limit repeat submissions from the same IP. |
| Rate Limit (minutes) | 5 | Minimum minutes between submissions from the same IP. |
Tuning the keyword blocklist
Section titled “Tuning the keyword blocklist”Tune the blocklist to the spam your site is actually receiving. A faith community site needs different terms from a B2B SaaS site. Open the spam folder for the address that receives form submissions, look for repeated phrases, paste them in.
Hard rule: keep blocklist entries specific. “click” or “free” will eat real submissions. “click here to claim your” or “free SEO audit” will not.
Gibberish-name filter
Section titled “Gibberish-name filter”Bot submissions almost always carry random alphanumeric strings in the name field. The filter detects four hard signatures no real human name has:
- Long consonant clusters (5+ in a row).
- Vowel ratio under 20%.
- Single-token length over 15 with no space.
- 4+ random case transitions (
QjDNLLnk-style camel-mixing).
A submission scoring 3 or more on these signals is silently dropped. The thresholds are calibrated so real names score 0 and bot strings score 5-7. Unusual real names (Zulu surnames like Mkhwanazi, hyphenated names, all-caps initialisms) sit at 0-2 and pass cleanly.
The filter scans the fields listed in Gibberish-Filter Fields (defaults to the common name-field naming conventions). It does not scan message bodies, where legitimate technical text or code snippets might trip the heuristic.
When the filter triggers, the submitter IP is automatically appended to the IP Blocklist if Auto-Ban Gibberish IPs is on. Same bot, second attempt, gets a free O(1) drop with no further analysis.
IP blocklist
Section titled “IP blocklist”Manual list of banned IPs, one per line. Submissions from any listed IP are silently dropped at the very start of the pipeline — before CSRF, honeypot, anything. Banned IPs cost nothing.
The auto-ban feature populates this list automatically as the gibberish filter catches bots. You can also paste in IPs you want to ban from server logs, abuse reports, or other sources.
To unblock an IP (false positive, or a real visitor caught by accident), just delete the line.
Central blocklist
Section titled “Central blocklist”The IP blocklist on a single site only learns from attacks that site has already absorbed. The central blocklist fixes that. Every install that opts in reports IPs its filters catch, and pulls IPs other installs have caught. Network-effect threat intelligence, scoped to the EP Email community.
Server lives at https://blocklist.elmspark.com/. Each site needs a per-site bearer token, issued by ElmsPark.
What gets shared
Section titled “What gets shared”Only IP addresses, the reason they were caught (gibberish, ai_triage_spam, etc.), and the plugin version. Site identity is SHA-256 hashed with random per-client salt before storage. The central database never holds plain site identifiers, so a leak doesn’t reveal “which site reported X”. Submission content is never sent to the central server — that is between your site and your form recipients.
Severity model
Section titled “Severity model”Reports are aggregated server-side into a severity score from 1 to 5:
severity = min(5, ceil(distinct_sources * 0.5 + report_count * 0.1))| Severity | Roughly means |
|---|---|
| 1 | Watched. Single source, single report. Your site won’t block on this at the default threshold. |
| 2 | Default block threshold. 3+ distinct sources OR 10+ reports. The IP has been flagged independently by enough sites to be confident. |
| 3 | Broadly bad. Many reports across many sources. |
| 4 | Aggressive. Persistently catching bots across the network. |
| 5 | Top-shelf. Effectively a global ban. |
Each install picks its own Minimum Severity to Block:
- 1 — aggressive. Block on a single report. Fastest to react to new bots; highest false-positive risk if a single bad-actor install reports legitimate IPs.
- 2 — default. Requires corroboration. A single bad-actor install cannot poison the well — it takes at least one other source to corroborate before any IP gets blocked.
- 3-5 — conservative to paranoid. Only block when many sources agree. Useful for sites with high cost of false positives.
How it behaves at runtime
Section titled “How it behaves at runtime”- The central list is fetched into a local cache at most once per 23 hours. Submissions add zero per-request latency.
- Local IP blocklist is checked first. Central is the second layer.
- When your local gibberish filter triggers an auto-ban, the IP is also reported to central (fire-and-forget, 3-second timeout, never blocks the user-visible response).
- Fail-open on central API errors. If
blocklist.elmspark.comis unreachable, your form keeps working.
Getting a token
Section titled “Getting a token”For now, tokens are issued by ElmsPark on request. Future versions may offer self-service. To request a token, open a ticket at help.elmspark.com with:
- The site’s primary domain.
- Confirmation you’ve read this guide and accept the privacy posture above.
You’ll receive the token via the same support thread. Paste it into Central Blocklist Token in EP Email settings, tick Enable Central Blocklist, save.
Beyond the heuristics: AI Triage
Section titled “Beyond the heuristics: AI Triage”Heuristics catch the obvious. They cannot read intent. For sites under sustained spam pressure, the EP Email AI Triage add-on classifies every submission that survives the heuristics with an LLM call — distinguishing a real customer enquiry from a spam pitch by reading the words. AI Triage’s blocks also feed the central blocklist when both are enabled. See its guide for setup.
Developer note: the should_send valet
Section titled “Developer note: the should_send valet”Extension developers can intercept submissions silently with the should_send($form_def, $field_data) method on EP_Email_Extension. Returning anything other than true short-circuits delivery: the user sees the form’s normal success message but no email is sent. AI Triage uses this valet. It suits any extension that needs to stop a submission without telling the submitter.
File Uploads
Section titled “File Uploads”The EP Email File Uploads extension adds drag-and-drop file upload fields to any contact form. Files are attached directly to the notification email as MIME attachments. They are not stored on your server.
Add a file field to any form with "type": "file":
{ "application": { "subject": "Application from {name}", "fields": [ {"type": "text", "name": "name", "label": "Full Name", "required": true}, {"type": "email", "name": "email", "label": "Email", "required": true}, {"type": "file", "name": "resume", "label": "Resume / CV", "required": true} ] }}Configuration
Section titled “Configuration”File upload settings live in EP Email Settings under the File Uploads section:
| Setting | Description |
|---|---|
| Max File Size | Maximum size per file. Default 5 MB. Limited by your server’s upload_max_filesize PHP setting. |
| Max Files | Maximum number of files per upload field. Default 3. |
| Allowed Types | Comma-separated list of permitted file extensions, for example pdf,doc,docx,jpg,png. |
How it works: files are uploaded temporarily during form submission, attached to the outgoing email, then deleted. Nothing is stored on your server permanently. That keeps the site clean and avoids storage management entirely.
Email Logging and Queue
Section titled “Email Logging and Queue”EP Email maintains a complete log of every email your site sends. The log records recipient, subject, delivery status and timestamp.
- Delivery Log. View every email sent. Filter by status, search by recipient or subject, diagnose delivery issues at a glance.
- Email Queue. Emails are queued and sent every minute through EP Cron, with automatic retry, so messages still reach their destination when the SMTP provider was briefly unreachable. If EP Cron is not installed, not active, paused or not running, emails are sent straight away instead. An email still waiting after 24 hours is not sent: it shows in the Delivery Log as failed, with “Not sent: it waited more than 24 hours in the queue.”
For most sites, direct delivery is all you need. Enable the queue only if you send high volumes of email (for example through EP Newsletter or EP Bookings) and want automatic retry for temporary failures.
Modular Content in emails
Section titled “Modular Content in emails”Since 1.10.72, you can put a PageMotor Modular Content block in an email. Write [modular-content id="7"] in an email body, a newsletter or the Email Footer Text setting, and the block’s content is sent in its place.
Edit the block once, and your pages and your emails both change. It suits an address, opening hours or a disclaimer.
Emails are stricter than pages, because a sent email cannot be taken back:
- Only live blocks are sent. A draft block is left out.
- Only Modular Content blocks can be used. The id of a page or post brings in nothing.
- Shortcodes inside a block stay as typed, so a form never ends up in an email.
- Scripts, styles, frames and form fields inside a block are removed.
- Text a visitor types into a contact form is never expanded.
- A block can include another block, up to three levels deep.
To show the shortcode itself, write it with double brackets: [[modular-content id="7"]].
Mail clients ignore your theme’s styles. A block meant for email needs inline styles.
Extension API
Section titled “Extension API”EP Email’s extension system lets developers build add-ons that integrate with the contact form engine. Extensions can register custom field types, act on submissions, add settings, and modify frontend behaviour.
PageMotor has no hooks or filters, and neither does EP Email. It uses valets, as core does: your extension class implements a named method, and EP Email calls it at the right moment.
Five main extension points, each a set of valets your extension can implement:
- Custom fields. Register new field types like signatures, ratings, date pickers, or file uploads. Extensions handle rendering, validation and email integration.
- Submission valets.
before_submit(),should_send(),modify_email()andafter_submit()validate fields, change the email, or act after it is sent, such as an auto-responder or a webhook. - Settings integration. Add configuration fields to the EP Email settings page. Keys are auto-prefixed to avoid collisions between extensions.
- Frontend valets. An add-on supplies a JavaScript object with methods such as
beforeSubmit,afterSuccessandafterReset. They change form behaviour, append data, react to a successful submission, or clean up after a reset. - Message text (1.10.72 and later). Change the body and footer text of every outgoing email, newsletters included. See Changing message text.
Building an extension
Section titled “Building an extension”An EP Email extension is a standard PageMotor plugin that registers an EP_Email_Extension subclass.
- Create a PageMotor plugin. Your plugin must be a
PM_Pluginsubclass with a valid plugin header. - Check for EP Email. In
construct(), verifyclass_exists('EP_Email_Extension')before proceeding. - Require your extension class. Include the file containing your
EP_Email_Extensionsubclass. - Register the extension. Create an instance and call
EP_Email_Extension::register().
Quick start
Section titled “Quick start”// In your plugin's construct() method:public function construct() { if (!class_exists('EP_Email_Extension')) return;
require_once $this->path . '/includes/class-my-extension.php'; $ext = new My_Extension(); EP_Email_Extension::register($ext);}Changing message text
Section titled “Changing message text”Since 1.10.72, an extension can change the text of every message before it is sent, with filter_body($text, $context). Return the changed text. Anything that is not text is ignored, and an error in one extension never stops a send.
It runs for all mail sent through EP Email, and for every EP Newsletter message with EP Newsletter 1.8.10 or later, whichever sending service the newsletter uses.
Text arrives in two parts, each passed to your method once, before the email template wraps it. So anything you add lands inside the message, not after its closing tag.
$context key | Value |
|---|---|
part | body, or footer for the Email Footer Text |
source | ep-newsletter, contact-form, api, test, feedback, or ep-email for anything else |
type | For newsletters: campaign, autoresponder, confirmation, welcome or test |
to | The recipient’s address |
html | true when the part will be sent as HTML: it goes into the email template, goes out through an EP Newsletter Delivery Service, or already contains HTML |
visitor_input | true when the text may contain words a site visitor typed |
public function filter_body($text, $context) { if (!empty($context['visitor_input'])) return $text; return strtr($text, array('{{my-address}}' => '12 Main Street'));}EP Newsletter calls filter_body() on a body before it fills in {name} and the other merge tags. A subscriber’s own details never reach your method, and a {name} your method adds is filled in like any other.
When several extensions implement filter_body(), they run in the order they registered. Each one gets the previous one’s output.
visitor_input is set by EP Email’s own contact forms and feedback form. Mail from other plugins does not set it, even when it includes text a visitor typed. Treat it as a floor, not a guarantee: never expand shortcodes or private content from text you cannot vouch for.
EP Email uses this valet itself for Modular Content blocks in emails.
The full list of valets and their arguments is in the comments of includes/class-ep-email-extension.php, inside the plugin.
Sending email from your own plugin
Section titled “Sending email from your own plugin”(supported surface as of 1.10.37) Any plugin on the same site can send one transactional email through EP Email — the same pipeline EP Password Reset and EP Newsletter use in production. Resolve the active EP_Email plugin instance and call its public send() method:
private function send_via_ep_email($to, $subject, $html) { global $motor; $ep_email = null; foreach (array('theme', 'admin') as $ctx) { if (!isset($motor->$ctx) || !is_object($motor->$ctx)) continue; if (empty($motor->$ctx->_plugins) || !is_object($motor->$ctx->_plugins)) continue; foreach ($motor->$ctx->_plugins->active as $plugin) { if ($plugin->_class === 'EP_Email') { $ep_email = $plugin; break 2; } } } if ($ep_email && method_exists($ep_email, 'send')) return $ep_email->send(array( 'to' => $to, 'subject' => $subject, 'body' => $html )); return array('error' => 'EP Email is not active on this site.');}The contract:
- Arguments.
to(one address or a comma-separated list),subject, andbody(HTML) are required.from_name,from_email,reply_toandtemplateare optional and default from the site’s EP Email settings — pass them only to override. If the site sets a default email template, your body is rendered inside it; pass'template' => ''to send bare HTML.headersandattachmentsarrays are also accepted. - Return. Strict
trueon success (sent, or queued when the site’s Email Queue is enabled), orarray('error' => '...')with a clear message on failure. A missing sender address returns an actionable error rather than an opaque provider failure. - Routing. The send goes through whatever transport the site has configured (SMTP or an API provider extension). PHP
mail()is only used when a site has no transport configured at all. - Logging. With delivery logging enabled, every send lands in the Email Log with its status and any error text.
- The
method_existsguard matters. It keeps your plugin safe on a site running an older EP Email.
Managing EP Email over the PM API
Section titled “Managing EP Email over the PM API”EP Email registers PM API actions under the EP_Email class. Browse them with list-actions, inspect one with describe-action, then invoke with call-action.
| Action | Access | Purpose |
|---|---|---|
send | admin | (new in 1.10.37) Send one transactional email through the site’s configured transport — same validation, queueing and delivery logging as every other EP Email send. Sender fields default from settings. |
list-log | admin | (new in 1.10.36) Read the delivery log, newest first, paginated. Requires Enable Delivery Logging. |
Troubleshooting
Section titled “Troubleshooting”Common issues and fixes.
SMTP connection fails
Section titled “SMTP connection fails”Check your host, port, encryption, and credentials. Verify them against your email provider’s documentation. Use Test Connection in EP Email Settings to isolate the issue. Common mistakes: wrong port for the selected encryption, or a regular password where an app password is required.
Emails go to spam
Section titled “Emails go to spam”Use a proper SMTP provider, set SPF and DKIM records in your domain’s DNS, and ensure the From Email address uses your own domain, not a free provider like gmail.com. A matching From address and an authenticated domain are the two biggest factors in inbox placement.
Contact form not appearing
Section titled “Contact form not appearing”Verify that the name attribute in your shortcode matches the form key in your JSON definition. If your JSON defines a form under "contact", the shortcode must be [ep-email name="contact"]. Check your JSON is valid as well. A missing comma or bracket will silently prevent the form from rendering.
File uploads not working
Section titled “File uploads not working”Ensure the EP Email File Uploads extension is installed and active in your Theme’s plugin configuration. Also check your PHP upload_max_filesize and post_max_size settings. If the server limit is lower than the configured max file size, uploads are rejected silently.
Form says “Failed to send”
Section titled “Form says “Failed to send””The form submitted correctly but the email failed to send. Go to EP Email Settings, verify SMTP credentials with Test Connection, and check the delivery log for error details. Common causes: an expired app password, the SMTP server temporarily unavailable, or the From Email rejected by the provider.
Custom field type not rendering
Section titled “Custom field type not rendering”Verify your extension is registered correctly. EP_Email_Extension::register() must be called in your plugin’s construct(). Check that field_types() returns the type name and render_field() returns valid HTML. If the extension plugin is not active in your Theme, its field types are not available.
Subject shows {name} literally
Section titled “Subject shows {name} literally”The field name in the placeholder must match a field name in your form definition exactly. If your field is "name": "full_name", the subject placeholder must be {full_name}, not {name}. Placeholder substitution is case-sensitive.
Feedback and corrections
Section titled “Feedback and corrections”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.
Changelog
Section titled “Changelog”1.10.74
Section titled “1.10.74”- For add-on developers: two new Extension API hooks for form fields.
field_attributes()lets an add-on add data attributes to a field’s wrapper, andfield_visible()lets it hide a field for one submission, which then counts as not filled in, not required and not sent, exactly like a field whose built-in condition is not met. EP Email — Advanced Forms 1.1.0 uses them for conditions with several rules. Nothing changes on a site without such an add-on. - For add-on developers: an add-on can show its own confirmation. A new
confirmation_html()hook lets an add-on return its own HTML for a delivered submission, such as a page of results. It takes the form’s place on screen, where the thank-you message would otherwise sit under the form. Nothing changes on a site without such an add-on. - Needs PageMotor 0.11.5.
1.10.73
Section titled “1.10.73”- A Modular Content block in your Email Footer Text keeps its formatting. When the footer held only the block’s shortcode, the block arrived as plain text: paragraphs, links and inline styles were lost. A block in the email body could lose its formatting the same way, when the body held nothing else. Both now arrive as the block was written.
- For add-on developers:
$context['html']is now true for any part that goes into the email template, even before it contains any HTML. - Needs PageMotor 0.11.5.
1.10.72
Section titled “1.10.72”- Enquiry forms built by Discovery AI deliver again with the time trap on. Since 1.10.67, a form that EP Email does not draw itself, such as a Discovery AI enquiry form, sent a time stamp EP Email no longer accepted. The visitor saw “Thank you for your submission.” and no email was sent. EP Email can now give those forms a stamp it accepts. A site with Discovery AI forms needs Discovery AI 1.11.74.1 or later as well.
- The Gibberish-Name Filter no longer turns away real people. It read a name with the spaces taken out, so ordinary names such as Mark Schwartz, Bjorn Strand or Ewan McGregor looked like bot strings: the visitor saw “Thank you for your submission.”, nothing was sent, and with Auto-Ban on, their address went onto your IP Blocklist. Each word of a name is now checked on its own, and spellings common in German, Polish, Czech and other names are read correctly. Random bot strings are still caught, a few more than before. If you turned the filter off for this reason, it is safe to turn back on; it is worth looking through your IP Blocklist for addresses the filter added.
- Modular Content blocks now work in emails. Put
[modular-content id="7"]in an email, a newsletter or your Email Footer Text, and the block’s content is sent in its place. Edit the block once and your pages and your emails both change: an address, opening hours or a disclaimer. Only live blocks are sent, and scripts, forms and other shortcodes inside a block are left out. Anything a visitor types into a contact form is never expanded. - New for add-on developers: change a message before it is sent. Add-ons built on the EP Email Extension API can now change the body and footer text of every outgoing message with a new
filter_body()method. It covers all mail sent through EP Email, and all EP Newsletter mail with EP Newsletter 1.8.10. Uses include stored text such as an address block, a signature or a legal notice. Nothing changes on a site with no such add-on. Details are under Extension API on this page. - Needs PageMotor 0.11.5.
1.10.71
Section titled “1.10.71”- Lighter pages. The code behind the contact form now loads only on pages that show a contact form, wherever the form sits: in the page itself, or in a header, footer or other box. Other pages, including your 404 page, no longer load it, which saves about 8 KB each.
- Needs PageMotor 0.11.5.
1.10.70
Section titled “1.10.70”- Old emails waiting in the queue are no longer sent. An email that has waited in the queue for more than 24 hours is now dropped instead of sent, and shows in the Delivery Log as failed with “Not sent: it waited more than 24 hours in the queue.” Before 1.10.67 nothing sent queued emails, so a site updating from an older version could suddenly send emails that had waited for months, such as old test emails, contact form copies or expired password reset links. Emails under 24 hours old are sent as before.
- Needs PageMotor 0.11.5.
1.10.69
Section titled “1.10.69”- Apostrophes and ampersands arrive intact in contact form emails. A message such as “It’s me & co” reached your inbox as
It's me & co, and a name such as O’Neill showed asO'Neillin the subject line. Every field in the email you receive, and the subject, now shows exactly what the visitor typed. Anything that looks like HTML is still shown as text, never run. - Needs PageMotor 0.11.5.
1.10.68
Section titled “1.10.68”- Links in a form’s thank-you message can be clicked. A link you put in a form’s thank-you message (an HTML link, a Markdown link such as
[our FAQ](/faq), or a plain web address) used to show as plain text after someone sent the form. It now shows as a working link. Only links are kept: any other HTML in the message is removed, and links that are not web, email, phone or same-site addresses stay as plain text. - Needs PageMotor 0.11.5.
1.10.67
Section titled “1.10.67”- Queued emails are now actually sent. With “Email Queue” switched on, emails were put in the queue and nothing ever sent them: contact forms, password resets and booking emails all waited there for ever while the form said “sent”. The queue now runs every minute through EP Cron. If EP Cron is not installed, not active, paused or not running, EP Email sends straight away instead of queuing, exactly as it does with the queue off. Emails already waiting in the queue are sent in small batches when you open the admin, until EP Cron takes over.
- A contact form can no longer add extra lines to the email’s headers. On the older shortcode form (no form name), a line break typed into the subject or name went into the email’s Subject line, where it could add headers or rewrite the message your site sent to itself. Line breaks in any subject are now turned into spaces before sending, whichever form or plugin sent it.
- Attachment names containing quote marks no longer break the attachment’s details in the email.
- The time trap can no longer be faked. Bots could make up a valid “this form was opened 10 seconds ago” stamp without loading the page. Stamps are now signed with a secret only your site knows. Forms opened before the update keep working for two hours.
- Log Retention Period now works. Old entries in the delivery log are deleted once a day, as the setting always said. Before, nothing was ever deleted, so every recipient and subject line was kept for ever.
- The IP Blocklist keeps at most 1,000 addresses. When auto-ban adds one beyond that, the oldest is dropped, so a bot using endless new addresses cannot make the setting grow without limit. Auto-ban also no longer overwrites a change you saved to the list at the same moment.
- When EP Email Quiz is set not to show results, the quiz taker’s browser no longer receives the scores and correct answers (needs EP Email Quiz 1.0.11).
- Sending services such as Mailgun and Brevo can now read the extra email headers other plugins set, such as the “automatic reply” marker EP Email Inbox adds.
- The Central Blocklist token is now stored encrypted and the field is write-only, like the SMTP password. It is encrypted automatically after the update; nothing to re-enter.
- Visitors no longer see your mail server’s error messages. When a contact form cannot be delivered, the visitor sees the form’s normal error message; the technical detail goes to your error log.
- The No-Link Filter’s own auto-ban switch now works on its own. It only banned when the gibberish auto-ban was ticked as well.
- “Download LLM Instructions” works again. It stopped with an error.
- The Default Email Template list no longer offers Notification, Minimal and Branded. Those templates never existed: choosing one sent the email with no template at all, exactly as Plain text does, so nothing changes for sites that picked one.
- An email stuck half-sent in the queue (for example after a server timeout) is marked failed after 30 minutes, so it shows in the queue count instead of disappearing. It is not resent automatically, in case it did go out.
- Malformed form posts (a field sent as a list instead of text) now get a normal error instead of crashing the request.
1.10.66
Section titled “1.10.66”- 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.
1.10.65
Section titled “1.10.65”- The DNS check now also recognises Mailgun domains that use their own DKIM key. Mailgun can sign in one of two ways: with keys it rotates for you, which 1.10.64 already recognised, or with a single key of your own. The second kind was not looked for, so on those domains the check could still wrongly say DKIM was missing. It is now checked too. Nothing else changes.
1.10.64
Section titled “1.10.64”- No more false “Gmail will likely reject this email” warning on Mailgun sites. After a test email, EP Email checks your DNS and warns if anything Gmail needs is missing. It looked for DKIM on your From address’s domain only, but Mailgun signs your email with its own sending domain (for example
mg.yourdomain.com), so a site whose email was arriving in Gmail with every check passing was told its DKIM was missing. When you send through Mailgun, the check now looks at the domain Mailgun actually signs and sends with, and confirms it lines up with your From address the way Gmail requires. - The Check DNS Records panel now says which service you are sending through and which domain it checked.
- DKIM keys published without a version tag, as Mailgun publishes them, are now recognised.
- A domain that genuinely has no DKIM record still gets the warning, so nothing that was a real problem is now hidden.
- For the most accurate result, update the EP Email Mailgun add-on to 1.0.16 as well. It reads your DNS records straight from Mailgun. Without it, EP Email checks the Mailgun sending domain in DNS instead.
1.10.63
Section titled “1.10.63”- The DNS check now recognises IONOS mail. The Check DNS Records button looks for your DKIM record under the selector names the common providers use. IONOS signs mail under
s1-ionosands2-ionos, which were not on the list, so a domain whose IONOS mailbox was signing correctly was told “No DKIM record found”. Both names are now checked. Nothing else about the check, or any setting, changes.
1.10.62
Section titled “1.10.62”- Booking invitations now show as proper meeting requests in Outlook. An email carrying a calendar invite, such as an EP Booking confirmation, used to attach the invite as a file only. Apple Mail and Gmail still recognised it, but Outlook showed a plain appointment.ics attachment with no way to accept it. The invite now also travels inside the email as a calendar part, the way Google Calendar sends its own invitations, so Outlook shows the meeting with Accept, Tentative and Decline. The file is still attached as well.
- Only emails that carry a calendar invite change. Every other email is built exactly as before.
- When a customer accepts in Outlook, Outlook may send a short “Accepted” reply to the address the booking email came from.
1.10.61
Section titled “1.10.61”- Sending services such as Mailgun can now pass on attachments. EP Email keeps each email’s attachments with the message, and services that send through an online API rather than SMTP (Mailgun, Brevo, SendGrid) had no way to read them, so attachments went missing on those services. EP Email now makes them available. The Mailgun add-on uses this from version 1.0.14; the others are unchanged for now.
- No change to how EP Email itself sends, or to any setting.
1.10.60
Section titled “1.10.60”- Emails sent by other plugins early in a page load no longer fail. Other plugins send their emails through EP Email. When one of them did that before EP Email had finished starting up, which depends on the order the plugins were activated in, the email failed with “Class EP_Email_Validator not found” and nothing was sent. The clearest case was EP Booking’s cancel and reschedule links: the customer’s change was saved, but no email went out (and before EP Booking 2.4.34, the customer was left on the home page). EP Email now loads everything it needs whenever it is asked to send.
- No change to how emails are sent once they get going, or to any setting.
1.10.59
Section titled “1.10.59”- The check that decides who adds the cache-busting tag to the contact-form script now reads beta and release-candidate PageMotor cores (such as 0.11b or 0.11rc1) correctly. It previously mistook them for versions older than 0.11 and added a second tag on top of the core’s.
- No change on regular PageMotor releases, and no change to how the contact form works.
1.10.58
Section titled “1.10.58”- Fixes a doubled cache-busting tag on this plugin’s contact-form script. On PageMotor 0.11 and later the page asked for the file as
contact-form.js?v=…?v=…, because the core had started adding its own version tag and the plugin was still adding one too. The file loaded correctly, so nothing was visibly broken; the address was just malformed and every cache saw a longer key than it needed. The plugin now leaves the tag to the core on 0.11 and later, and still adds its own on older cores, where nothing else would. - No change to what the contact-form script does.
1.10.57
Section titled “1.10.57”- Fixes emails sent by other EP plugins going nowhere on sites using Mailgun, Brevo or the ElmsPark relay. Password-reset and welcome emails from EP Membership, and any other email a plugin sends while the page is still loading, could silently fall back to the server’s own mail program instead of the sending service you configured, and on most hosting that mail program does not exist. Whether it happened depended on the order the plugins were activated in, which is why it hit some sites and not others. Emails now reach the configured service regardless of activation order.
- If the selected sending service still cannot be found, the fallback to the server mail program is now written to the PHP error log instead of happening in silence.
- The Send Test Email button was never affected, which is why the problem could hide behind a passing test.
1.10.56
Section titled “1.10.56”- Restores spam protection on sites running an older EP plugin alongside this one. EP plugins share one common code library and whichever copy loads first is the one they all use, so an out-of-date plugin could leave this one without the check that rejects a particular spam bot. The check was being switched off in that case; it now carries its own copy and keeps working. Contact form submissions were never blocked or lost by this, the spam simply got through.
- No change on a site where every EP plugin is current.
1.10.55
Section titled “1.10.55”- A warning now appears in the admin bar when SMTP is selected with authentication but no password is saved. Until now that state failed silently: every contact form submission was refused by the mail server with an authentication error, and nobody knew until someone tested the form. On PageMotor 0.11 and later the warning sits in the admin notifications flag with a link to the settings page, and it clears itself the moment a password is saved or the transport changes. On every core the Transport status card on the EP Email settings page turns red with the same message.
- Asked for by MarkieSparky on forum topic 717, after the 0.11.3/0.11.4 secret move (fixed in 1.10.54) left contact forms failing for days with no sign of it in the admin.
1.10.54
Section titled “1.10.54”- Fixes the SMTP password reading as empty after a PageMotor 0.11.3 or 0.11.4 update. After the core update every send failed with
535 Incorrect authentication dataand the password field showed blank, until the password was typed in again. Nothing was deleted: the encrypted password was still in the settings row, but PageMotor 0.11.3 moved the site secret that opens it, and this plugin was still looking in the old place. It now finds the secret in both places, so an existing password opens again without re-entry, and a password that was re-entered in the meantime keeps working and is moved back under the site secret. - If you updated PageMotor and then re-entered the password, there is nothing to do. If you updated and have not re-entered it, this release restores it on the next page load.
1.10.53
Section titled “1.10.53”- Corrects the PageMotor 0.11.3 preparation shipped in 1.10.52. 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.
1.10.52
Section titled “1.10.52”- Prepares the plugin for PageMotor 0.11.3, which requires every plugin to group its API and MCP actions into named families. On that release an action declaring no family is refused silently, so without this update the plugin’s actions would stop being offered with nothing on screen to say why.
- Families exist so that an AI working on your site sees the related actions together instead of stopping at the first one it tries.
- Safe to install on your current PageMotor. No behaviour changed.
1.10.51
Section titled “1.10.51”Fix — the email panel on the Command Centre disagreed with itself. It showed “0 failures (7d)” with a list of five red-flagged failures directly beneath it, under the heading “Recent failures”. Both were accurate and they were answering different questions: the count covered the last seven days, the list covered all time, so it showed the five most recent failures on record however old they were. On a healthy site that meant months-old bounces sitting under the word “recent”.
The list now covers the same seven days as the count above it, so the panel reads consistently and a red flag on it means something happened this week. Your full failure history is unchanged and still in the EP Email log.
1.10.50
Section titled “1.10.50”A tightening of what your site contributes to the Central Blocklist. It now only reports a visitor’s address when that address is publicly routable. A loopback or internal network address, the sort a site sees when it sits behind a proxy or load balancer that does not pass the real visitor address along, identifies nobody outside your own site, so sending it would have added noise to a list every other install relies on.
Nothing to configure, and nothing changes for a normally configured site. Your own local blocklist is untouched: you can still ban any address you like on your own site.
If your site is behind a proxy and the real visitor address is not reaching it, EP Email now says so in your PHP error log instead of quietly contributing nothing. That is worth checking if your Central Blocklist dashboard shows you have never reported anything.
1.10.49
Section titled “1.10.49”If you use the Central Blocklist, it was not working, and this release fixes it.
The shared blocklist service moved to a new address. EP Email carried the old one, and because the plugin treated the redirect as a plain failure, affected sites quietly stopped both halves of the exchange: they never downloaded the shared list of known spam sources, and never contributed the ones their own filters caught. There was no error, no warning and nothing in the settings panel, so there was no way to tell from inside your site. Every site still on the default address had been disconnected since the move.
You do not need to do anything. On update, EP Email rewrites the saved address for you. If you self-host your own central server, your address is left exactly as it is.
Failures are now visible. If the plugin cannot reach the blocklist, the Central Blocklist panel in EP Email Settings says so in red, with the reason and how long ago, in place of the “Connected” line it used to show from a stale local cache. Where the cause is an out-of-date address, the panel names the address to change it to.