Міграція пошти без простою: масове перенесення скриньок через imapsync і безшовне перемикання MX
MX-запис — це не рубильник. Розбираємо, чому «просто змінити MX» дає кілька діб split-brain, і показуємо реальний runbook: знижений TTL, первинний imapsync, dual delivery, фінальна дельта й верифікація лічильників. Так інженер evilmail.pro переносить сотні скриньок за ніч без жодного втраченого листа.
EvilMail Team18 липня 2026 р.12 хв читання
Чому «просто змінити MX» ламає пошту
Типовий сценарій провалу виглядає так. Адмін об 11:00 заходить у панель DNS, міняє MX-запис із old.example.com на mail.evilmail.pro, тисне «зберегти» і йде пити каву з відчуттям виконаного обов'язку. Наступні 24–48 годин частина листів усе одно падає на старий сервер, у клієнтів скриньки наполовину порожні, а служба підтримки збирає тікети «де мій лист від контрагента».
Причина не в обсязі даних і не в imapsync. Причина в нерозумінні того, що взагалі робить MX-запис. MX керує тільки новою вхідною доставкою і не діє миттєво. Він не переносить жодного старого листа зі старого сервера. І він не «перемикається» в момент збереження: чужі резолвери тримають старе значення в кеші рівно стільки, скільки каже TTL. Якщо у вашого MX стояв TTL 3600 (а часто це взагалі 86400), то ще годину-добу після зміни половина світу продовжує слати пошту на старий хост. Це і є split-brain: два сервери одночасно приймають пошту для одного домену, і листи розтікаються по двох різних скриньках.
У міграції насправді сплетено три незалежні задачі, які всі помилково зводять до одного кліку:
перенести історію IMAP
Міграція пошти без простою: imapsync + безшовне перемикання MX — EvilMail Blog
— усі старі листи, папки, флаги, дати;
переключити вхідний потік — щоб нова пошта йшла на новий сервер;
не втратити нічого у вікні пропагації DNS — поки резолвери світу оновлюють кеш.
Кожна вирішується окремо і своїм інструментом. Далі — по одній фазі на кожну.
Модель міграції: три фази і одне золоте правило
Уся процедура розтягнута в часі навмисне. Ніяких «зробити все об 11:00».
T−72 год — знижуємо TTL MX-запису до 300 секунд. Це єдина зміна, яку треба зробити сильно заздалегідь, бо новий TTL сам почне діяти лише коли протухне старий.
T−24 год — первинний imapsync. Копіює ~99% історії, поки стара пошта повністю жива й користувачі нічого не помічають.
T−0 — вмикаємо dual delivery, міняємо основний MX, після цього ганяємо фінальну дельту.
T+72 год — верифікація лічильників, потім decommission старого сервера.
Золоте правило, яке робить нуль простою фізично можливим: обидва сервери приймають пошту протягом усього вікна cutover. Якщо старий сервер продовжує приймати й пересилати вхідне на новий, то навіть коли якийсь провінційний резолвер закешував старий MX на цілу добу довше очікуваного — лист усе одно доїде в правильну скриньку. Втрата стає неможливою не тому, що DNS швидкий, а тому, що ми взагалі не покладаємось на швидкість DNS.
Готуємо приймач: скриньки, автентифікація, TTL
До першого запуску imapsync на новому сервері вже має існувати все, у що він писатиме. Порядок тут критичний — жоден sync не створить вам SPF-запис і не валідує DKIM за вас.
Скриньки з відомими паролями. imapsync логіниться в приймач як звичайний IMAP-клієнт, тобто йому потрібен пароль кожного користувача. Якщо ви мігруєте сотні акаунтів і не хочете збирати паролі вручну, використайте майстер-пароль Dovecot або автентифікацію через doveadm від імені користувача — тоді у CSV достатньо логіна без реального пароля приймача.
SPF / DKIM / DMARC налаштовані й провалідовані заздалегідь. Це найчастіша причина, чому після cutover пошта починає падати в спам. DKIM-підпис нового сервера має валідуватись ще до того, як на нього піде бойовий вхідний і, головне, вихідний потік.
bash
# TTL MX має бути вже 300 за 72 год до cutover
dig +short evilmail.pro MX # 10 mail.evilmail.pro.
dig evilmail.pro MX | grep -E '^\S+\s+300' # переконатись, що TTL=300
# SPF: дозволяємо і власний mx, і інфраструктуру приймача
# v=spf1 mx include:_spf.evilmail.pro -all
# DKIM: селектор нового сервера має резолвитись і мати p=...
dig +short default._domainkey.example.com TXT
# "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."
# DMARC на час міграції — м'який, поки два підписувачі активні
# v=DMARC1; p=none; rua=mailto:[email protected]
Окремо про DMARC: не ставте `p=reject`, поки в домені два активні підписувачі (старий і новий сервери). У вікні cutover листи можуть підписуватись будь-яким із них; жорсткий DMARC відкидатиме валідну пошту. Тримайте p=none весь час міграції й підіймайте політику назад лише після decommission.
Масовий imapsync: батч по CSV і прапорці, що реально важать
Це ядро всієї процедури. Спершу — один робочий рядок, щоб розібрати кожен прапорець:
`--automap` — автоматично зіставляє спецпапки (Sent / Trash / Junk / Drafts) між серверами з різними схемами іменування. Без нього «Надіслані» легко приземляться в окрему нову папку замість Sent.
`--useheader Message-Id` — дедуплікація за заголовком листа. Саме це робить imapsync ідемпотентним: він звіряє листи не за позицією, а за Message-Id.
`--addheader` — дописує службовий заголовок про перенесення, зручно для аудиту.
`--nofoldersizes` — пропускає попередній підрахунок розмірів усіх папок. На великих скриньках економить хвилини на кожному запуску.
`--maxsize 104857600` — не тягнути листи важчі за 100 МБ (зазвичай це старі вкладення, які й так відкинуться лімітами приймача).
`--tmpdir /var/tmp/imapsync` — тут лежить кеш стану. Не чистьте його між запусками — саме він забезпечує ідемпотентність.
Для нестандартних імен папок додайте явний мапінг, напр. --f1f2 "INBOX.Sent=Sent".
Тепер батч. CSV із роздільником ; у форматі old_user;old_pass;new_user;new_pass і цикл, який пише окремий лог на кожну скриньку:
Перший прогін — обов'язково з `--dry`. Він нічого не пише, лише показує, що збирається робити: скільки листів, у які папки, чи не помилилися ви з мапінгом. Побачили осмислені числа — прибрали --dry, запустили по-справжньому.
Тротлінг — не опція, а необхідність.--maxbytespersecond 5000000 тримає ~5 МБ/с на скриньку. Gmail і Microsoft 365 агресивно банять інтенсивний IMAP: без ліміту ви на десятій скриньці отримаєте тимчасове блокування джерела, і міграція встане. Тримайте не більше 2–3 паралельних сесій на один хост-джерело, решту ганяйте послідовно. Типова швидкість — 20–40 листів/с на скриньку залежно від середнього розміру.
І головне про ідемпотентність: imapsync можна безпечно перезапускати скільки завгодно разів. Завдяки кешу в --tmpdir і звірці за Message-Id повторний запуск копіює лише те, чого бракує в приймачі. Обірвався по мережі на 300-й скриньці — просто запустіть скрипт знову, перші 299 пролетять майже миттєво.
Первинна синхронізація проти фінальної дельти
Один і той самий скрипт запускається двома хвилями.
Перша хвиля (T−24 год) переносить майже всю історію, поки користувачі спокійно працюють зі старою скринькою. Ця хвиля довга — години на великих обсягах — але нікого не турбує, бо стара пошта повністю жива й вхідне ще йде на старий сервер.
Друга хвиля (T−0, уже після переключення MX) — коротка дельта. Вона копіює лише те, що прийшло у вікно між первинним sync і cutover. Завдяки ідемпотентності це ті самі кілька команд, просто цього разу вони знаходять на порядок менше нових листів і завершуються за хвилини.
Про --delete2 --delete2folders — обережно. Ці прапорці вмикають дзеркалення: те, чого немає в джерелі, видаляється в приймачі. Для міграції вони зазвичай не потрібні й небезпечні. Використовуйте їх лише свідомо, коли справді робите дзеркало, а не одноразове перенесення. І тверде правило: нічого не видаляти на джерелі до підтвердженої верифікації лічильників. Старий сервер — це ваш план відкату; поки він живий, будь-яка помилка оборотна.
Переключення MX без split-brain
Найтонше місце всієї процедури. Наївний спосіб — «поміняли MX і чекаємо» — гарантує втрати, бо різні резолвери світу перемикаються в різний час. Правильний спосіб забезпечує dual delivery до зміни MX.
Два робочі варіанти dual delivery:
Relay на старому сервері. На старому Postfix прописуємо transport_maps, щоб увесь вхідний для домену пересилався на mail.evilmail.pro. Робимо це після первинного sync і до зміни MX. Тоді хоч би що приземлилось на старий сервер за час пропагації — воно миттєво їде на новий.
Старий сервер запасним MX після переключення. Коли основним MX стає новий хост, старий залишаємо другим MX із нижчим пріоритетом (більше число). Відправники, що вже бачать новий запис, ідуть напряму на новий; ті, у кого ще старий кеш, потрапляють на старий — а він через relay пересилає лист далі. Обидва шляхи сходяться в одну скриньку.
Ключове тут — relay: без нього другий MX сам по собі split-brain не лікує. Коли пересилання стоїть — міняємо основний MX. MX завжди останній крок, ніколи не перший.
Далі стежимо, коли трафік на старому сервері реально висохне:
bash
# що віддають різні публічні резолвери
dig @1.1.1.1 MX example.com +short
dig @8.8.8.8 MX example.com +short
# черга й вхідний потік на старому Postfix
postqueue -p
mailq
tail -f /var/log/mail.log
Коли postqueue -p показує «Mail queue is empty», а в mail.log нових вхідних для домену немає кілька годин поспіль — пропагація завершилась.
Верифікація, відкат і decommission
Перед тим як щось видаляти, звіряємо кількість листів по папках. На приймачі Dovecot:
bash
# кількість листів у папці нової скриньки
doveadm mailbox status -u [email protected] messages INBOX
doveadm mailbox status -u [email protected] messages '*'
# порівняння папок без копіювання — imapsync тільки рахує
imapsync --host1 old.example.com --user1 [email protected] --password1 '***' --ssl1 \
--host2 mail.evilmail.pro --user2 [email protected] --password2 '***' --ssl2 \
--justfoldersizes
Критерій успіху: лічильники по кожній значущій папці збігаються (невелика розбіжність у Junk/Trash допустима, якщо ви їх виключали). Тільки після цього старий сервер можна гасити.
Відкат займає 5 хвилин, і саме заради цього ми знижували TTL. Оскільки TTL = 300 і старий сервер ще живий, повернути MX назад — це один запис DNS, який пропагується за кілька хвилин. Тому джерело не гасимо, поки не витримали мінімум 72 години dual delivery й не підтвердили верифікацію.
Порядок згортання: спершу знімаємо dual delivery (relay на старому), переконавшись, що прямий потік на новий сервер стабільний; потім підіймаємо TTL MX назад до 3600, щоб не тримати домен на «нервовому» короткому кеші; далі повертаємо DMARC до p=quarantine/p=reject; і в останню чергу — гасимо старі скриньки.
Чеклист cutover-ночі
1.Заморозити будь-які зміни на джерелі (аліаси, форварди, нові скриньки).
2.За 72 год знизити TTL MX до 300 і переконатись dig-ом, що він реально протух.
3.Створити всі скриньки на приймачі; налаштувати SPF/DKIM/DMARC (p=none).
4.Первинний imapsync за 24 год — спершу --dry, потім бойовий прогін по CSV.
5.Перевірити, що DKIM приймача валідується (dig default._domainkey).
6.Увімкнути dual delivery: relay на старому Postfix або secondary MX.
7.Змінити основний MX-запис — і тільки тепер, не раніше.
8.Моніторити чергу старого MTA (postqueue -p) і пропагацію з 1.1.1.1/8.8.8.8.
9.Запустити фінальну дельту тим самим скриптом (короткий прогін).
10.Звірити лічильники (doveadm mailbox status, --justfoldersizes); decommission — не раніше ніж через 72 год dual delivery.