Реалізація персоналізованих email-розсилок на сайті

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

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

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація персоналізованих email-розсилок на сайті
Середня
~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

Розробка персоналізованих email-кампаній

Персоналізація виходить за межі {{firstName}} — це різні блоки контенту за сегментами, рекомендації товарів на основі історії покупок, варіанти A/B для Subject Line, адаптація контенту за мовою та часовим поясом. Все це вимагає коду на серверній частині та даних з CRM/аналітики.

Рівні персоналізації

Рівень 1 — базові поля: Ім'я, компанія, дата останнього візиту. Просте підставлення через engine шаблонів.

Рівень 2 — сегментація: Різні блоки контенту для різних груп користувачів.

Рівень 3 — поведінкові дані: Рекомендації на основі історії покупок/переглядів, персональні знижки на покинуті товари.

Рівень 4 — прогностична персоналізація: ML-моделі для прогнозування оптимального часу відправлення та контенту.

Побудова персоналізованого листа

interface PersonalizationContext {
  user: User;
  segment: 'new' | 'active' | 'at_risk' | 'churned';
  recommendedProducts: Product[];
  lastViewedCategory: string;
  totalOrders: number;
  preferredLanguage: 'ru' | 'en';
  discount?: { code: string; percent: number; validUntil: Date };
}

async function buildPersonalizedEmail(
  userId: string,
  campaignId: string
): Promise<{ subject: string; html: string }> {
  // Збір контексту з різних джерел паралельно
  const [user, orders, recentViews, discount] = await Promise.all([
    db.users.findById(userId),
    db.orders.getRecentByUser(userId, 5),
    db.productViews.getRecentByUser(userId, 20),
    db.discounts.getPersonalDiscount(userId),
  ]);

  const segment = classifySegment(user, orders);
  const recommended = await recommendationEngine.getProducts(userId, recentViews);

  const ctx: PersonalizationContext = {
    user,
    segment,
    recommendedProducts: recommended.slice(0, 3),
    lastViewedCategory: recentViews[0]?.categoryName ?? '',
    totalOrders: orders.length,
    preferredLanguage: user.language ?? 'ru',
    discount: discount ?? undefined,
  };

  // Виберіть тему листа на основі сегмента
  const subjects: Record<PersonalizationContext['segment'], string> = {
    new: `${user.name}, ось що допоможе вам розпочати`,
    active: `${user.name}, спеціально для вас — новинки в "${ctx.lastViewedCategory}"`,
    at_risk: `Ми за вами сумуємо, ${user.name}! Спеціальна пропозиція всередині`,
    churned: `${user.name}, повертайтесь — ${discount?.percent ?? 20}% знижка чекає на вас`,
  };

  const html = render(<PersonalizedCampaign ctx={ctx} campaignId={campaignId} />);

  return { subject: subjects[segment], html };
}

Сегментація користувачів

function classifySegment(user: User, orders: Order[]): PersonalizationContext['segment'] {
  const daysSinceRegistration = daysBetween(user.createdAt, new Date());
  const daysSinceLastOrder = orders.length > 0
    ? daysBetween(orders[0].createdAt, new Date())
    : Infinity;

  if (daysSinceRegistration < 7) return 'new';
  if (daysSinceLastOrder < 30) return 'active';
  if (daysSinceLastOrder < 90) return 'at_risk';
  return 'churned';
}

React Email компонент з умовним контентом

function PersonalizedCampaign({ ctx, campaignId }) {
  const { user, segment, recommendedProducts, discount } = ctx;

  return (
    <Html>
      <Preview>
        {segment === 'churned'
          ? `${discount?.percent}% знижка — тільки для вас`
          : `Новинки спеціально для ${user.name}`}
      </Preview>
      <Body>
        {/* Hero залежить від сегмента */}
        {segment === 'at_risk' || segment === 'churned' ? (
          <ReEngagementHero discount={discount} userName={user.name} />
        ) : (
          <StandardHero userName={user.name} />
        )}

        {/* Персональні рекомендації */}
        {recommendedProducts.length > 0 && (
          <Section>
            <Heading>Рекомендуємо для вас</Heading>
            <Row>
              {recommendedProducts.map(product => (
                <Column key={product.id}>
                  <ProductCard
                    product={product}
                    utm={`utm_campaign=${campaignId}&utm_content=rec-${product.id}`}
                    discount={discount}
                  />
                </Column>
              ))}
            </Row>
          </Section>
        )}

        {/* Персональний промокод — тільки для at_risk та churned */}
        {discount && (segment === 'at_risk' || segment === 'churned') && (
          <Section style={{ background: '#fef3c7', padding: 24, borderRadius: 8 }}>
            <Text>Ваш персональний промокод:</Text>
            <Text style={{ fontSize: 28, fontWeight: 800, letterSpacing: 4 }}>
              {discount.code}
            </Text>
            <Text style={{ color: '#92400e' }}>
              {discount.percent}% знижка до {formatDate(discount.validUntil)}
            </Text>
          </Section>
        )}

        <Footer unsubscribeUrl={generateUnsubscribeUrl(user.id)} />
      </Body>
    </Html>
  );
}

Оптимальний час відправлення

// Аналізуємо історію відкриттів для визначення найкращого часу
async function getOptimalSendTime(userId: string): Promise<Date> {
  const openHistory = await db.emailOpenEvents.getByUser(userId, 90);  // 90 днів

  if (openHistory.length < 5) {
    // Недостатньо даних — використовувати за замовчуванням 10:00 за часовим поясом
    return getNextOccurrenceOfHour(10, userTimezone);
  }

  // Знайти годину з найбільшою кількістю відкриттів
  const hourCounts = openHistory.reduce((acc, event) => {
    const hour = new Date(event.openedAt).getHours();
    acc[hour] = (acc[hour] ?? 0) + 1;
    return acc;
  }, {} as Record<number, number>);

  const bestHour = Number(
    Object.entries(hourCounts).sort(([, a], [, b]) => b - a)[0][0]
  );

  return getNextOccurrenceOfHour(bestHour, userTimezone);
}

Тривалість

Персоналізована кампанія з сегментацією, рекомендаціями та умовним контентом займає 1 тиждень. З ML-моделлю оптимального часу відправлення — ще 3–5 днів.