Skip to content

MailerLite Migration Specification

GitHub Issue: https://github.com/InteractionDesignFoundation/IxDF-web/issues/26906Status: Draft Last Updated: 2026-01-15


Table of Contents

  1. Executive Summary
  2. Current State Analysis
  3. Problem Statement
  4. Migration Strategy
  5. Technical Requirements
  6. Implementation Plan
  7. Audience Segments & Communication Types
  8. Open Questions
  9. 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.org domain
  • Phases out inactive subscribers from the old interaction-design.org domain

Key Decisions

DecisionOutcome
Create new MailerLite accountYes - separate account for ixdf.org domain
Member newsletter chainFully migrate to NEW account, restart all members on email #1
Non-member newsletter chainNEW subscribers → NEW account; OLD subscribers → OLD account (phase out)
Exclusive mailersSend to BOTH accounts
Subscriber count displayAdd 300,000 offset to NEW account count

2. Current State Analysis

2.1 MailerLite Account Statistics

MetricValue
Total subscribers417,509
Inactive subscribers (6+ months no opens)147,323 (~35%)
Active Newsletter group subscribers5,897
Bounced40,862
Unsubscribed67,720

2.2 Current Groups

GroupActivePurpose
Newsletter5,897Main newsletter list (synced from IxDF app)
IxDF Local Leaders and Managers424Local group leaders
RookieUp0Legacy acquisition
LinkedIn Newsletter Signups0LinkedIn campaign

2.3 Current Segments (Key Ones)

SegmentTotalOpen RatePurpose
Non Members188,02020.97%Acquisition targets
All members with any status112,11033.98%All member types
Canceled Members91,45431.67%Reactivation targets
Active or Expired Members20,56438.91%Current/recent members
Individual Members19,59139.06%Individual plan holders
Company Members97334.10%Team plan members
Mxtoolbox300,13023.38%Deliverability monitoring
Inactive subscribers147,3239.67%Cleanup candidates

2.4 Current Automations

AutomationStepsDescription
Member Automated Chain84Weekly emails for members
Non-Member Automated Chain88Weekly emails for non-members
Re-engage Inactive Subscribers11Win-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 client
  • app/Modules/Subscription/Jobs/MailerLite/SendSubscriberData.php - Sync job
  • app/Modules/Subscription/Listeners/UpdateSubscriberInExternalList.php - Subscriber events
  • app/Modules/Subscription/Listeners/UpdateMembershipInExternalList.php - Membership events

Data synced to MailerLite:

  • email - Subscriber email
  • name - Subscriber name
  • country - Country name
  • membership_plan_category - individual | team | non-member
  • member_status - non-member | canceled | active-or-expired
  • membership_expire_at - Expiration date (Y-m-d)

Webhook events received:

  • subscriber.bounced (hard bounces)
  • subscriber.unsubscribed
  • subscriber.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.org domain
  • [ ] 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

TaskOwnerStatus
Create new MailerLite accountAlies[ ]
Configure sending domain DNS (SPF, DKIM, DMARC)Alies[ ]
Create API credentials for new accountAlies[ ]
Update application config for dual-accountAlies[ ]
Add database migration for trackingAlies[ ]
Set up webhook endpoints for new accountAlies[ ]

6.2 Phase 2: Automation Setup

TaskOwnerStatus
Clone Member Automated Chain to new accountVeronika[ ]
Clone Non-Member Automated Chain to new accountVeronika[ ]
Write first 3 emails for each chain (buffer)Rikke[ ]
Test automation triggersAlies/Veronika[ ]

6.3 Phase 3: Migration Emails

TaskOwnerStatus
Write 3 "[ACTION REQUIRED]" migration emailsVeronika[ ]
Implement migration confirmation endpointAlies[ ]
Create migration confirmation landing pageAlies[ ]
Test migration flow end-to-endAlies/Veronika[ ]

6.4 Phase 4: Application Updates

TaskOwnerStatus
Implement SubscriberRouter logicAlies[ ]
Update subscriber sync jobs for dual-accountAlies[ ]
Update subscriber count display logicAlies[ ]
Add feature flag for migration cutoverAlies[ ]
Update exclusive mailer sending to target both accountsAlies[ ]

6.5 Phase 5: Cleanup

TaskOwnerStatus
Run "Clean up inactive" on OLD accountVeronika[ ]
Delete unnecessary/legacy segmentsVeronika[ ]
Archive or sunset unused automationsVeronika[ ]
Document final state of both accountsAlies/Veronika[ ]

7. Audience Segments & Communication Types

7.1 Audience Segments

SegmentDescriptionGoal
AcquisitionNon-members (potential members)Convert to membership
RenewalActive members expiring within 30 daysRenew before expiry
RetentionActive members with low engagement ("zombie" members)Increase engagement
ReactivationMembers whose membership expiredGet them to reactivate
Active MembersActive users (individual, company, EPs)Keep informed and happy

7.2 Communication Types

TypeRecipientsFrequency
Campaign offersNon-members, Renewal, Retention, ReactivationMonthly (non-members), Twice yearly (others)
Masterclass/Course announcementsAll members, Non-membersAs scheduled
Almanac newsletterAll members, Non-membersMonthly
Automated weekly newsletterMembers (chain), Non-members (chain)Weekly

7.3 Segment Implementation

