Back to Admin Dashboard

๐Ÿ“ง 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.

Section 1 โ€” SMTP Setup & Contact Forms

How we configured reliable email delivery and built working contact forms on this WordPress VPS.

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.

โš ๏ธ Key Concept

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

  1. Go to Plugins โ†’ Add New
  2. Search for WP Mail SMTP by WPForms โ€” it has 4+ million installs, look for that one specifically
  3. Install and Activate
  4. The setup wizard will launch automatically
โš ๏ธ Setup Wizard โ€” Mailer Selection

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:

SettingValue
SMTP Hostsmtp.ionos.co.uk
EncryptionTLS
SMTP Port587
AuthenticationON
SMTP Usernameinfo@bodysleepmind.com
SMTP PasswordYour Ionos mailbox password
From NameBody Sleep Mind
Force From NameON
From Emailinfo@bodysleepmind.com
Force From EmailON
๐Ÿšซ Common Mistakes
  • 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:

AddressPurpose
*@bodysleepmind.comCatch-all โ€” any address @bodysleepmind.com lands here. Used for website forms and system mail.
katie@bodysleepmind.comPersonal โ€” for Katie’s direct correspondence only.
๐Ÿ’ก How the Catch-All Works

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

๐Ÿšซ Rule 1 โ€” Never Use Reserved Names for Form Fields

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.

๐Ÿšซ Rule 2 โ€” Form Action Must Be Empty String

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="".

๐Ÿšซ Rule 3 โ€” Never Duplicate Hidden Fields

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.

โš ๏ธ Rule 4 โ€” Wrap All Functions and Constants

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:

  1. PHP processing block at the top โ€” handles POST, sanitizes, calls wp_mail()
  2. CSS <style> block
  3. HTML form markup with PHP echoes for success/error messages
  4. JavaScript <script> block at the bottom
โš ๏ธ Method B Gotchas
  • 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 in if (!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.

  1. Does the form render? If not, WPCode snippet is not active or not assigned to the page.
  2. 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.
  3. Check the form action in the console:
    document.querySelector('form').action
    It should return the current page URL. If it returns ##something you have the double hash problem โ€” change action="#" to action="".
  4. 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.
  5. 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.
  6. 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:

โœ… What the Production Form Does Right
  • 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

Section 2 โ€” Triggered & Designed Emails

How we use FluentCRM with MemberPress to send beautifully branded automated emails when members sign up, renew, or cancel.

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

  1. Go to Plugins โ†’ Add New
  2. Search for FluentCRM
  3. Install and Activate FluentCRM โ€” Marketing Automation and CRM
  4. Run through the setup wizard โ€” it will ask for your From Name and From Email. Use Body Sleep Mind and info@bodysleepmind.com
  5. FluentCRM will automatically import your existing WordPress users and MemberPress members into its contact list
โš ๏ธ Delivery Is Already Handled

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:

  1. Go to FluentCRM โ†’ Settings โ†’ Integrations
  2. Confirm MemberPress is listed and enabled
  3. 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.

  1. Go to MemberPress โ†’ Settings โ†’ Emails
  2. For each email type you are replacing with a FluentCRM automation, uncheck Enable and save
  3. 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 EmailReplace With FluentCRM?
Welcome Email (new member)Yes โ€” build a branded welcome automation
Payment ConfirmationYes โ€” build a branded receipt email
Payment FailedYes โ€” build a failed payment automation
Subscription ExpiringYes โ€” build a renewal reminder sequence
Admin New Member NotificationOptional โ€” 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.

  1. Go to FluentCRM โ†’ Email Templates โ†’ Add New
  2. Give the template a clear internal name โ€” e.g. Welcome Email โ€” Premium Member
  3. Choose a layout from the pre-built options or start from blank
  4. Use the drag-and-drop editor to add your blocks โ€” image, text, button, divider, footer
  5. Save the template
โš ๏ธ HTML Email Is Not Web Design

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

ElementValue
Primary background#ffffff
Header background#0f172a
Accent / CTA colour#00c48c
Body text#334155
Footer text#94a3b8
FontArial or Helvetica โ€” web-safe only for email
LogoUse 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.

  1. Go to FluentCRM โ†’ Automations โ†’ Add New Automation
  2. Give it a clear name โ€” e.g. Welcome Sequence โ€” New Member
  3. 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
  4. Filter by membership level if needed โ€” e.g. only trigger for “Premium” members
  5. Add a Wait step if appropriate โ€” a 1-2 minute delay prevents the email arriving before the member has finished the checkout page
  6. Add a Send Email action and select your designed template
  7. Set the automation to Active
๐Ÿšซ Do Not Activate Before Testing

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:

TagOutputs
{{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
โš ๏ธ Unsubscribe Link Is Not Optional

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:

  1. In the email template editor click Send Test Email โ€” sends to your admin email address
  2. Check it in both Gmail and Outlook โ€” they render differently, both must look correct
  3. Check on mobile โ€” over 60% of email is read on mobile. Single-column layouts are safest.
  4. 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
  5. In the automation, use Test This Automation with a test contact before setting to Active
  6. Once live, monitor FluentCRM โ†’ Reports for open rates and any bounce or failure notifications
โœ… Go-Live Checklist
  • 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

Section 3 โ€” Mass Email Campaigns Coming Soon

Guidance for sending broadcast emails to segments of the member base. This section will be written when the mass email system is built out.

๐Ÿšง

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