Appearance
Email Domain Migration: interaction-design.org → ixdf.org
Executive Summary
Goal: Migrate email sending infrastructure from mailgun.interaction-design.org to mailgun.ixdf.org while maintaining email deliverability and avoiding DMARC authentication failures.
Key Challenge: During the gradual warm-up period, emails are sent through both old and new mailers. The sender email domain must dynamically match the mailer domain to pass DMARC authentication.
Solution: Dynamic email domain transformation in MailNotification that aligns the From address domain with the selected mailer.
Architecture Overview
Email Flow
text
Notification Created
↓
MailNotification::toMail()
↓
build() creates MailMessage with sender (e.g., mads@interaction-design.org)
↓
applySpecificMailerIfNeeded()
├─ Selects mailer based on:
│ 1. UseSpecificMailer attribute (explicit override)
│ 2. IxdfOrgMailerFeature lottery (warm-up percentage)
↓
alignSenderEmailDomainWithMailer()
├─ If mailer = mailgun-ixdf-org → transform @interaction-design.org → @ixdf.org
└─ If mailer = mailgun → keep @interaction-design.org (or reverse if needed)
↓
Email sent with aligned From addressDMARC Authentication
For emails to pass DMARC, the following must align:
| Component | Old Mailer | New Mailer |
|---|---|---|
| Sending Server | mailgun.interaction-design.org | mailgun.ixdf.org |
| DKIM Signs For | interaction-design.org | ixdf.org |
| SPF Authenticates | mailgun.interaction-design.org | mailgun.ixdf.org |
| From Address | @interaction-design.org | @ixdf.org |
Mismatch = DMARC Failure: If mailgun.ixdf.org sends email with From: mads@interaction-design.org, Gmail rejects with:
5.7.26 Unauthenticated email from interaction-design.org is not accepted due to domain's DMARC policyMigration Plan (Two Phases)
Phase 1: Domain Warm-up (2025-12-12 to 2026-03-12)
Objective: Build reputation for mailgun.ixdf.org gradually.
Implementation: IxdfOrgMailerFeature uses exponential warm-up schedule:
| Phase | Days | % | Emails/day (~7k total) |
|---|---|---|---|
| Baseline | 0-15 | 1-4% | 70-280 |
| Early | 16-30 | 5-10% | 350-700 |
| Growth | 31-50 | 14-28% | 980-1,960 |
| Mid-ramp | 51-70 | 35-65% | 2,450-4,550 |
| Final | 71-90 | 75-100% | 5,250-7,000 |
Dynamic Domain Transformation: During this phase, alignSenderEmailDomainWithMailer() ensures:
- Emails via
mailgun-ixdf-orguse@ixdf.orgFrom addresses - Emails via
mailgunuse@interaction-design.orgFrom addresses
Phase 2: Sender Address Migration (After 2026-03-12)
Objective: Update all sender email constants to @ixdf.org.
Files to Update:
app/Notifications/Senders/MadsLocalGroups.php→mads.soegaard.lg@ixdf.orgapp/Notifications/Senders/MadsAccounting.php→accounting@ixdf.orgapp/Notifications/Senders/MadsMarketingMailbox.php→mads.soegaard@ixdf.orgapp/Notifications/Senders/MadsReferralPrograms.php→mads.soegaard.p@ixdf.orgapp/Notifications/Senders/RikkeMemberSupport.php→rikke.friis.dam@ixdf.orgapp/Notifications/Senders/HelloAtInteractionDesignFoundation.php→hello@ixdf.org
Cleanup: Remove alignSenderEmailDomainWithMailer() method and @todo-by 2026-03-12 markers.
Key Components
IxdfOrgMailerFeature
Location: app/Modules/Notification/Features/IxdfOrgMailerFeature.php
Purpose: Controls gradual rollout of new mailer using Laravel Pennant feature flags.
Key Constants:
php
public const string OUTDATED_MAILER = 'mailgun';
public const string NEW_MAILER = 'mailgun-ixdf-org';Key Methods:
shouldUseNewMailer()- Returns true if current request should use new mailergetMailerName()- Returns appropriate mailer name based on feature flaggetRolloutPercentage()- Returns current warm-up percentage
MailNotification
Location: app/Notifications/Support/Mail/MailNotification.php
Key Methods:
applySpecificMailerIfNeeded()- Selects mailer and calls domain alignmentalignSenderEmailDomainWithMailer()- Transforms From address domain
Domain Transformation Logic:
php
private function alignSenderEmailDomainWithMailer(MailMessage $message, string $mailer): void
{
$useNewDomain = $mailer === IxdfOrgMailerFeature::NEW_MAILER;
$oldDomain = '@interaction-design.org';
$newDomain = '@ixdf.org';
if ($message->from !== [] && \is_string($message->from[0])) {
$message->from[0] = $useNewDomain
? str_replace($oldDomain, $newDomain, $message->from[0])
: str_replace($newDomain, $oldDomain, $message->from[0]);
}
}EmailSender Interface
Location: app/Notifications/Senders/EmailSender.php
Implementations:
MadsLocalGroups- Local Groups emailsMadsAccounting- Accounting/billing emailsMadsMarketingMailbox- Marketing emailsMadsReferralPrograms- Referral program emailsRikkeMemberSupport- Member support emailsHelloAtInteractionDesignFoundation- Generic IxDF emailsArbitrarySender- Dynamic sender (external emails)IxDFMember- Member-initiated emails
Monitoring & Verification
Key Metrics During Warm-up
| Metric | Target | Action if Exceeded |
|---|---|---|
| Bounce rate | <2% | Pause warm-up |
| Spam complaints | <0.1% | Investigate immediately |
| Delivery rate | >95% | Review Mailgun logs |
| Open rate | Compare to baseline | Monitor trends |
Verification Steps
Check Mailgun Logs:
- Verify DKIM/SPF/DMARC pass for emails via new mailer
- Monitor bounce rates by mailer
Test Email Delivery:
php// Trigger test notification $member->notify(new LocalGroupTestNotification());Verify Domain Alignment:
php// In test environment $notification = new SomeNotification(); $message = $notification->toMail($recipient); // Check mailer and from address alignment assert($message->mailer === 'mailgun-ixdf-org'); assert(str_contains($message->from[0], '@ixdf.org'));
Emergency Response
If deliverability issues occur:
- Immediate: Set
WARMUP_START_DATEto future date (reverts all traffic to old mailer) - Investigate: Check Mailgun logs for bounce/complaint patterns
- Resume: Adjust schedule if needed, restart warm-up
Related Files
app/Modules/Notification/Features/IxdfOrgMailerFeature.php- Feature flag logicapp/Notifications/Support/Mail/MailNotification.php- Domain alignmentapp/Notifications/Support/Mail/MailMessage.php- Mail message builderapp/Modules/Notification/Attributes/UseSpecificMailer.php- Override attributetests/Unit/Notifications/Support/Mail/MailMessageMailerSelectionTest.php- Testsconfig/mail.php- Mailer configuration
Known Limitations
Mailboxes Must Exist for Both Domains
During Phase 1 warm-up, emails may be sent from either @interaction-design.org or @ixdf.org addresses depending on which mailer is selected. Both mailboxes must exist in Google Workspace to receive replies:
| Sender Class | Old Email | New Email (must exist) |
|---|---|---|
| MadsLocalGroups | mads.soegaard.lg@interaction-design.org | mads.soegaard.lg@ixdf.org |
| MadsAccounting | accounting@interaction-design.org | accounting@ixdf.org |
| RikkeMemberSupport | rikke.friis.dam@interaction-design.org | rikke.friis.dam@ixdf.org |
| HelloAtInteractionDesignFoundation | hello@interaction-design.org | hello@ixdf.org |
Cross-Domain DKIM Not Supported
Mailgun cannot sign emails for domains outside its configured hierarchy. For example, mailgun.ixdf.org cannot DKIM-sign for @interaction-design.org. This is why dynamic domain transformation is required during the warm-up period.
Related Issues
- #26640 - Main email domain migration tracking issue
- #26665 - Smooth mail domain migration implementation
- #26766 - Local Groups sender email migration to @ixdf.org