Pull to refresh

Брошенная корзина Mailchimp: гайд для ленивых

Reading time10 min
Views5.8K
Сначала немного разглагольствований :) Рано или поздно перед любым интернет-магазином встает вопрос настройки брошенной корзины. Статистика и сосущее под ложечкой ощущение упущенных денег не щадят никого.

Процент брошенных корзин с 2006 по 2017


Процент брошенных корзин с 2006 по 2017
Источник

Процент брошенных корзин на первый квартал 2018 года в разрезе индустрии:
Процент брошенных корзин на первый квартал 2018 года в разрезе индустрии
Источник

При этом, несмотря на общедоступную статистику, большинство интернет-магазинов не пользуются доступными возможностями и не подключают брошенную корзину. Недавнее «домашнее» исследование от EmailSoldiers наглядно показывает, что бОльшая часть магазинов вообще не заморачивается об этом.

Текущая статистика по подключенным брошенным корзинам


Исследование EmailSoldiers
Источник

При этом все (и мы тоже не святые) нагоняют трафик, докручивают рекламу и креативы, но даже не пытаются вернуть человека, который сорвался в последний момент.

А ведь и в первой итерации можно получить прирост заказов, используя письмо без динамического контента, нужно только настроить его. Всего один раз приложить усилия, чтобы в фоновом режиме это приносило деньги — это ли не сказка?

Естественно, письмо с динамическим контентом, в которое подтягиваются товары из корзины, может сработать лучше. А, может, на вашу аудиторию круче подействует очередной котенок с грустными глазами. Или вы подтянете рекомендуемые товары к товарам в корзине и увидите, что из писем их покупают чаще и повышают средний чек. Или у вас дойдут руки сделать серию писем, из которой конверсия будет еще больше.

Можно улучшать и тестировать письма брошенной корзины до недостижимого идеала, но любое единожды настроенное письмо уже несравнимо лучше, чем ничего.

Конверсия для брошенной корзины по данным RetailRocket


RetailRocket конверсия брошенной корзины
Источник

И вот мы с камрадом Артемом Александровым начали внедрение корзины с двух сторон.

Техническая реализация


ТЗ на интеграцию


Кратко описываем суть задачи.

Задача: подключить брошенную корзину для сайта ххх.хх с помощью рассылочного сервиса Mailchimp

Выдаем все необходимые материалы.

Ключ API: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-usXX

Где взять ключ?

Где взять ключ?

Даем ссылку на документацию

ID листа, к которому подключаем Store: XXXXXXXXXX

Где взять ID листа?

Где взять ID листа?

В сервисе рассылок заранее должно быть создано письмо. Как только API-запрос получен сервисом рассылок, происходит автоматическое формирование письма и добавление адресата в очередь для отправки.

Для нашего случая мы выбрали следующую логику отправки брошенной корзины:
авторизованный на сайте пользователь добавляет товары в корзину, не совершает транзакцию и не завершает заказ, корзина остается без изменений 1 час. После этого отправляется запрос в Mailchimp, в котором передается email, состав заказа пользователя, изображения товаров, цена товаров и ссылка на корзину пользователя.

Верстка шаблона


При создании автоматизации для брошенной корзины мейлчимп сразу спрашивает, одно ли будет письмо или несколько и предлагает выбрать подключенный магазин.

Создание автоматизации для брошенной корзины в Mailchimp

В базовых шаблонах мч предлагает на выбор три штуки:

Письма на выбор для брошенной корзины Mailchimp
  1. Брошенная корзина с динамическими товарами
  2. Брошенная корзина с продуктовыми рекомендациями (нужно настраивать отдельно)
  3. Брошенная корзина без товаров (просто текстовое письмо)

В лучших традициях, если у вас есть время, можно заверстать корзину самостоятельно.

Динамические товары без стилей выглядят примерно так (забудем про отступы, они некрасивенько здесь отображаются):

<table>
<tbody> 
*|ABANDONED_CART:[$total=3]|*
<table>
<tbody> 
<tr>
<td>
<a href="*|CART:URL|*" title="*|PRODUCT:TITLE|*" target="_blank">
<img src="*|PRODUCT:IMAGE_URL|*">
</a>
</td>
<td>
*|PRODUCT:TITLE|* — *|PRODUCT:PRICE|*
</td>
</tr>
</tbody>
</table>
*|END:ABANDONED_CART|*
</tbody>
</table>
*|END:ABANDONED_CART|*
</tbody>
</table>

