Appearance
MailerLite Migration Specification
GitHub Issue: https://github.com/InteractionDesignFoundation/IxDF-web/issues/26906Status: Draft Last Updated: 2026-01-15
Table of Contents
- Executive Summary
- Current State Analysis
- Problem Statement
- Migration Strategy
- Technical Requirements
- Implementation Plan
- Audience Segments & Communication Types
- Open Questions
- Timeline Considerations
1. Executive Summary
Goal
Migrate from a single "polluted" MailerLite account to a dual-account setup that:
- Preserves deliverability reputation for engaged subscribers
- Establishes a clean slate for new subscribers on the
ixdf.orgdomain - Phases out inactive subscribers from the old
interaction-design.orgdomain
Key Decisions
| Decision | Outcome |
|---|---|
| Create new MailerLite account | Yes - separate account for ixdf.org domain |
| Member newsletter chain | Fully migrate to NEW account, restart all members on email #1 |
| Non-member newsletter chain | NEW subscribers → NEW account; OLD subscribers → OLD account (phase out) |
| Exclusive mailers | Send to BOTH accounts |
| Subscriber count display | Add 300,000 offset to NEW account count |
2. Current State Analysis
2.1 MailerLite Account Statistics
| Metric | Value |
|---|---|
| Total subscribers | 417,509 |
| Inactive subscribers (6+ months no opens) | 147,323 (~35%) |
| Active Newsletter group subscribers | 5,897 |
| Bounced | 40,862 |
| Unsubscribed | 67,720 |
2.2 Current Groups
| Group | Active | Purpose |
|---|---|---|
| Newsletter | 5,897 | Main newsletter list (synced from IxDF app) |
| IxDF Local Leaders and Managers | 424 | Local group leaders |
| RookieUp | 0 | Legacy acquisition |
| LinkedIn Newsletter Signups | 0 | LinkedIn campaign |
2.3 Current Segments (Key Ones)
| Segment | Total | Open Rate | Purpose |
|---|---|---|---|
| Non Members | 188,020 | 20.97% | Acquisition targets |
| All members with any status | 112,110 | 33.98% | All member types |
| Canceled Members | 91,454 | 31.67% | Reactivation targets |
| Active or Expired Members | 20,564 | 38.91% | Current/recent members |
| Individual Members | 19,591 | 39.06% | Individual plan holders |
| Company Members | 973 | 34.10% | Team plan members |
| Mxtoolbox | 300,130 | 23.38% | Deliverability monitoring |
| Inactive subscribers | 147,323 | 9.67% | Cleanup candidates |
2.4 Current Automations
| Automation | Steps | Description |
|---|---|---|
| Member Automated Chain | 84 | Weekly emails for members |
| Non-Member Automated Chain | 88 | Weekly emails for non-members |
| Re-engage Inactive Subscribers | 11 | Win-back campaign |
2.5 Application Integration
The IxDF application syncs subscriber data to MailerLite via:
Files involved:
config/ixdf_mailerlite.php- Configuration (API key, group ID)app/Modules/Subscription/Services/MailerLite/MailerLiteSubscriberDataManager.php- API clientapp/Modules/Subscription/Jobs/MailerLite/SendSubscriberData.php- Sync jobapp/Modules/Subscription/Listeners/UpdateSubscriberInExternalList.php- Subscriber eventsapp/Modules/Subscription/Listeners/UpdateMembershipInExternalList.php- Membership events
Data synced to MailerLite:
email- Subscriber emailname- Subscriber namecountry- Country namemembership_plan_category-individual|team|non-membermember_status-non-member|canceled|active-or-expiredmembership_expire_at- Expiration date (Y-m-d)
Webhook events received:
subscriber.bounced(hard bounces)subscriber.unsubscribedsubscriber.spam_reported
3. Problem Statement
3.1 Deliverability Risk
Current list has ~147,000 inactive subscribers (35%) who:
- Never open emails
- Negatively impact sender reputation with mailbox providers (Gmail, Outlook, Yahoo)
- Increase risk of spam placement
3.2 Mailbox Provider Signals
Providers track and penalize based on:
- Low open rates
- Low click rates
- No replies
- Spam complaints
- Deletions without reading
3.3 Domain Reputation
The interaction-design.org domain's email reputation is tied to years of:
- Accumulated inactive subscribers
- Varying engagement patterns
- Unknown bounces and complaints
4. Migration Strategy
4.1 Dual-Account Architecture
text
┌─────────────────────────────────────────────────────────────────┐
│ OLD ACCOUNT │
│ (interaction-design.org) │
├─────────────────────────────────────────────────────────────────┤
│ Newsletter Chain: Non-Member only (phasing out) │
│ Exclusive Mailers: Yes (to both accounts) │
│ New Subscribers: NO │
│ Status: Maintenance mode → eventual sunset │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ NEW ACCOUNT │
│ (ixdf.org) │
├─────────────────────────────────────────────────────────────────┤
│ Newsletter Chain: Member (full migration) + Non-Member (new) │
│ Exclusive Mailers: Yes (to both accounts) │
│ New Subscribers: YES (all new signups) │
│ Status: Primary account going forward │
└─────────────────────────────────────────────────────────────────┘4.2 Migration Phases
Phase 1: Pre-Launch Setup
- [ ] Create new MailerLite account with
ixdf.orgdomain - [ ] Configure DNS (SPF, DKIM, DMARC) for new domain
- [ ] Set up new sending subdomain (e.g.,
mail.ixdf.org) - [ ] Create automation chains in new account (Member + Non-Member)
- [ ] Update application to support dual-account sync
Phase 2: Domain Warm-up (3-4 months before mass sending)
- [ ] Start sending low volumes from new domain
- [ ] Gradually increase volume
- [ ] Monitor deliverability metrics
Phase 3: Public Launch (Rebirth)
- [ ] Switch new subscriber signups to NEW account
- [ ] Migrate ALL members to NEW account (restart on email #1)
- [ ] Keep old Non-Member chain running on OLD account
Phase 4: Active Subscriber Migration
- [ ] Send 3 "[ACTION REQUIRED]" emails to OLD list
- [ ] Move subscribers who click "Yes, I want to continue" to NEW account
- [ ] Start migrated subscribers on email #1 in NEW account
Phase 5: List Cleanup
- [ ] Run MailerLite "Clean up inactive" on OLD account
- [ ] Remove subscribers with 6+ months of no opens
- [ ] Maintain OLD account in reduced-volume mode
5. Technical Requirements
5.1 Application Changes
5.1.1 Dual-Account Configuration
php
// config/ixdf_mailerlite.php (proposed structure)
return [
'accounts' => [
'old' => [
'api_key' => env('MAILERLITE_API_KEY_OLD'),
'groups' => [
'newsletter' => ['id' => '105313679009908297'],
],
'domain' => 'interaction-design.org',
'enabled_for_new_subscribers' => false,
],
'new' => [
'api_key' => env('MAILERLITE_API_KEY_NEW'),
'groups' => [
'newsletter_members' => ['id' => 'TBD'],
'newsletter_non_members' => ['id' => 'TBD'],
],
'domain' => 'ixdf.org',
'enabled_for_new_subscribers' => true,
],
],
];5.1.2 Subscriber Routing Logic
php
// Pseudocode for subscriber routing
class SubscriberRouter
{
public function routeSubscriber(Subscriber $subscriber): string
{
// New subscribers always go to NEW account
if ($subscriber->wasRecentlyCreated) {
return 'new';
}
// Members on NEW account
if ($subscriber->isMember()) {
return 'new';
}
// Migrated subscribers (clicked "Yes" in migration email)
if ($subscriber->migrated_to_new_account) {
return 'new';
}
// Legacy non-members stay on OLD
return 'old';
}
}5.1.3 Database Changes
sql
-- Add migration tracking to subscribers table
ALTER TABLE subscription__contact_lists
ADD COLUMN mailerlite_account ENUM('old', 'new') DEFAULT 'old',
ADD COLUMN migrated_at TIMESTAMP NULL,
ADD COLUMN migration_source VARCHAR(50) NULL; -- 'launch', 'email_confirmation', 'manual'5.1.4 Subscriber Count Display
php
// app/Modules/Subscription/Values/NewsletterSubscribersCount.php
class NewsletterSubscribersCount extends CachedValue
{
private const DISPLAY_OFFSET = 300_000;
public static function displayValue(): int
{
$newAccountCount = self::getNewAccountCount();
return $newAccountCount + self::DISPLAY_OFFSET;
}
}5.2 Migration Confirmation Endpoint
php
// Route: POST /newsletter/confirm-migration
// Controller: ConfirmMigrationController
public function __invoke(Request $request): Response
{
$token = $request->input('token');
$subscriber = Subscriber::where('migration_token', $token)->firstOrFail();
// 1. Update local database
$subscriber->update([
'mailerlite_account' => 'new',
'migrated_at' => now(),
'migration_source' => 'email_confirmation',
]);
// 2. Remove from OLD MailerLite account
dispatch(new RemoveFromOldMailerLite($subscriber));
// 3. Add to NEW MailerLite account (starts on email #1)
dispatch(new AddToNewMailerLite($subscriber));
return redirect('/newsletter/migration-confirmed');
}5.3 Segment Automation
php
// Console command to sync conditional segments
// php artisan mailerlite:sync-segments
class SyncMailerLiteSegments extends Command
{
public function handle(): void
{
// Renewal segment: Members expiring within 30 days
$this->syncSegment('renewal', function () {
return Member::query()
->whereDate('expires_at', '<=', now()->addDays(30))
->whereDate('expires_at', '>', now())
->pluck('email');
});
// Expired segment: Members expired within last 30 days
$this->syncSegment('expired', function () {
return Member::query()
->whereDate('expires_at', '>=', now()->subDays(30))
->whereDate('expires_at', '<', now())
->pluck('email');
});
// Retention segment: Active members with low engagement
// (implementation depends on engagement tracking)
}
}6. Implementation Plan
6.1 Phase 1: Infrastructure Setup
| Task | Owner | Status |
|---|---|---|
| Create new MailerLite account | Alies | [ ] |
| Configure sending domain DNS (SPF, DKIM, DMARC) | Alies | [ ] |
| Create API credentials for new account | Alies | [ ] |
| Update application config for dual-account | Alies | [ ] |
| Add database migration for tracking | Alies | [ ] |
| Set up webhook endpoints for new account | Alies | [ ] |
6.2 Phase 2: Automation Setup
| Task | Owner | Status |
|---|---|---|
| Clone Member Automated Chain to new account | Veronika | [ ] |
| Clone Non-Member Automated Chain to new account | Veronika | [ ] |
| Write first 3 emails for each chain (buffer) | Rikke | [ ] |
| Test automation triggers | Alies/Veronika | [ ] |
6.3 Phase 3: Migration Emails
| Task | Owner | Status |
|---|---|---|
| Write 3 "[ACTION REQUIRED]" migration emails | Veronika | [ ] |
| Implement migration confirmation endpoint | Alies | [ ] |
| Create migration confirmation landing page | Alies | [ ] |
| Test migration flow end-to-end | Alies/Veronika | [ ] |
6.4 Phase 4: Application Updates
| Task | Owner | Status |
|---|---|---|
Implement SubscriberRouter logic | Alies | [ ] |
| Update subscriber sync jobs for dual-account | Alies | [ ] |
| Update subscriber count display logic | Alies | [ ] |
| Add feature flag for migration cutover | Alies | [ ] |
| Update exclusive mailer sending to target both accounts | Alies | [ ] |
6.5 Phase 5: Cleanup
| Task | Owner | Status |
|---|---|---|
| Run "Clean up inactive" on OLD account | Veronika | [ ] |
| Delete unnecessary/legacy segments | Veronika | [ ] |
| Archive or sunset unused automations | Veronika | [ ] |
| Document final state of both accounts | Alies/Veronika | [ ] |
7. Audience Segments & Communication Types
7.1 Audience Segments
| Segment | Description | Goal |
|---|---|---|
| Acquisition | Non-members (potential members) | Convert to membership |
| Renewal | Active members expiring within 30 days | Renew before expiry |
| Retention | Active members with low engagement ("zombie" members) | Increase engagement |
| Reactivation | Members whose membership expired | Get them to reactivate |
| Active Members | Active users (individual, company, EPs) | Keep informed and happy |
7.2 Communication Types
| Type | Recipients | Frequency |
|---|---|---|
| Campaign offers | Non-members, Renewal, Retention, Reactivation | Monthly (non-members), Twice yearly (others) |
| Masterclass/Course announcements | All members, Non-members | As scheduled |
| Almanac newsletter | All members, Non-members | Monthly |
| Automated weekly newsletter | Members (chain), Non-members (chain) | Weekly |
7.3 Segment Implementation
| Segment | Type | Automation |
|---|---|---|
| Non Members | MailerLite conditional segment | member_status = 'non-member' |
| Individual Members | MailerLite conditional segment | membership_plan_category = 'individual' |
| Company Members | MailerLite conditional segment | membership_plan_category = 'team' |
| Active or Expired | MailerLite conditional segment | member_status = 'active-or-expired' |
| Canceled Members | MailerLite conditional segment | member_status = 'canceled' |
| Renewal (expiring soon) | Application-managed | Sync via console command |
| Expired (recently) | Application-managed | Sync via console command |
| Retention (low engagement) | Application-managed | Requires engagement tracking |
8. Open Questions
8.1 Resolved Questions
| # | Question | Answer |
|---|---|---|
| 1 | When a member cancels, do they automatically move from Member chain to Non-Member chain? | No automatic move. Both chains trigger on "subscriber joins Newsletter group" but have condition steps. Member chain checks for "Individual Members" segment, Non-Member chain checks for "Non Members" segment. When status changes, subscriber exits current chain at next condition check but doesn't automatically enter another chain. |
| 2 | Can we consolidate Member and Non-Member chains into one with proper conditioning? | Technically possible but not recommended for migration. Would require significant restructuring. Better to migrate existing chains as-is and optimize later. |
| 3 | How does the application currently handle membership status changes in relation to MailerLite automations? | When membership changes, IndividualMembershipUpdated event fires → UpdateMembershipInExternalList listener → SendSubscriberData job updates member_status field in MailerLite. This changes which segment the subscriber belongs to, affecting automation condition checks. |
8.2 Automation Routing Discovered
Current mechanism:
text
Subscriber joins Newsletter group
↓
┌───────────────────────────────────┬───────────────────────────────────┐
│ Member Automated Chain │ Non-Member Automated Chain │
│ (triggers on group join) │ (triggers on group join) │
├───────────────────────────────────┼───────────────────────────────────┤
│ Condition: Is Individual │ Delay step │
│ Member segment? │ ↓ │
│ ↓ │ Condition: Is Non Members │
│ YES → Continue to emails │ segment? │
│ NO → Exit automation │ ↓ │
│ │ YES → Continue to emails │
│ │ NO → Exit automation │
└───────────────────────────────────┴───────────────────────────────────┘What happens when membership status changes:
- Member becomes non-member (cancels):
member_statusupdates tocanceled - Subscriber moves to "Canceled Members" segment
- In Member chain: At next condition check, no longer matches "Individual Members" → exits
- Does NOT automatically enter Non-Member chain (triggers only fire on group JOIN)
- Implication: Canceled members stop receiving automated emails entirely until re-subscribed
8.3 Remaining Technical Questions
| # | Question | Owner | Status |
|---|---|---|---|
| 4 | Can MailerLite's "one-click unsubscribe" (List-Unsubscribe header) be configured to work without leaving Gmail? | Alies | [ ] Investigate |
| 5 | What is the "Mxtoolbox" segment (300K subscribers) and should it be migrated? | Veronika | [ ] Clarify |
8.4 Business Questions
| # | Question | Owner | Status |
|---|---|---|---|
| 1 | Confirm rebirth campaign will occur on OLD account before domain warm-up completes | Mads | [ ] Confirm |
| 2 | Should Local Leaders group be migrated to new account? | Mads | [ ] Decide |
| 3 | What happens to the 40K+ bounced emails in the old account? | Veronika | [ ] Clarify |
9. Timeline Considerations
9.1 Domain Warm-up Period
Critical: New ixdf.org domain requires 3-4 months warm-up before mass sending.
text
Timeline:
├── Month 1-2: Very low volume (hundreds/day)
├── Month 3: Moderate volume (thousands/day)
├── Month 4: Higher volume (tens of thousands/day)
└── Month 5+: Full volume capability9.2 Rebirth Campaign Implication
If the rebirth campaign is scheduled before the 4-month warm-up completes:
- Rebirth campaign emails MUST be sent from OLD account
- NEW account should only receive post-launch signups initially
9.3 Recommended Timeline
| Milestone | Timing |
|---|---|
| New account created + DNS configured | ASAP |
| Start domain warm-up | ASAP |
| Application changes complete | Before rebirth |
| Rebirth campaign | From OLD account |
| Member migration to NEW account | At rebirth launch |
| Migration emails to OLD list | 1-2 weeks after rebirth |
| List cleanup on OLD account | 1 month after migration emails |
10. Key Risks and Mitigations
| Risk | Impact | Mitigation |
|---|---|---|
| Domain warm-up not complete before rebirth | Mass send from cold domain → spam folder | Start warm-up ASAP; send rebirth campaign from OLD account |
| Dual-account complexity increases maintenance | Bugs, sync issues, forgotten updates | Clear documentation; feature flags; automated tests |
| Migration confirmation emails have low engagement | Few subscribers migrate; effort wasted | Compelling subject lines; clear value prop; 3-email sequence |
| Canceled members stop receiving all emails | Lost re-engagement opportunity | Consider separate "Canceled Members" automation in new account |
| Loss of historical engagement data | Can't compare pre/post migration | Export metrics before migration; document baseline |
| Webhook configuration forgotten for new account | Bounces/unsubscribes not synced | Checklist item; automated verification |
11. Action Items Summary
Alies (Technical)
| Priority | Task | Dependencies |
|---|---|---|
| P0 | Create new MailerLite account | None |
| P0 | Configure DNS for ixdf.org sending domain | New account created |
| P0 | Start domain warm-up | DNS configured |
| P1 | Update app config for dual-account support | None |
| P1 | Add database migration for tracking | None |
| P1 | Implement SubscriberRouter logic | Config done |
| P1 | Create migration confirmation endpoint | Database migration done |
| P2 | Set up webhooks for new account | New account created |
| P2 | Update exclusive mailer logic for dual-send | Config done |
| P2 | Update subscriber count display (300K offset) | Config done |
Veronika (Operations)
| Priority | Task | Dependencies |
|---|---|---|
| P0 | Clone automations to new account | New account created |
| P1 | Write 3 migration emails (ACTION REQUIRED) | Rikke input on messaging |
| P1 | Clarify Mxtoolbox segment purpose | None |
| P2 | Delete unnecessary segments from old account | After Alies confirms no code dependencies |
| P2 | Run "Clean up inactive" on old account | After migration emails sent |
Rikke (Content)
| Priority | Task | Dependencies |
|---|---|---|
| P0 | Write first 3 Member chain emails | None |
| P0 | Write first 3 Non-Member chain emails | None |
| P1 | Review/approve migration email copy | Veronika draft |
Mads (Decisions)
| Priority | Task | Dependencies |
|---|---|---|
| P0 | Confirm rebirth campaign timing vs warm-up | Timeline from Alies |
| P1 | Decide on Local Leaders group migration | None |
Appendix A: Current Webhook Configuration
Route: POST /api/webhooks/newsletter/update-subscriber-statusMiddleware: VerifyMailerLiteWebhookSignatureSecret: MAILERLITE_SUBSCRIBER_EVENTS_SECRET
Events handled:
subscriber.bounced(hard bounces only)subscriber.unsubscribedsubscriber.spam_reported
Appendix B: Glossary
| Term | Definition |
|---|---|
| Automation (Workflow) | Sequence of emails triggered by subscriber action |
| Campaign | One-off email sent manually or scheduled |
| Group | Static list of subscribers (manual add/remove) |
| Segment | Dynamic list based on field conditions |
| Exclusive Mailer | One-off announcement emails (masterclasses, campaigns) |
| Chained Emails | Weekly sequential newsletter emails (automation) |
| Warm-up | Gradual increase of sending volume for new domain |
Appendix C: MailerLite Account Credentials
| Account | Environment Variable | Domain |
|---|---|---|
| OLD | MAILERLITE_API_KEY | interaction-design.org |
| NEW | MAILERLITE_API_KEY_NEW | ixdf.org |
Document History
| Date | Author | Changes |
|---|---|---|
| 2026-01-15 | Claude/Alies | Initial draft from unstructured notes |
| 2026-01-15 | Claude/Alies | Added automation routing analysis, resolved questions, action items |
Quick Reference: Key IDs
| Entity | ID | Notes |
|---|---|---|
| Newsletter Group (OLD) | 105313679009908297 | Main subscriber group |
| Member Automated Chain | 106978041733842297 | 84 steps |
| Non-Member Automated Chain | 107905476279142043 | 88 steps |
| Non Members Segment | 106169135938929783 | 188K subscribers |
| Individual Members Segment | 106169420470028078 | 19.5K subscribers |
| Active or Expired Segment | 106177092348020160 | 20.5K subscribers |
| Canceled Members Segment | 106177352879310352 | 91.4K subscribers |