Advanced

Migrating to Super Speedy Emails (from MailPoet, MailerLite, or WooCommerce)

Last updated July 3, 2026

This guide walks you through moving your existing subscribers and customers into Super Speedy Emails (SSE), then verifying everything is correct before you send a single email. The migration is run via WP-CLI and is designed to be re-runnable — you can dry-run as many times as you like before committing changes.

Three things get migrated, each independently:

  1. WooCommerce customers → the sse_product_owners ownership cache. So SSE knows which subscribers own which products, even for orders placed long before the plugin was installed.
  2. MailPoet subscribers + lists + unsubscribes → SSE subscribers + lists + suppressions.
  3. MailerLite subscribers + groups + unsubscribes → SSE subscribers + lists + suppressions, via a CSV export.

You can do any subset, and you can do them in any order. They don’t interfere with each other.


Why dry-run first?

Every importer ships with a --dry-run mode that writes nothing — it only counts what would happen. Treat dry-run as the rehearsal: read the output, sanity-check the totals, then add --commit to apply.

⚠️ The default mode is --dry-run. You must pass --commit explicitly to write changes.


Step 1 — Back up your database

Standard advice but worth saying. Before any commit run:

wp db export ~/sse-pre-migration-$(date +%Y%m%d).sql

If a migration produces a result you didn’t expect, restore from this dump and re-do the dry-run with different inputs.


Step 2 — Import existing WooCommerce customers

This populates SSE’s product-ownership cache from every paid order in your store. New orders are picked up automatically by a hook, but existing orders need a one-off backfill.

# Dry-run — see how many orders would be processed:
wp sse migrate wc-customers

# Commit — actually populate the cache. Can take a minute or two on a big store.
wp sse migrate wc-customers --commit

# Or just the last year's orders, if you don't want to backfill years of history:
wp sse migrate wc-customers --days=365 --commit

What this does, plainly:

  • Walks every WooCommerce order with status completed or processing.
  • For each order, ensures there is an SSE subscriber row matching the billing email (creates one if not).
  • For each line item, writes a row into sse_product_owners so segments like “owners of Scalability Pro” find this customer instantly.
  • Bundle expansion is automatic: a Super Speedy Pack buyer gets direct=1 for the Pack and direct=0 rows for each component. So “owners of Scalability Pro” includes Pack buyers, but “direct buyers of Scalability Pro” does not.

Important: Subscribers created from this backfill have source = wc_purchase and marketing_opt_in = 0. They are not on any marketing list. They can receive product_support, onboarding, and transactional emails (per the legal-basis matrix), but they will never receive newsletter or marketing blasts unless they explicitly sign up for a list.

This is the “no silent marketing opt-in” rule and it’s deliberate.


Step 3 — Migrate MailPoet

If MailPoet is installed on this WordPress install, the importer reads directly from MailPoet’s database tables. No exports required.

# Dry-run:
wp sse migrate mailpoet

# Commit:
wp sse migrate mailpoet --commit

What the dry-run output tells you

You’ll see something like:

MailPoet migration — DRY RUN

  Lists:
    matched existing       0
    new lists              4 (would do)

  Subscribers:
    new                    2843 (would do)
    updated                0 (would do)

  Breakdown by status:
    active                 2204
    pending                12
    unsubscribed           589
    bounced                38

  Suppression rows:        627 (would do)
  List memberships:        2204 (would do)

Success: dry-run complete (no writes)

Reading this:

  • 2204 active subscribers will be imported as marketing-opted-in members of your lists.
  • 12 unconfirmed will be imported as pending (no list memberships, no welcome resend — pinging stale unconfirmed addresses is a deliverability footgun).
  • 589 unsubscribed + 38 bounced = 627 suppression rows. These people will not receive marketing email from SSE. (The unsubscribed get a marketing scope suppression; the bounced get a global scope suppression.) Their subscriber rows are still created — they just can’t be marketing-mailed.
  • 2204 list memberships — every active subscriber’s MailPoet segment membership is preserved.

If those numbers look right, run with --commit.

MailPoet status mapping