Казалось бы, если изменить цифру в переменной *|ABANDONED_CART:[$total=3]|*, то в письме будет отображаться другое количество товаров, но нет, поставьте хоть 5, хоть 100, мч отказывается показывать другое количество.

И, что тоже немного странно, переменная *|PRODUCT:PRICE|* заменяется на значения формата RUB288, и поменять это тоже почему-то нельзя, но об этом позже.

Для разнообразия мы пытались подставить еще и переменные с количеством игр и с общей стоимостью заказа, которые передаем по api, но мейлчимп и тут сказал «нет». Что ж, да будет так.

Слово программисту :)


С мэйлчимпом был исторический опыт интеграции с их сторонним сервисом mandrill, там все просто и понятно. Очень дружелюбная документация (конечно же, на английском), но не возникло никаких шероховатостей и заработало с первого пинка.

Также у нас на сайте был внедрен механизм подписки через специальные формы. Вот там мы в полной мере ощутили недосказанность и двусмысленность документации мэйлчимпа. Английский не является проблемой для опытного разработчика, а вот знание клингонского диалекта не подразумевается по умолчанию.

Исходные данные такие: язык php7 и фрэймворк yii2, который сильно оброс уже своей экосистемой. Т.е. у нас уже 6 небольших проектов, которые стараются использовать общие компоненты как на бэкенде, так и на фронтенде. Соответственно, реализация любой задачи требует решать ее проектонезависимо, но это не подразумевает фрэймворконезависимость, т.к. за это приходится платить человекочасами, коих всегда дефицит.

Получив задачу на интеграцию, надо первым делом осмотреться. Что нам дано? Во-первых, сервис мэйлчимп, с которым надо подружиться. Идем на гитхаб и видим, что там достаточно много реализаций. Но выбор прост — у самого популярного пакета 1.5к звезд (drewm/mailchimp-api).

Пакет дает простую обертку над rest-взаимодействием с мэйлчимпом. Нам остается только обрастить это своей логикой.

Во-вторых, нам дана документация. Исходя из документации, у нас есть ресурс Store с вложенными ресурсами: Cart, Customer, Order, Product, Promo rule. Для брошенной корзины без рекомендованных товаров нам понадобятся только Product, Cart и Customer. Cart в свою очередь состоит из набора Cart line, а Product содержит Product variants.

Мы декомпозировали задачу следующим образом:

  1. Загрузить данные по магазину в ресурс Stores
  2. Загрузить все доступные к покупке товары в Products
  3. Настроить загрузку корзин с пользователями по расписанию

Ок, поехали. Первым делом беремся за сущность «магазин». Мы решили сразу использовать тестовый и боевой вариант магазина и, в зависимости от переменной окружения, отвечающей за дев/прод режим, мы работали либо с одним магазином, либо с другим.

Чтобы загрузить данные по магазину, мы стучимся post-запросом по адресу /ecommerce/stores со следующим набором параметров:

[
   'id' => 'dev.***.ru',
   'list_id' => '****',
   'name' => '*** - test',
   'domain' => 'dev.***.ru',
   'email_address' => 'admin@***.ru',
   'currency_code' => 'RUB',
   'primary_locale' => 'ru',
   'money_format' => '₽',
]

Параметров несколько больше, но все зависит от потребностей. Т.к. мы не собирались использовать контактные данные магазина в письмах, то не заполнили поля phone, address, timezone и т. п.
Но нас ожидал небольшой сюрприз. Поле money_format вроде специально создано для возможности представить цену в удобном нам формате. Но при построении шаблона брошенной корзины мэйлчимп упорно подставляет RUB перед числом. Мэйлчимп, перестань!

После загрузки мы можем проверить данные с помощью get-запроса по адресу /ecommerce/stores, чтобы увидеть все загруженные магазины, либо /ecommerce/stores/{id} для получения данных по конкретному магазину.

Теперь можно добавлять все остальные данные, т. к. Store является корневой точкой в нашем дереве данных, т.к. все остальные данные мы будем загружать в определенный магазин.

Так-с, чтобы МЧ мог подставлять товары в брошенную корзину, надо ему скормить эти товары. Для этого у нас есть адрес /ecommerce/stores/{store_id}/products, куда мы отправляем post-запросы на создание продуктов в системе.


