๐ง Body Sleep Mind โ Email System Guide
The complete reference for everything email on this site. Read the relevant section before making any changes. Written for developers โ junior or otherwise.
Contents
1 Why SMTP โ The Problem With WordPress Email Out of the Box
By default WordPress sends email using PHP’s built-in mail() function. On a VPS like Ionos this is either blocked entirely or so poorly authenticated that every email lands in spam. The solution is SMTP โ authenticated email delivery through your actual Ionos mail account.
SMTP and Ionos Webmail are not two separate systems. SMTP is simply the sending protocol for the same info@bodysleepmind.com account you log into at webmail.ionos.co.uk. You are not creating anything new โ just giving WordPress permission to send through it.
2 Installing WP Mail SMTP
- Go to Plugins โ Add New
- Search for WP Mail SMTP by WPForms โ it has 4+ million installs, look for that one specifically
- Install and Activate
- The setup wizard will launch automatically
The wizard will show you SendLayer, Brevo, Mailgun and others marked as “Recommended”. Ignore all of them. These are third party services requiring separate accounts. Scroll to the bottom and select Other SMTP. That is the correct option for Ionos.
3 Configuring WP Mail SMTP for Ionos
Navigate to WP Mail SMTP โ Settings and enter the following:
| Setting | Value |
|---|---|
| SMTP Host | smtp.ionos.co.uk |
| Encryption | TLS |
| SMTP Port | 587 |
| Authentication | ON |
| SMTP Username | info@bodysleepmind.com |
| SMTP Password | Your Ionos mailbox password |
| From Name | Body Sleep Mind |
| Force From Name | ON |
| From Email | info@bodysleepmind.com |
| Force From Email | ON |
- SMTP Username must be the full email address โ not a short name like
chris1. Ionos requires the complete address. - From Email must match your Ionos address โ if it shows a Gmail or other address, Ionos will reject the connection. It can only send from an address that exists on its own server.
- Force From Email must be ON โ otherwise other plugins can override it with a different address and break delivery.
Testing
Go to WP Mail SMTP โ Tools โ Email Test. Send a test to your inbox. If it arrives, the configuration is correct and all WordPress email โ including contact forms โ will work.
4 Our Ionos Email Setup
This site has two Ionos email addresses โ the maximum on our plan:
| Address | Purpose |
|---|---|
*@bodysleepmind.com | Catch-all โ any address @bodysleepmind.com lands here. Used for website forms and system mail. |
katie@bodysleepmind.com | Personal โ for Katie’s direct correspondence only. |
Because of the catch-all, you can use info@, contact@, noreply@ or any other prefix in your form code โ they all arrive in the same inbox. The SMTP plugin authenticates using the catch-all account credentials.
5 Writing a Contact Form That Works
We learned several hard lessons building the forms on this site. Follow this pattern exactly and you will avoid all of them.
The Minimal Working Pattern
Always start here and build outward. Never start with a complex form and debug backwards.
<?php if (isset($_POST['test_submit'])) { $sent = wp_mail( 'info@bodysleepmind.com', 'Test Email', 'This is a test.' ); echo $sent ? 'SENT OK' : 'FAILED'; } ?> <form method="POST" action=""> <input type="hidden" name="test_submit" value="1"> <button type="submit">Send Test Email</button> </form>
Confirm SENT OK before adding anything else. Then add fields one at a time.
Critical Rules โ Read These Carefully
WordPress intercepts POST variables with certain names and tries to look up pages or posts by that value. The result is a 404 “page not found” error even though the URL is correct.
Never use these as name="" attributes on form inputs:
name ยท p ยท page ยท s ยท cat ยท tag ยท author ยท feed ยท year ยท day
Always prefix your field names: cf_name, cf_email, cf_message etc. This one mistake cost hours of debugging.
The form action attribute must be set to action="" โ an empty string. This tells the browser to post to the current page.
Do not use action="#" โ this creates a double hash in the URL (##anchor) which the browser treats as an anchor jump, not a POST request. The form fires repeatedly but never actually submits. Always use action="".
Only include your submit detection hidden field once. Having <input type="hidden" name="submit" value="1"> twice in the same form causes unpredictable behaviour. Check your form HTML carefully.
Any PHP function you define in a snippet must be wrapped. WPCode snippets can load more than once in certain conditions and PHP will fatal error on a duplicate function definition.
if (!function_exists('my_function')) { function my_function() { // your code } }Same applies to
define() โ always wrap constants:
if (!defined('MY_CONSTANT')) define('MY_CONSTANT', 'value');
6 Deploying a Form via WPCode
We use WPCode to deploy PHP contact forms without touching theme files. There are two methods โ both work, choose based on the situation.
Method A โ Physical File + WPCode Include Recommended
Save your form as a physical PHP file in your theme directory, then include it from a WPCode snippet. This keeps the code in version control and easy to edit via SFTP.
In your WPCode snippet (set type to PHP Snippet):
include( get_stylesheet_directory() . '/email/your-form.php' );
Method B โ Full Form Written Directly in WPCode Snippet Also Works
Write the entire form โ PHP processing, HTML, CSS and JS โ as a single self-contained WPCode PHP snippet. No external files, no includes. The partners page form was built this way.
The snippet contains everything in this order:
- PHP processing block at the top โ handles POST, sanitizes, calls
wp_mail() - CSS
<style>block - HTML form markup with PHP echoes for success/error messages
- JavaScript
<script>block at the bottom
- WPCode must be set to PHP Snippet type โ not HTML. HTML type silently ignores all PHP.
- Do not use
ob_start()/ob_get_clean()wrappers โ not needed and causes WPCode to swallow output silently. - If the snippet is assigned to a page more than once, the form renders twice, JavaScript event listeners stack, and the submit fires hundreds of times. Check assignments carefully.
- All PHP functions wrapped in
if (!function_exists())and all constants inif (!defined())โ see Rule 4 above.
WPCode Snippet Settings Checklist
- Code Type: PHP Snippet โ not HTML Snippet. HTML snippets ignore all PHP.
- Status: Active
- Insert Location: Page-Specific if targeting one page, or Shortcode if placing via
[wpcode id="..."]in page content - Check the snippet is not assigned to the page more than once โ duplicate assignments cause the form to render multiple times and stack JavaScript event listeners
7 Debugging a Form That Isn’t Working
Follow this exact sequence. Do not skip steps.
- Does the form render? If not, WPCode snippet is not active or not assigned to the page.
- Does clicking submit do anything? Open browser DevTools โ Network tab. If “No network activity recorded” after submit, something is blocking the form client-side โ likely a JavaScript error or an
action="#"issue. - Check the form action in the console:
document.querySelector('form').actionIt should return the current page URL. If it returns##somethingyou have the double hash problem โ changeaction="#"toaction="". - Is the POST reaching PHP? Add this at the very top of your snippet temporarily:
if (isset($_POST['your_submit_field'])) { echo 'POST received'; die(); }
If you see “POST received” the PHP is working. If not, check field names for reserved word conflicts. - Does wp_mail() return true? Test in isolation with the minimal pattern from Section 5. If it returns false, check WP Mail SMTP settings and run the plugin’s own email test.
- Email sent but not arriving? Check spam. Check the From Email in WP Mail SMTP matches your Ionos address exactly.
8 Production Form Template
The partners page form at /email/partners-page.php is the canonical working example on this site. Use it as your starting point for any new form. Key things it does correctly:
- All field names prefixed with
cf_โ no reserved word collisions - Form
action=""โ posts to current page cleanly - Single hidden submit detection field โ no duplicates
- All functions wrapped in
if (!function_exists()) - All constants wrapped in
if (!defined()) - Success message shown, form hidden after send โ prevents duplicate submissions
- Failure message shown with direct email fallback address
- All output escaped with
esc_html(),esc_attr(),sanitize_text_field() - No
ob_start()/ob_get_clean()wrapper โ unnecessary and causes issues in WPCode
1 Why FluentCRM
MemberPress has a built-in email system but its editor is basic โ plain text with limited HTML. It cannot produce properly designed branded emails. FluentCRM is a WordPress-native CRM and email automation plugin that sits on top of MemberPress and adds a full visual email designer and automation engine.
We chose FluentCRM over alternatives for these reasons:
- Stays entirely inside WordPress โ no third party service, no new accounts
- Uses the existing WP Mail SMTP + Ionos setup for delivery โ nothing new to configure
- Native MemberPress integration โ reads memberships, tags members automatically
- Visual drag-and-drop email builder with proper layouts, imagery and typography
- Free core plugin covers everything needed at this stage
2 Installation
- Go to Plugins โ Add New
- Search for FluentCRM
- Install and Activate FluentCRM โ Marketing Automation and CRM
- Run through the setup wizard โ it will ask for your From Name and From Email. Use
Body Sleep Mindandinfo@bodysleepmind.com - FluentCRM will automatically import your existing WordPress users and MemberPress members into its contact list
FluentCRM uses wp_mail() to send โ which means WP Mail SMTP routes everything through Ionos SMTP automatically. You do not need to configure any sending settings inside FluentCRM itself. If WP Mail SMTP works, FluentCRM works.
3 MemberPress Integration
FluentCRM detects MemberPress automatically on activation. To verify the integration is active:
- Go to FluentCRM โ Settings โ Integrations
- Confirm MemberPress is listed and enabled
- Go to FluentCRM โ Contacts โ your MemberPress members should already be imported
FluentCRM creates tags automatically based on MemberPress membership levels. For example a member on the “Premium” plan will be tagged MemberPress: Premium. These tags are what you use to target the right people in automations.
4 Disabling Duplicate MemberPress Emails
This is the most important step and is easy to miss. MemberPress sends its own emails for signup, payment confirmation etc. Once FluentCRM is handling those same emails you must turn off the MemberPress versions โ otherwise members receive two emails for every event.
- Go to MemberPress โ Settings โ Emails
- For each email type you are replacing with a FluentCRM automation, uncheck Enable and save
- Do this one at a time as you build each FluentCRM automation โ do not disable everything at once before your FluentCRM automations are live and tested
| MemberPress Email | Replace With FluentCRM? |
|---|---|
| Welcome Email (new member) | Yes โ build a branded welcome automation |
| Payment Confirmation | Yes โ build a branded receipt email |
| Payment Failed | Yes โ build a failed payment automation |
| Subscription Expiring | Yes โ build a renewal reminder sequence |
| Admin New Member Notification | Optional โ keep MemberPress handling this if you prefer plain text admin alerts |
5 Designing an Email Template
FluentCRM has a built-in visual email builder. All designed emails start here before being used in an automation.
- Go to FluentCRM โ Email Templates โ Add New
- Give the template a clear internal name โ e.g. Welcome Email โ Premium Member
- Choose a layout from the pre-built options or start from blank
- Use the drag-and-drop editor to add your blocks โ image, text, button, divider, footer
- Save the template
Email clients render HTML very differently from browsers. Outlook uses Microsoft Word as its rendering engine. Key constraints to keep in mind:
- No flexbox or CSS grid โ FluentCRM’s builder handles this for you, do not add custom CSS that uses these
- Keep layouts simple โ single column or simple two-column maximum
- Images must be hosted on a public URL โ they are not embedded in the email
- Always test in both Gmail and Outlook before going live โ they render very differently
- Keep the total email width at 600px โ this is the industry standard safe width
Brand Settings to Apply Consistently
| Element | Value |
|---|---|
| Primary background | #ffffff |
| Header background | #0f172a |
| Accent / CTA colour | #00c48c |
| Body text | #334155 |
| Footer text | #94a3b8 |
| Font | Arial or Helvetica โ web-safe only for email |
| Logo | Use the hosted URL from your media library |
6 Building an Automation
An automation is a trigger + one or more actions. For example: Member completes signup โ wait 1 minute โ send welcome email.
- Go to FluentCRM โ Automations โ Add New Automation
- Give it a clear name โ e.g. Welcome Sequence โ New Member
- Click Add Trigger and select the MemberPress event:
- MemberPress โ Membership Activated for new signups and renewals
- MemberPress โ Membership Cancelled for cancellations
- MemberPress โ Membership Expired for expiries
- MemberPress โ Payment Failed for failed payments
- Filter by membership level if needed โ e.g. only trigger for “Premium” members
- Add a Wait step if appropriate โ a 1-2 minute delay prevents the email arriving before the member has finished the checkout page
- Add a Send Email action and select your designed template
- Set the automation to Active
Set the automation to Draft while building. Only switch to Active after you have tested it using the test send feature. An automation set live with an untested template will send broken emails to real members immediately.
7 Merge Tags Reference
Merge tags pull live data into your email at send time. Use these in your templates:
| Tag | Outputs |
|---|---|
{{contact.first_name}} | Member’s first name |
{{contact.last_name}} | Member’s last name |
{{contact.full_name}} | Member’s full name |
{{contact.email}} | Member’s email address |
{{crm.business_name}} | Body Sleep Mind |
{{crm.business_email}} | info@bodysleepmind.com |
{{subscription.plan_name}} | MemberPress membership level name |
{{subscription.billing_amount}} | Amount charged |
{{unsubscribe_url}} | Unsubscribe link โ required by law, always include in footer |
Every marketing or automated email must include an unsubscribe link. This is a legal requirement under GDPR. FluentCRM provides {{unsubscribe_url}} โ always include it in the footer of every template. Transactional emails (payment receipts, login details) are exempt but it is good practice to include it anyway.
8 Testing
Never go live without testing. Follow this sequence:
- In the email template editor click Send Test Email โ sends to your admin email address
- Check it in both Gmail and Outlook โ they render differently, both must look correct
- Check on mobile โ over 60% of email is read on mobile. Single-column layouts are safest.
- Check all merge tags resolve correctly โ a tag that fails renders as the raw tag text e.g.
{{contact.first_name}}appearing literally in the sent email - In the automation, use Test This Automation with a test contact before setting to Active
- Once live, monitor FluentCRM โ Reports for open rates and any bounce or failure notifications
- Template tested in Gmail and Outlook
- Template tested on mobile
- All merge tags resolving correctly
- Unsubscribe link present in footer
- Automation tested with test contact
- Equivalent MemberPress email disabled
- Automation set to Active
๐ง
This section is not yet written.
It will cover audience segmentation, broadcast campaigns, send frequency best practices, deliverability at scale, and unsubscribe management.
Last updated: September 2026 ยท Body Sleep Mind Dev Notes