Політика зберігання поштових даних за GDPR: терміни, право на видалення й автоочищення логів і листів
GDPR не вимагає «видалити все» — він вимагає задокументованого й автоматизованого життєвого циклу даних. Розкладаємо пошту по категоріях із власним строком і правовою підставою, автоматизуємо очищення (logrotate, journald, doveadm, pg_cron) і будуємо відтворюваний процес обробки запитів на видалення за ст. 17.
EvilMail Team21 липня 2026 р.13 хв читання
Типовий поштовий сервер тримає mail.log за пів року «про всяк випадок», а таблицю login_history — взагалі вічно, бо ніхто ніколи не писав скрипт, який би її чистив. Обидва набори нашпиговані персональними даними: клієнтські IP, envelope-адреси відправника й отримувача, user-agent, точні таймстемпи входів. За рішенням CJEU у справі Breyer (C-582/14) навіть динамічна IP-адреса є персональними даними, якщо оператор має законний спосіб зв'язати її з особою. Отже, ваш grep по mail.log — це обробка персональних даних, а зберігання цього файлу без строку — пряме порушення статті 5(1)(e) GDPR про обмеження зберігання.
Ключова помилка інженерів у тому, що вони читають GDPR як «зберігай якомога менше». Насправді регламент про інше: зберігай рівно стільки, скільки можеш обґрунтувати, і будь готовий довести, що решту видаляєш автоматично. Це принцип accountability зі ст. 5(2). Ручне «я іноді чищу старі логи» доказом не є. Доказ — це конфіг logrotate, cron-джоба з логуванням і рядок у вашому RoPA. Далі — як зібрати такий життєвий цикл технічно.
Що з вашої пошти є персональними даними
Перш ніж призначати строки, потрібна інвентаризація: керувати зберіганням того, чого ви не бачите, неможливо. Розкладіть усі джерела по категоріях і для кожного зафіксуйте правову підставу обробки за ст. 6:
GDPR і зберігання поштових даних: терміни, ст. 17 і автоочищення логів — EvilMail Blog
Dovecot mailstore (/var/mail/vhosts/{domain}/{user}/): самі тіла листів, вкладення, метадані IMAP-сесій. Підстава — contract (ст. 6(1)(b)): ви надаєте послугу зберігання пошти.
`login_history` (IP + user-agent + timestamp + success/fail): auth-аудит. Legitimate interest — виявлення brute-force і компрометації акаунтів.
`app_promo_events`: аналітика поведінки. Підстава залежить від того, чи це агрегована статистика, чи прив'язана до особи.
Інвойси й платіжні дані: legal obligation (ст. 6(1)(c)) — податкове й бухгалтерське законодавство.
Бекапи: не окрема категорія даних, а дзеркало всіх вищих. І це головна пастка комплаєнсу — про неї нижче.
Головний висновок цього кроку: IP-адреса в логах — персональні дані, навіть якщо вона динамічна. Envelope-адреса — персональні дані. Тому transport-логи не можна тримати «безстроково для статистики».
Скільки насправді зберігати: терміни за категоріями
Єдиного «строку GDPR» не існує — регламент не диктує жодного числа. Строк ви призначаєте самі й мусите його обґрунтувати. Це і є accountability. Нижче — робочі орієнтири, які легко захистити перед аудитором, і матриця, що показує їх одним поглядом.
Розшифровка по категоріях:
Temp-скриньки — строк дорівнює TTL сесії, 1 або 24 години. Дані щезають за дизайном ще до того, як хтось про них згадає.
Transport-логи Postfix — 7–30 днів для операційних потреб. До 90 днів можна тримати за legitimate interest для anti-abuse (розслідування спам-кампаній, накопичення репутаційних сигналів), але це вже треба чесно записати як окрему підставу.
`login_history` / auth — 6–12 місяців. Достатньо, щоб розслідувати інцидент безпеки, але не «назавжди».
Інвойси та податкові дані — 6–10 років залежно від юрисдикції. Тут спрацьовує ст. 17(3)(b): право на видалення НЕ діє, коли обробка потрібна для виконання юридичного обов'язку. Клієнт не може вимагати стерти інвойс, бо податкове законодавство перекриває його право на забуття. Це legal hold, і його треба вміти пояснити.
Бекапи — прив'язані до циклу ротації, а не до окремого строку категорії.
Автоматичне очищення логів
Почнемо з mail.log. Для rsyslog це logrotate. Мета — не тримати нічого старшого за призначений строк і не робити це руками.
Найпотужніший прийом мінімізації — анонімізація IP ще на етапі запису. Якщо для агрегації й дебагу вам достатньо підмережі, обрізайте останній октет IPv4 (і останні 80 біт IPv6). Обрізаний IP перестає бути персональними даними, а логи лишаються корисними для аналізу. Правило rsyslog, що маскує адресу перед записом:
ipv4.bits="8" mode="zero" перетворює 203.0.113.47 на 203.0.113.0. Тепер такі логи можна тримати довше без ризику, бо це вже не персональні дані в розумінні Breyer.
Автоочищення поштових скриньок
Dovecot уміє чистити сам. Для Trash і Junk налаштуйте autoexpunge прямо в namespace — це найчистіший приклад privacy by design зі ст. 25:
Для разових чи планових прочищень старих листів — doveadm. Він працює по всіх акаунтах (-A) з предикатами savedbefore / before:
bash
# видалити з Trash усе, збережене раніше ніж 30 днів тому
doveadm expunge -A mailbox Trash savedbefore 30d
# приклад політики: INBOX не старший за 2 роки (якщо так домовлено в оферті)
doveadm expunge -A mailbox INBOX before 2y
Дві вимоги до цих операцій. По-перше, expunge має бути ідемпотентним: повторний запуск на тих самих даних нічого не ламає. По-друге, факт видалення має логуватися в окремий audit-канал — але цей аудит теж персональні дані, тож і йому призначте строк (наприклад, ті самі 12 місяців, що й login_history).
TTL temp-скриньок на evilmail.pro — це той самий механізм, доведений до логічної межі: дані живуть 1 або 24 години й видаляються без жодного запиту. Мінімізація вбудована в продукт, а не прикручена політикою зверху.
Очищення бази даних
На рівні SQL найпростіший шлях — DELETE за предикатом часу:
sql
DELETE FROM login_history
WHERE created_at < now() - interval '12 months';
DELETE FROM app_promo_events
WHERE created_at < now() - interval '6 months';
Але для «гарячих» логів масовий DELETE — погана ідея: він лишає bloat, навантажує autovacuum і не звільняє диск одразу. Для таблиць, що ростуть постійно, правильніше партиціонування за created_at і DROP цілої партиції — це миттєве вивільнення й нуль навантаження на vacuum:
sql
-- замість DELETE — просто відкидаємо стару партицію
DROP TABLE login_history_2025_06;
Планувати очищення можна двома способами. Системний cron із psql — просто, але живе поза базою. pg_cron тримає розклад усередині Postgres, що зручніше для реплікації й аудиту:
sql
SELECT cron.schedule(
'purge-logins',
'0 3 * * *',
$$DELETE FROM login_history
WHERE created_at < now() - interval '12 months'$$
);
Окрема пастка — Prisma. Багато проєктів роблять soft-delete через поле deletedAt. Для UX це чудово, але soft-delete не задовольняє ст. 17: дані фізично лишаються в таблиці, отже вони НЕ видалені. Для реального виконання запиту на видалення потрібен hard delete, який стирає рядок насправді.
Право на видалення (ст. 17) як інженерний процес
Запит на видалення (DSAR erasure) не можна обробляти руками щоразу по-новому — це джерело помилок і пропущених дедлайнів. Потрібен відтворюваний workflow. Дедлайн за ст. 12(3) — один місяць від отримання запиту, з можливістю продовження ще на два за складності.
Три моменти, які інженери найчастіше провалюють. Перший — legal hold: перш ніж каскадно видаляти, перевірте, чи немає активного інвойсу, податкового зобов'язання або спору. Якщо є — ви відмовляєте у видаленні саме цих даних із посиланням на ст. 17(3)(b) або 17(3)(e), а не мовчки. Другий — бекапи: ви не зобов'язані розпаковувати кожен архів, щоб вирізати звідти один рядок. Прийнятна практика — позначити запис «не відновлювати» і дати даним щезнути природно при перезаписі бекапу в межах його циклу ротації. Але цю логіку треба задокументувати в RoPA, інакше в аудитора виникнуть питання. Третій — каскадність: видалення в БД без відповідного doveadm expunge лишає листи на диску, і навпаки.
Чеклист впровадження
RoPA зі ст. 30 із явним строком і правовою підставою по кожній категорії даних — це ваш головний доказ accountability.
Задокументований retention schedule, який збігається з тим, що реально роблять скрипти. Розбіжність між політикою на папері й cron-джобою гірша за відсутність політики.
Автоматизація з моніторингом: logrotate + doveadm + cron/pg_cron мало налаштувати — треба алерт, що вони справді відпрацьовують. Мовчазна зупинена cron-джоба = дані, що накопичуються поза строком.
Анонімізація IP там, де повний адрес не потрібен (mmanon у rsyslog) — знімає цілий клас логів з-під режиму персональних даних.
Політика бекапів із термінами ротації, шифруванням (ст. 32) і задокументованою логікою «не відновлювати видалене».
DPA з усіма субпроцесорами (хостинг, антиспам, аналітика) — їхні строки мають не суперечити вашим.
Квартальна перевірка, що ніщо не «протікає» повз строки: разовий скрипт, який рахує найстаріший запис у кожній таблиці й найстаріший файл у кожному лог-каталозі.
Дві найчастіші дірки в комплаєнсі — це soft-delete, який видає невидалені дані за видалені, і «сирі» бекапи, про які всі забувають. Закрийте ці дві — і решта retention-політики стає питанням дисципліни cron, а не юридичного ризику.