[
   'id' => '742',
   'title' => 'Кастрюля',
   'handle' => 'kastrulya',
   'url' => 'http://***.ru/catalog/kastrulya/',
   'description' => 'Кастрюля — незаменимая вещь на кухне. Купив кастрюлю, вы измените    свою жизнь в лучшую сторону. Вы поймете, что невозможно прожить без этой вещи и дня! В каждый дом по кастрюле и пусть никто не уйдет обиженным!',
   'type' => 'Посуда',
   'vendor' => 'Рога и Копыта',
   'image_url' => 'http://***.ru/images/742/product.png',
   'variants' => [
       [
           'id' => '742',
           'title' => 'Кастрюля',
           'url' => 'http://***.ru/catalog/kastrulya/',
           'price' => 890,
           'sku' => 'KA453',
           'inventory_quantity' => 1000,
           'image_url' => 'http://***.ru/images/742/product.png',
           'visibility' => 'visible',
       ],
   ],
]

Что тут примечательного? Ну во-первых, каждый товар должен состоять хотя бы из одного товарного предложения. По сути Product — это некий контейнер для загрузки товарных предложений. Причем, id товара и товарного предложения могут пересекаться, т. к. это разные ресурсы в api МЧ.

И вот тут началась недосказанность документации МЧ. Приходилось догадываться, пускай это было несложно, но ведь можно было сделать нормально, а не как всегда.

Поле handle было описано как «the handle of a product». Ок, посовещавшись мы решили, что это часть урла, относящегося к самому продукту (чпу). Но это подтвердилось только в ходе тестов.

Также к товару можно прикрепить массив картинок, но мы решили, что нам не пригодится и поэтому грузили только главное изображение как к товару, так и к каждому товарному предложению.

И тут у нас возникла проблема, товары почему-то не отображались в темплейтах мейлчимпа.

Начали рыться в доке по Product. И нашли поле visibility с роскошным описанием:

описание поля visibility

Ну ок, тип String! А что туда можно передавать? Почему нельзя описать все возможные значения?! Я ведь могу туда отправить, например, «show me pls!».

Благо есть пример запроса!

пример запроса

Ну, это не отменяет проблему. Я ведь так и не знаю, какие еще могут быть значения, которые могут оказаться полезными.

Все, с этим справились! Теперь email-маркетолог может убедиться в наличии товаров в системе через построение шаблона с участием товаров или все через те же get-запросы с помощью консоли.

Дальше перед нами стоит задача загрузки брошенных корзин в МЧ. Изначально в голову пришло 2 варианта:

  1. При каждом изменении корзины (добавление/удаление товара), мы повторяем это действие в МЧ. Из минусов — сразу напрашивается огромное количество запросов к внешнему сервису.
  2. Раз в n минут смотреть корзины, которые не менялись более часа назад. После чего отправляем их в МЧ. Проблема только одна — следить за корзинами, которые были возобновлены после того, как отправились в МЧ.

Для начала делаем запрос в нашу базу данных (далее БД) за нашими данными в окне от 1 часа до 3. Почему 3? Через час после последнего изменения мы отправляем корзину в систему. В МЧ настроен минимально возможный интервал отправки корзины — 1 час. Поэтому в теории через 2 часа ± 5 минут произойдет отправка письма. Так что 3 часа — это величина даже с запасом.

Получив данные из БД, мы делаем get-запрос по адресу /ecommerce/stores/{store_id}/carts. Таким образом мы получаем все корзины, которые сидят в системе e-commerce и ждут своей очереди на отправку (либо уже отправлены). Для чего нам это нужно? Нужно для синхронизации с нашими данными. Мы отправим все корзины, полученные из БД, но нам нужно удалить те, которые уже не находятся в промежутке 1-3 часа. После 3х — уже неактуальные данные. До часа — корзины, которые могли опять возобновить, либо оформить заказ.

Для удаления нам надо просто найти разницу между двумя массивами/коллекциями корзин.
Получив корзины, которые необходимо удалить, мы отправляем delete-запрос /ecommerce/stores/{store_id}/carts/{cart_id}.

Дальше берем корзины для загрузки и циклом отправляем их post-запросами в систему.

Параметры корзины выглядят как-то так:


