Веб

Как закачать все письма с 1 янв. 2024 в cron с Imapflow

Узнайте, как использовать поиск по дате и асинхронный итератор Imapflow в cron‑задаче Strapi, чтобы получить все письма с 1 янв. 2024 без пропусков дубликатов.

Как быстро и надёжно получить все письма с 1 января 2024 года до сегодняшнего дня, используя Imapflow в cron‑задаче, чтобы не возникали ошибки и не пропускались сообщения? Я сейчас вытаскиваю их по message_id, но кажется, что некоторые письма пропускаются. Есть ли более надёжный способ, или я просто делаю что‑то не так? Я использую Strapi 5.18.1 как бекенд.

Самый быстрый и надёжный способ — дать Imapflow выполнить поиск по дате, а затем стримить результаты через fetch‑итератор. Это устраняет пропуски UID, избавляет от «пропущенных писем» и отлично работает в cron‑задачах.


Содержание


Почему поиск по дате безопаснее, чем по message_id

На самом деле, message_id (заголовок RFC‑822 Message‑ID) является необязательным и может дублироваться или отсутствовать. Если полагаться на него, можно пропустить письма без ID или с одинаковым ID.
Поиск по SINCE/BEFORE гарантирует, что все сообщения в почтовом ящике, попадающие в диапазон, будут возвращены, независимо от заголовка Message‑ID.
Документация Imapflow подтверждает, что метод search принимает строки since и before в формате DD-MMM-YYYY и возвращает UID‑ы, удовлетворяющие критериям.


Подготовка клиента Imapflow для cron‑задачи

Создайте один постоянный клиент, который можно переиспользовать между запусками, если планировщик держит процесс живым.
Всегда захватывайте блокировку ящика перед запросом, чтобы избежать гонок.
Если соединение разорвалось, автоматически переподключайтесь; Imapflow бросает ConnectionError, который можно перехватить и повторить попытку.

ts
import { ImapFlow } from 'imapflow';
import { simpleParser } from 'mailparser';

const client = new ImapFlow({
  host: 'imap.example.com',
  port: 993,
  secure: true,
  auth: { user: 'user@example.com', pass: 'app‑pw' },
});

async function ensureConnected() {
  if (!client.isConnected) await client.connect();
}

Синтаксис поиска по диапазону дат

Imapflow ожидает даты в формате DD-MMM-YYYY (например, 01-Jan-2024).
Чтобы охватить «1 января 2024 до сегодняшнего дня», задайте since этой датой и опустите before (или установите его в завтрашнюю дату, чтобы включить все письма до сегодняшнего дня).

ts
const since = '01-Jan-2024';
const before = new Date();
before.setDate(before.getDate() + 1); // завтра
const beforeStr = `${before.getDate()}-${['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][before.getMonth()]}-${before.getFullYear()}`;

const searchOpts = { since, before: beforeStr, all: true };

Флаг all: true заставляет Imapflow возвращать все совпадающие UID, даже если в ящике есть флаги, которые обычно бы их отфильтровали.


Получение всех результатов через итератор

Imapflow предоставляет асинхронный итератор (client.fetch), который стримит письма по одному, предотвращая переполнение памяти.

ts
await client.getMailboxLock('INBOX');
try {
  for await (const msg of client.fetch(searchOpts, { source: true })) {
    const parsed = await simpleParser(msg.source);
    // Сохраняем или обрабатываем разобранное письмо
  }
} finally {
  client.releaseMailboxLock();
}

Если вам нужны только заголовки или флаги, замените source: true на, например, { envelope: true, flags: true }.


Обработка пагинации и ошибок

Пагинация: если в ящике тысячи писем, итератор автоматически справится с серверной пагинацией.
Обработка ошибок: оберните весь блок в try/catch.
ConnectionError: переподключитесь и повторите попытку.
FetchError: логируйте и пропускайте проблемное письмо.
TimeoutError: увеличьте client.timeout или прервите цикл и попробуйте позже.

ts
try {
  // цикл fetch
} catch (err) {
  if (err.name === 'ConnectionError') {
    await client.connect();
    // При желании повторить fetch
  } else {
    console.error('Fetch error:', err);
  }
}

Полный пример для cron‑задачи Strapi

В Strapi 5.18.1 можно добавить cron‑задачу в config/functions/cron.js:

js
// config/functions/cron.js
module.exports = () => {
  const { ImapFlow } = require('imapflow');
  const { simpleParser } = require('mailparser');

  const client = new ImapFlow({
    host: process.env.IMAP_HOST,
    port: 993,
    secure: true,
    auth: { user: process.env.IMAP_USER, pass: process.env.IMAP_PASS },
  });

  async function fetchOldEmails() {
    await client.connect();
    await client.getMailboxLock('INBOX');

    const since = '01-Jan-2024';
    const before = new Date();
    before.setDate(before.getDate() + 1);
    const beforeStr = `${before.getDate()}-${['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][before.getMonth()]}-${before.getFullYear()}`;

    const searchOpts = { since, before: beforeStr, all: true };

    try {
      for await (const msg of client.fetch(searchOpts, { source: true })) {
        const parsed = await simpleParser(msg.source);
        // Пример: создать запись в Strapi
        await strapi.entityService.create('api::email.email', {
          data: {
            subject: parsed.subject,
            from: parsed.from.text,
            date: parsed.date,
            body: parsed.text,
          },
        });
      }
    } catch (e) {
      strapi.log.error('Imapflow fetch error:', e);
    } finally {
      client.releaseMailboxLock();
      await client.logout();
    }
  }

  // Запуск каждый день в 02:00
  strapi.cron.schedule('0 2 * * *', fetchOldEmails);
};

Убедитесь, что переменные окружения с учётными данными IMAP заданы.


Устранение распространённых проблем

Симптом Возможная причина Как исправить
Письма пропадают Используется message_id, который не гарантирован Переключитесь на поиск по дате, как показано
Дублирующиеся UID Не используется all: true при наличии флагов Добавьте all: true
Проблемы соединения в cron Процесс cron завершается после выполнения, но клиент остаётся подключённым Вызывайте client.connect() в начале каждой задачи
Большой объём памяти Загружаются все письма сразу Используйте асинхронный итератор (for await)
Несоответствие часового пояса Даты интерпретируются в локальном времени сервера Используйте UTC‑даты или установите client.tz, если поддерживается

Итог

Используя поиск по дате с since/before и стриминг через асинхронный итератор Imapflow, вы надёжно получите каждое письмо с 1 января 2024 до настоящего момента, не пропуская и не дублируя. Встраиванием логики в cron‑задачу Strapi, автоматическим переподключением и надёжной обработкой ошибок вы получите устойчивое и поддерживаемое решение.


Источники

  1. Imapflow – Module documentation
  2. Imapflow GitHub repository
  3. Imapflow – Searching Messages
  4. Imapflow – Fetching Messages
  5. Strapi – Cron Jobs
Авторы
Проверено модерацией
Модерация