MailPoet statusSSE outcome
subscribedActive SSE subscriber, marketing-opted-in, list memberships set
unconfirmedSSE subscriber as pending, marketing-opt-in off, no list memberships
unsubscribedSSE subscriber created, marketing-opt-in off, marketing-scope suppression added
bouncedSSE subscriber created, marketing-opt-in off, global-scope suppression added
inactive (MailPoet’s stale-engagement bucket)Treated as unsubscribed — marketing-scope suppression
Soft-deleted (deleted_at set)Skipped entirely

Segments → SSE lists

Each MailPoet segment of type default becomes an SSE list with slug mailpoet-<id>-<segment-name> and category list_broadcast. Auto-segments (e.g. “WooCommerce customers” of type wc_users) are filtered out — those aren’t real opt-in lists.

If a list with the matching slug already exists, the importer reuses it instead of creating a duplicate.


Step 4 — Migrate MailerLite

MailerLite is an external service, so the importer reads from a CSV export rather than a database table.

Export from MailerLite

  1. Log into MailerLite.
  2. Go to Subscribers.
  3. Click Export (top-right). Choose All subscribers or a specific group/status. The “All subscribers” export is fine — the importer respects each row’s status field.
  4. Pick CSV format. MailerLite emails you a download link or starts the download.
  5. Save the file somewhere on the WordPress server (e.g. /tmp/mailerlite.csv).

The importer accepts these column names (case-insensitive):

  • email (required)
  • first_name / first name / fname / name
  • last_name / last name / lname
  • status (active / unsubscribed / bounced / unconfirmed / junk)
  • groups / group / lists / list — comma- or semicolon-separated list names

The standard MailerLite export includes these.

Run the import

# Dry-run:
wp sse migrate mailerlite --file=/tmp/mailerlite.csv

# Commit:
wp sse migrate mailerlite --file=/tmp/mailerlite.csv --commit

MailerLite status mapping

MailerLite statusSSE outcome
activeActive SSE subscriber, marketing-opted-in, group memberships → list memberships
unconfirmedSSE pending, no list memberships
unsubscribedMarketing-scope suppression
bouncedGlobal-scope suppression
junk (MailerLite’s complaint bucket)Global-scope suppression, reason=complaint

Groups → SSE lists

Each unique group name in the CSV becomes (or reuses) an SSE list with slug mailerlite-<sanitised-name> and category list_broadcast.


Step 5 — Sanity-check report

After running any combination of the above with --commit, run the sanity report:

wp sse migrate report

You’ll see:

== Subscribers ==
  total                 4127
  marketing-opted-in    2531
  pending (no consent)  12
  from MailPoet         2843
  from MailerLite       811
  from WC purchase      473

== Suppressions ==
  total                 1184
  marketing-scope       1104
  global-scope (hard)   80
  imported (migrated)   1104

== Lists & WC ownership ==
  lists                 8
  product owners (subs) 473
  ownership rows total  812
  WC subs with no list  473  (product-only audience — expected, can still receive product_support/onboarding/transactional)

This is your final pre-launch checklist. Things to look at:

  1. Total vs. marketing-opted-in. Big gap = lots of suppressed-but-imported subscribers. That’s correct if you had many unsubscribes — bad if it doesn’t match what your old provider reported.
  2. Suppression total. This should roughly match the count of “unsubscribed + bounced” people across all your old providers. If this number is suspiciously low, your unsubscribes were not fully imported — STOP, investigate, do not send.
  3. WC subs with no list. These are customers backfilled from WooCommerce who never signed up to a marketing list. They will not receive your newsletter. They can be reached for product support, onboarding, and transactional. This is the legally cleanest default.

Step 6 — Manually verify the unsubscribers are respected

Sanity-checking that unsubscribes were honoured is the single most important check before sending.

Verification A — Pick a known unsubscriber

If you can think of a specific person who unsubscribed in MailPoet/MailerLite, look them up:

# Check the subscriber row exists:
wp db query "SELECT id, email, status, marketing_opt_in, source FROM wp_sse_subscribers WHERE email = 'someone@example.com'"