[
   'id' => '1207',
   'customer' =>
       [
           'id' => '25',
           'email_address' => 'email@example.com',
           'opt_in_status' => false,
       ],
   'currency_code' => 'RUB',
   'order_total' => 1597,
   'checkout_url' => 'http://***.ru/cart/abandoned/?cart=eyJpdGVtcyI6eyI1OTgwIjoxLCIzNDA0IjoxLCI3NzMiOjEsIjkwNTgiOjEsIjkwOTEiOjEsIjE4ODciOjEsIjc4NCI6MSwiNTExMSI6MSwiODA1MyI6MSwiMTk0MSI6MSwiNTQ0NSI6MSwiNzk1NCI6MywiOTA2NyI6NCwiOTA2NSI6NCwiNzg0MyI6MSwiOTA2NiI6M30sInByb21vY29kZSI6bnVsbH0%253D',
   'lines' => [
       0 => [
           'id' => '123',
           'product_id' => '5980',
           'product_variant_id' => '5980',
           'quantity' => 1,
           'price' => 841,
       ],
       1 => [
           'id' => '124',
           'product_id' => '3404',
           'product_variant_id' => '3404',
           'quantity' => 1,
           'price' => 756,
       ],
   ],
]

И опять наша любимая рубрика «догадайся, как работают эти поля». Например, методом научного тыка было выявлено, что можно не создавать покупателя отдельным запросом. Надо передать минимально требуемый набор полей, и он автоматически создастся, если его не было в системе. В нашем случае мы ограничились id, email, opt_in_status. Последний параметр отвечает за состояние подписки юзера в нашем листе. Если он true, то это означает состояние subscribed, в противном случае transactional.

Список товаров без проблем загружается через массив Cart Lines, который в свою очередь является ресурсом сущности Cart. Т.е. мы можем отдельно управлять этим набором с помощью rest-запросов.

Ну вот вроде бы и все? А вот и нет.

При тестировании мы обратили внимание, что отправив одну и ту же корзину, она отправляется лишь раз. Хотя мы ее удаляли из системы и загружали заново. Нигде ничего не сказано, ни единого слова! В итоге опытным путем, с помощью какой-то матери, мы приняли за основу гипотезу, что корзина с одним и тем же id может быть отправлена только один раз.

Тут я полностью согласен с создателями, что это ограничение уберегает нас от заспамливания пользователей. Но напишите об этом! Не нужны нам квесты, нам хватает своих мистических ошибок в коде, который мы пишем.

После того, как мы все это проделали, МЧ начал присылать нам красивые письма о брошенной корзине. И тут появился второй вопрос. Если юзер, бросил корзину и вернулся из письма в свой же аккаунт, т. е. он был авторизован в момент перехода по ссылке, то он попадет в свою корзину без проблем. А так получается, что письмо говорит тебе «на! возьми свою корзину назад!», а мы при переходе ему говорим «упс, чего-то потерялося все! Мы ничего не трогали! Оно само!»

Было решено кодировать состав корзины в строку и передавать в checkout_url при отправке корзины в МЧ. А при переходе на сайт ловить эту строку, декодировать и накидывать все товары в корзину, не забыв перед этим ее полностью обнулить.

Таким образом, в какой бы браузер мы не отправили юзера, он получает свою корзину, как мы и обещали. Единственный минус, что ему остается только авторизоваться. Но авторизовывать через ссылку — это моветон, да и вообще дело опасное, в первую очередь для наших клиентов.
Что в итоге? В принципе, проблем особых не было при реализации, так как очень часто выручали ответы МЧ при ошибках, связанных с валидацией переданных полей. Но их было бы еще меньше, если бы они нормально по-человечьи описали все эти тонкости работы МЧ и более подробно описали бы поля.

Настройка отчета в Google.Analytics


Чтобы отслеживать успех всей операции, придется настроить и периодически посматривать отчет в аналитике. Представим, что у нас у всех, как у взрослых, подключен ecommerce, иначе чуда не получится :)

Чтобы собрать новый отчет под брошенную корзину, идем в «Мои отчеты»:

Мои отчеты

Дальше «Добавить отчет»:

Добавить отчет

А после добавляем параметры, которые будем отслеживать. Мы решили, что будем смотреть на брошенную корзину в разрезе городов, у вас может быть другое видение.

У мейлчимпа стандартной кампанией для брошенной корзины является ABANDONED_CART_EMAIL, подставляем ее в фильтр и получаем отчет.

image

That’s all forks!

Теперь у вас настроена отправка брошенной корзины и отчет, по которому вы можете смотреть выхлоп с нее. И тестируйте, тестируйте, тестируйте! ;)
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 8: ↑7 and ↓1+6
Comments14

Articles