Реалізація масової відправки документів на підписання на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація масової відправки документів на підписання на сайті
Середня
~3-5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація масової відправки документів на підписання

Масова відправка документів на підписання—це сценарій, коли одна компанія відправляє сотні або тисячі персоналізованих документів отримувачам: трудові договори при масовому найму, акти виконаних робіт фрилансерам, згоди на обробку даних користувачам.

Архітектура системи

Масова рассилка не виконується синхронно—це фонова задача з чергою та прогрес-трекингом:

Користувач завантажує CSV/Excel зі списком отримувачів
  ↓
Валідація даних (email, ФІО, обов'язкові поля шаблону)
  ↓
Створення batch записи в БД
  ↓
Job queue: генерація персональних документів
  ├── Підстановка даних у шаблон
  ├── Генерація PDF
  ├── Створення унікальної ссилки на підписання
  └── Відправка email/SMS
  ↓
Мониторинг: хто відкрив, хто підписав, хто ігнорував

Модель даних

CREATE TABLE signing_batches (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name            VARCHAR(500),
  template_id     UUID REFERENCES document_templates(id),
  initiated_by    UUID REFERENCES users(id),
  total_count     INT NOT NULL,
  sent_count      INT DEFAULT 0,
  signed_count    INT DEFAULT 0,
  failed_count    INT DEFAULT 0,
  status          VARCHAR(50) DEFAULT 'pending',
  -- pending → processing → completed / partially_failed
  deadline_at     TIMESTAMPTZ,
  created_at      TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE signing_requests (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  batch_id        UUID REFERENCES signing_batches(id),
  recipient_email VARCHAR(500) NOT NULL,
  recipient_name  VARCHAR(500),
  recipient_phone VARCHAR(50),
  template_data   JSONB NOT NULL,     -- Дані для підстановки у шаблон
  document_id     UUID REFERENCES documents(id),
  signing_token   UUID UNIQUE DEFAULT gen_random_uuid(), -- Унікальний токен для ссилки
  status          VARCHAR(50) DEFAULT 'pending',
  -- pending, generating, sent, opened, signed, declined, expired
  sent_at         TIMESTAMPTZ,
  opened_at       TIMESTAMPTZ,
  signed_at       TIMESTAMPTZ,
  reminder_count  INT DEFAULT 0,
  expires_at      TIMESTAMPTZ,
  error_message   TEXT
);

Завантаження та валідація CSV

// Парсинг та валідація файлу отримувачів
async function processBatchUpload(file: Express.Multer.File, templateId: string) {
  const records = await parseCSV(file.buffer, { headers: true });
  const template = await db.documentTemplates.findByPk(templateId);
  const requiredFields = extractTemplateVariables(template.content);

  const errors: ValidationError[] = [];
  const validRows: RecipientRow[] = [];

  records.forEach((row, index) => {
    const rowErrors = [];

    if (!row.email || !isValidEmail(row.email)) {
      rowErrors.push(`Рядок ${index + 2}: некоректний email`);
    }

    for (const field of requiredFields) {
      if (!row[field]) {
        rowErrors.push(`Рядок ${index + 2}: відсутнє поле "${field}"`);
      }
    }

    if (rowErrors.length > 0) {
      errors.push(...rowErrors);
    } else {
      validRows.push(row);
    }
  });

  return { valid: validRows, errors, totalRows: records.length };
}

Обробка черги

// BullMQ worker: обробляє завдання підписання з черги
const signingWorker = new Worker('document-signing', async (job) => {
  const { requestId } = job.data;
  const request = await db.signingRequests.findByPk(requestId);

  try {
    await db.signingRequests.update(requestId, { status: 'generating' });

    // 1. Генеруємо персональний документ
    const pdfBytes = await documentGenerator.generate(
      request.template,
      request.templateData
    );

    // 2. Зберігаємо документ
    const document = await documentStorage.store(pdfBytes, {
      batchId: request.batchId,
      requestId: request.id,
    });

    // 3. Створюємо ссилку на підписання
    const signingUrl = `${process.env.APP_URL}/sign/${request.signingToken}`;

    // 4. Відправляємо повідомлення
    await emailService.send({
      to: request.recipientEmail,
      subject: 'Документ очікує вашої підписи',
      template: 'signing-invitation',
      data: {
        recipientName: request.recipientName,
        documentName: request.template.name,
        signingUrl,
        expiresAt: request.expiresAt,
      },
    });

    await db.signingRequests.update(requestId, {
      status: 'sent',
      documentId: document.id,
      sentAt: new Date(),
    });

    // Оновлюємо лічильник batch
    await db.signingBatches.increment(request.batchId, 'sent_count');
  } catch (error) {
    await db.signingRequests.update(requestId, {
      status: 'failed',
      errorMessage: error.message,
    });
    await db.signingBatches.increment(request.batchId, 'failed_count');
  }
}, {
  concurrency: 10, // Паралельна обробка
  connection: redisConnection,
});

Сторінка підписання (без авторизації)

Отримувач переходить по ссилці https://app.example.com/sign/{token}—авторизація не потрібна, доступ тільки по токену:

app.get('/sign/:token', async (req, res) => {
  const request = await db.signingRequests.findOne({
    signingToken: req.params.token,
    status: { not: ['expired', 'signed', 'declined'] },
  });

  if (!request) return res.redirect('/sign/invalid');
  if (request.expiresAt < new Date()) {
    await db.signingRequests.update(request.id, { status: 'expired' });
    return res.redirect('/sign/expired');
  }

  // Фіксуємо відкриття
  if (!request.openedAt) {
    await db.signingRequests.update(request.id, { openedAt: new Date() });
  }

  res.render('signing-page', { request, document: request.document });
});

Нагадування

// Cron: щодневна відправка нагадувань
async function sendSigningReminders() {
  const pending = await db.signingRequests.findAll({
    status: 'sent',
    reminderCount: { lt: 3 },
    sentAt: { lt: subDays(new Date(), 2) }, // Нагадування через 2 дня
    expiresAt: { gt: new Date() },
  });

  for (const request of pending) {
    const lastReminderAt = request.lastReminderAt || request.sentAt;
    const daysSinceLastReminder = differenceInDays(new Date(), lastReminderAt);

    if (daysSinceLastReminder >= 2) {
      await emailService.sendReminder(request);
      await db.signingRequests.update(request.id, {
        reminderCount: request.reminderCount + 1,
        lastReminderAt: new Date(),
      });
    }
  }
}

Дашборд мониторинга batch

Прогрес bar: відправлено/підписано/не відкрито/прострочено. Таблиця з фільтрами по статусу. Експорт у CSV отримувачів та статусів. Кнопка «Відправити нагадування» для всіх незакритих.

Обмеження швидкості рассилки

Email-провайдери мають ліміти. Для batch з 10K документів рассилка йде зі швидкістю 100–200 листів у хвилину через чергу з throttling. При використанні Resend—rate limit 100 req/s, Postmark—100 req/s.

Терміни

Завантаження CSV, валідація, створення batch та queue-based генерація PDF + відправка—7–10 днів. Сторінка підписання по токену, нагадування, мониторинг дашборд—5–7 днів.