SegmentTypeAutomation
Non MembersMailerLite conditional segmentmember_status = 'non-member'
Individual MembersMailerLite conditional segmentmembership_plan_category = 'individual'
Company MembersMailerLite conditional segmentmembership_plan_category = 'team'
Active or ExpiredMailerLite conditional segmentmember_status = 'active-or-expired'
Canceled MembersMailerLite conditional segmentmember_status = 'canceled'
Renewal (expiring soon)Application-managedSync via console command
Expired (recently)Application-managedSync via console command
Retention (low engagement)Application-managedRequires engagement tracking

8. Open Questions

8.1 Resolved Questions

#QuestionAnswer
1When 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.
2Can 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.
3How 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:

  1. Member becomes non-member (cancels): member_status updates to canceled
  2. Subscriber moves to "Canceled Members" segment
  3. In Member chain: At next condition check, no longer matches "Individual Members" → exits
  4. Does NOT automatically enter Non-Member chain (triggers only fire on group JOIN)
  5. Implication: Canceled members stop receiving automated emails entirely until re-subscribed

8.3 Remaining Technical Questions

#QuestionOwnerStatus
4Can MailerLite's "one-click unsubscribe" (List-Unsubscribe header) be configured to work without leaving Gmail?Alies[ ] Investigate
5What is the "Mxtoolbox" segment (300K subscribers) and should it be migrated?Veronika[ ] Clarify

8.4 Business Questions

#QuestionOwnerStatus
1Confirm rebirth campaign will occur on OLD account before domain warm-up completesMads[ ] Confirm
2Should Local Leaders group be migrated to new account?Mads[ ] Decide
3What 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 capability

9.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
MilestoneTiming
New account created + DNS configuredASAP
Start domain warm-upASAP
Application changes completeBefore rebirth
Rebirth campaignFrom OLD account
Member migration to NEW accountAt rebirth launch
Migration emails to OLD list1-2 weeks after rebirth
List cleanup on OLD account1 month after migration emails

10. Key Risks and Mitigations

RiskImpactMitigation
Domain warm-up not complete before rebirthMass send from cold domain → spam folderStart warm-up ASAP; send rebirth campaign from OLD account
Dual-account complexity increases maintenanceBugs, sync issues, forgotten updatesClear documentation; feature flags; automated tests
Migration confirmation emails have low engagementFew subscribers migrate; effort wastedCompelling subject lines; clear value prop; 3-email sequence
Canceled members stop receiving all emailsLost re-engagement opportunityConsider separate "Canceled Members" automation in new account
Loss of historical engagement dataCan't compare pre/post migrationExport metrics before migration; document baseline
Webhook configuration forgotten for new accountBounces/unsubscribes not syncedChecklist item; automated verification

11. Action Items Summary

Alies (Technical)

PriorityTaskDependencies
P0Create new MailerLite accountNone
P0Configure DNS for ixdf.org sending domainNew account created
P0Start domain warm-upDNS configured
P1Update app config for dual-account supportNone
P1Add database migration for trackingNone
P1Implement SubscriberRouter logicConfig done
P1Create migration confirmation endpointDatabase migration done
P2Set up webhooks for new accountNew account created
P2Update exclusive mailer logic for dual-sendConfig done
P2Update subscriber count display (300K offset)Config done

Veronika (Operations)

PriorityTaskDependencies
P0Clone automations to new accountNew account created
P1Write 3 migration emails (ACTION REQUIRED)Rikke input on messaging
P1Clarify Mxtoolbox segment purposeNone
P2Delete unnecessary segments from old accountAfter Alies confirms no code dependencies
P2Run "Clean up inactive" on old accountAfter migration emails sent

Rikke (Content)

PriorityTaskDependencies
P0Write first 3 Member chain emailsNone
P0Write first 3 Non-Member chain emailsNone
P1Review/approve migration email copyVeronika draft

Mads (Decisions)

PriorityTaskDependencies
P0Confirm rebirth campaign timing vs warm-upTimeline from Alies
P1Decide on Local Leaders group migrationNone

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.unsubscribed
  • subscriber.spam_reported

Appendix B: Glossary

TermDefinition
Automation (Workflow)Sequence of emails triggered by subscriber action
CampaignOne-off email sent manually or scheduled
GroupStatic list of subscribers (manual add/remove)
SegmentDynamic list based on field conditions
Exclusive MailerOne-off announcement emails (masterclasses, campaigns)
Chained EmailsWeekly sequential newsletter emails (automation)
Warm-upGradual increase of sending volume for new domain

Appendix C: MailerLite Account Credentials

AccountEnvironment VariableDomain
OLDMAILERLITE_API_KEYinteraction-design.org
NEWMAILERLITE_API_KEY_NEWixdf.org

Document History

DateAuthorChanges
2026-01-15Claude/AliesInitial draft from unstructured notes
2026-01-15Claude/AliesAdded automation routing analysis, resolved questions, action items

Quick Reference: Key IDs

EntityIDNotes
Newsletter Group (OLD)105313679009908297Main subscriber group
Member Automated Chain10697804173384229784 steps
Non-Member Automated Chain10790547627914204388 steps
Non Members Segment106169135938929783188K subscribers
Individual Members Segment10616942047002807819.5K subscribers
Active or Expired Segment10617709234802016020.5K subscribers
Canceled Members Segment10617735287931035291.4K subscribers