Формы на сайте: пример для Next.js

Обновлено: Oct 12, 2026Раздел: Интеграции

Интегрируйте форму подписки в Next.js с BFF-маршрутом: браузер отправляет данные в ваше приложение, а ваш маршрут вызывает Mailoo с серверными учётными данными. Предпочтительнее использовать @mailoo/forms. Контракт API описан в website-forms{.interpreted-text role="doc"}.

Предварительные требования

  • Проект Mailoo с интеграцией Form (FORM)
  • API-ключ с webhook.form-submission (или FULL)

Окружение (только сервер)

@mailoo/forms по умолчанию использует префикс переменных окружения MAILOO_CONTACT_FORM_INTEGRATION для интеграций FORM. Название историческое: это не интеграция CONTACT_FORM (обратная связь). Обратная связь использует MAILOO_FEEDBACK_INTEGRATION --- см. feedback-form-nextjs-example{.interpreted-text role="doc"}.

Добавьте в .env.local (или в секреты хоста):

MAILOO_CONTACT_FORM_INTEGRATION_API=https://api.mailoo.app
MAILOO_CONTACT_FORM_INTEGRATION_API_KEY=your-api-key-here
MAILOO_CONTACT_FORM_INTEGRATION_PROJECT_UID=your-project-uid-here
MAILOO_CONTACT_FORM_INTEGRATION_ID=your-form-integration-id-here

Все четыре переменные обязательны для префикса по умолчанию. Не используйте NEXT_PUBLIC_* для URL API или ключа. Для второй интеграции FORM передайте пользовательский prefix (или getConfig) в фабрику маршрутов.

Обработчик BFF-маршрута

// app/api/v1/webhooks/forms/submit/route.ts
import { createFormSubmitHandler } from '@mailoo/forms/routes'

export const POST = createFormSubmitHandler()

Клиентская форма

Отправляйте данные в ваш BFF. Поля тела для FORM: email (обязательно), необязательные name, subject, content, source, metadata. Используйте content (а не message), если вы хотите задать произвольное тело входящего сообщения.

С хуком пакета:

'use client'

import { useState } from 'react'
import { useMailooFormSubmit } from '@mailoo/forms/hooks'

export function NewsletterForm() {
  const { submit, pending } = useMailooFormSubmit({
    endpoint: '/api/v1/webhooks/forms/submit',
  })
  const [statusMessage, setStatusMessage] = useState('')

  async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    setStatusMessage('')
    const fd = new FormData(e.currentTarget)
    const result = await submit({
      email: String(fd.get('email') || '').trim(),
      name: String(fd.get('name') || '').trim() || undefined,
      source: typeof window !== 'undefined' ? window.location.href : '',
    })
    if (!result.ok) {
      setStatusMessage(result.message)
      return
    }
    const data = result.data as { message?: string }
    setStatusMessage(
      typeof data.message === 'string' ? data.message : 'Subscribed'
    )
  }

  return (
    <form onSubmit={onSubmit}>
      <input name="name" placeholder="Your name (optional)" disabled={pending} />
      <input name="email" type="email" required disabled={pending} />
      <button type="submit" disabled={pending}>
        {pending ? 'Subscribing…' : 'Subscribe'}
      </button>
      {statusMessage ? <p>{statusMessage}</p> : null}
    </form>
  )
}

Или выполните fetch самостоятельно с тем же форматом JSON.

События жизненного цикла

Анонимная подписка на рассылку отличается от регистрации идентифицированного пользователя. После создания пользователя в вашем приложении отправьте событие с сервера:

import { ingestMailooLifecycleEvent } from '@mailoo/forms/server'
import { MAILOO_LIFECYCLE_EVENTS } from '@mailoo/forms/events'

await ingestMailooLifecycleEvent({
  eventName: MAILOO_LIFECYCLE_EVENTS.USER_REGISTERED,
  idempotencyKey: `reg:${user.id}`,
  externalUserId: user.id,
  email: user.email,
  marketingConsent: { granted: true, source: 'signup' },
})

Передавайте id пользователя приложения как externalUserId (не Mailoo contactId). Browser → BFF: useMailooLifecycleEvent({ getHeaders }) для host Authorization. Массовый статус админки: useMailooLifecycleStatus → ?externalUserIds=. Retry: POST .../events/{eventId}/retry через useMailooLifecycleRetry.

Префикс окружения по умолчанию: MAILOO_LIFECYCLE_*. Полный контракт: lifecycle-events{.interpreted-text role="doc"}.

Связанные страницы

  • website-forms{.interpreted-text role="doc"} --- эндпоинт и поведение подписчиков
  • lifecycle-events{.interpreted-text role="doc"} --- user.registered / user.activated
  • feedback-form-nextjs-example{.interpreted-text role="doc"} --- CONTACT_FORM / обратная связь
  • nextjs-packages{.interpreted-text role="doc"} --- обзор пакетов
📚