# Check they have a suppression:
wp db query "SELECT email, scope, reason, source FROM wp_sse_suppressions WHERE email = 'someone@example.com'"

Expected: subscriber row present, marketing_opt_in = 0. Suppression row present, scope = marketing (for an unsubscribe) or scope = global (for a hard bounce or complaint).

Verification B — Spot-check a handful in the admin

  1. Go to SSE Emails → Suppressions in the WP admin.
  2. You should see the marketing-scope rows from MailPoet/MailerLite listed with reason = migrated.
  3. Search for a few specific email addresses you know unsubscribed in your old provider — they should appear here.

Verification C — Dry-run a campaign and check the count

Compose a broadcast campaign in the admin against a list you migrated. Before sending, the audience-resolver shows the recipient count. That count should exclude suppressed addresses automatically — the gatekeeper handles this without you needing to remember.

If the count is suspiciously high (close to the total subscriber count), the suppression mask probably isn’t being applied. Stop and check.

💡 You can always run a campaign in test mode first. Set wp option update sse_test_mode log_only — no real emails go out, but log rows are written so you can audit who would have been sent to.


Step 7 — Stop sending from the old provider

Once everything looks right:

  1. Disable sending in MailPoet (don’t uninstall yet — keep the data around as backup).
  2. Stop scheduling in MailerLite (or downgrade to a free plan if you’re paying).
  3. Send your first SSE campaign to a small list as a final smoke test.
  4. After a couple of weeks of confidence, uninstall MailPoet.

Re-running migrations

Every importer is idempotent. Re-running on the same source produces no duplicates:

  • Subscribers are matched by email; existing rows are updated, not duplicated.
  • Lists are matched by slug; existing lists are reused.
  • Suppressions are uniquely keyed on (email, scope); re-adding a row is a no-op.

This means it is safe to re-run a migration if:

  • You realised mid-migration that something was wrong and want to start fresh after fixing it.
  • You exported MailerLite again with more data and want to re-import.
  • You’re not sure whether a previous run completed; just run again with --commit and check the sanity report.

What does NOT get migrated

  • Sending history. SSE doesn’t import “this person was sent this email in 2023”. The audit trail starts fresh.
  • Open/click tracking history. Same reason.
  • Templates from MailPoet/MailerLite. SSE is plain-text-first; you’ll re-author any newsletter templates in SSE’s composer.
  • Sender domains / authentication. Set up your sending domain (MailGun) directly. SSE doesn’t touch DNS.

Troubleshooting

MailPoet tables not found in this install Either MailPoet is no longer installed, or it lives in a different table prefix. If your install has a non-standard prefix, set:

wp option update sse_migrate_mailpoet_prefix wp_yourprefix_

and re-run.

CSV file not readable Path is wrong, or the WP user can’t read it. Use an absolute path; check ls -la shows readable permissions.

Dry-run says 0 subscribers For MailPoet: the tables exist but are empty. For MailerLite: the CSV header didn’t match any of the expected column aliases. Open the CSV in a text editor, check the first row, and rename headers if needed (e.g. change Email Address to email).

“Updated” counts are non-zero on the first dry-run That means subscriber rows with those email addresses already exist in SSE. This is expected if you’ve also run the WooCommerce backfill — a WC customer who’s also on a MailPoet list will have one row created by the WC backfill, then “updated” by the MailPoet import to set marketing_opt_in=1 and source. This is the correct behaviour.


In a hurry — the absolute minimum

# Back up:
wp db export ~/sse-pre-migration.sql

# Backfill WC customers (no consent risk; just populates the ownership cache):
wp sse migrate wc-customers --commit

# Migrate MailPoet (dry-run, sanity-check the numbers, then commit):
wp sse migrate mailpoet
wp sse migrate mailpoet --commit

# Migrate MailerLite from a CSV export:
wp sse migrate mailerlite --file=/tmp/mailerlite.csv
wp sse migrate mailerlite --file=/tmp/mailerlite.csv --commit

# Final check:
wp sse migrate report

If the report numbers match expectations, you’re done — disable sending in your old provider and start using SSE.

Leave a Reply

Your email address will not be published. Required fields are marked *

×
1/1