Loops, czyli poczta produktowa bez dwóch systemów
Aplikacja z użytkownikami wysyła dwa rodzaje wiadomości. Transakcyjne: potwierdzenie rejestracji, reset hasła, faktura. I te wynikające z cyklu życia: powitanie po zapisie, przypomnienie przed końcem okresu próbnego, wiadomość do osoby, która przestała się logować.
Historycznie obsługiwały je dwa osobne narzędzia, bo pierwsze wymaga niezawodności i szybkości, a drugie edytora i segmentacji. Loops łączy oba w jednym miejscu i to jest cały jego pomysł.
Kontakty i zdarzenia
Model danych opiera się na kontaktach z dowolnymi właściwościami i na zdarzeniach, które wysyłasz z aplikacji.
import { LoopsClient } from 'loops'
const loops = new LoopsClient(process.env.LOOPS_API_KEY!)
await loops.createContact('anna@example.com', {
plan: 'trial',
firmaWielkosc: 12,
zrodlo: 'polecenie'
})
await loops.sendEvent({
email: 'anna@example.com',
eventName: 'ukonczyla_konfiguracje',
eventProperties: { krokow: 4 }
})Zdarzenie jest tu wyzwalaczem automatyzacji, a właściwości kontaktu służą do segmentacji i personalizacji treści. To znaczy, że logika decydująca o tym, kto dostaje jaką wiadomość, żyje w narzędziu, a nie w Twoim kodzie.
Ten podział jest zaletą i pułapką naraz. Zaletą, bo osoba odpowiedzialna za komunikację może zmienić warunek bez wdrożenia. Pułapką, bo zachowanie systemu zależy wtedy od konfiguracji, której nie ma w repozytorium i której zmiana nie przechodzi przez przegląd.
Rozsądny kompromis polega na trzymaniu w narzędziu decyzji o treści i czasie, a w kodzie decyzji o tym, czy zdarzenie w ogóle wysłać.
export async function poUkonczeniuKonfiguracji(uzytkownik: Uzytkownik) {
if (uzytkownik.plan === 'enterprise') return
if (!uzytkownik.zgodaMarketingowa) return
await loops.sendEvent({
email: uzytkownik.email,
eventName: 'ukonczyla_konfiguracje',
eventProperties: { krokow: uzytkownik.ukonczoneKroki }
})
}Dwa warunki na początku są tu tym, co należy do kodu. Sprawdzenie zgody marketingowej po stronie aplikacji, a nie w konfiguracji narzędzia, jest jedyną wersją, którą da się przetestować i wykazać przy kontroli. Wtedy warunki biznesowe są w kodzie z testami, a układ kampanii tam, gdzie da się go szybko poprawić.
Wiadomości transakcyjne
Wiadomość transakcyjną wysyłasz przez osobne wywołanie, wskazując szablon i zmienne.
await loops.sendTransactionalEmail({
transactionalId: 'reset-hasla',
email: uzytkownik.email,
dataVariables: {
imie: uzytkownik.imie,
link: `https://app.example.com/reset/${token}`
}
})Trzy rzeczy warto ustawić od początku. Pierwsza to obsługa błędu wysyłki: reset hasła, który nie doszedł, jest zgłoszeniem do wsparcia, więc nieudane wywołanie powinno trafić do kolejki ponowień, a nie zniknąć w logu.
Druga to rozdzielenie środowisk. Wysyłka z testów na prawdziwe adresy zdarza się każdemu raz, a wystarczy osobny klucz i osobne szablony, żeby to wykluczyć.
Trzecia to możliwość wypisania się. Wiadomości transakcyjne z definicji nie wymagają zgody marketingowej, ale granica bywa cienka, a wiadomość o nowej funkcji wysłana jako transakcyjna to problem prawny, nie techniczny.
Automatyzacje cyklu życia
To główny powód sięgania po to narzędzie, więc warto wiedzieć, co da się zbudować.
Typowy przepływ zaczyna się od zdarzenia: użytkownik się zarejestrował. Potem następuje sekwencja z odstępami: powitanie od razu, wskazówka po dniu, przypomnienie o niedokończonej konfiguracji po trzech dniach, ale tylko jeśli nadal jej nie ukończył.
Ten warunek jest sednem. Sekwencja bez sprawdzania stanu wysyła przypomnienie o czymś, co użytkownik już zrobił, i to jest najczęstszy powód, dla którego ludzie wypisują się z takich wiadomości.
Drugim typowym przepływem jest reakcja na brak aktywności. Użytkownik, który nie zalogował się od dwóch tygodni, dostaje wiadomość z pytaniem, co poszło nie tak. Skuteczność zależy tu wprost od tego, czy wiadomość brzmi jak pytanie, czy jak kampania.
Trzecim jest przypomnienie przed końcem okresu próbnego, gdzie treść zależy od tego, ile użytkownik faktycznie zrobił w aplikacji. Osoba, która przeszła całą konfigurację, potrzebuje innej wiadomości niż osoba, która zalogowała się raz. Pierwszej wystarczy przypomnienie o dacie, drugiej trzeba zaproponować pomoc, bo najprawdopodobniej utknęła na czymś, czego nie zgłosiła.
Synchronizacja z bazą aplikacji
Właściwości kontaktu w narzędziu poczty są kopią danych z Twojej bazy, a każda kopia z czasem się rozjeżdża. Warto ustalić, jak temu zapobiec, zanim rozjazd stanie się widoczny w treści wiadomości.
Trzy podejścia mają sens. Pierwsze to aktualizacja przy zmianie: kod zmieniający plan użytkownika wysyła też aktualizację kontaktu. Najprostsze, natomiast łatwo o pominięcie miejsca, w którym dane zmieniają się inaczej.
Drugie to synchronizacja cykliczna. Zadanie uruchamiane raz na dobę przechodzi po zmienionych rekordach i aktualizuje kontakty wsadowo. Wolniejsze w reakcji i odporniejsze na pominięcia, bo obejmuje wszystkie zmiany niezależnie od tego, skąd pochodzą.
Trzecie to zdarzenia jako jedyne źródło. Zamiast synchronizować właściwości, wysyłasz zdarzenia opisujące, co się stało, a segmentacja opiera się na nich. To najczystsze podejście i wymaga zaplanowania zestawu zdarzeń od początku.
W praktyce sprawdza się połączenie pierwszego z drugim: aktualizacja natychmiastowa przy zmianach ważnych i przebieg nocny jako zabezpieczenie. Przy zapleczu opartym o Drizzle albo inną warstwę dostępu do bazy taki przebieg to kilkanaście linii kodu.
const zmienieni = await db
.select()
.from(uzytkownicy)
.where(gt(uzytkownicy.zaktualizowano, wczoraj))
for (const u of zmienieni) {
await loops.updateContact(u.email, {
plan: u.plan,
firmaWielkosc: u.rozmiarFirmy,
ostatniaAktywnosc: u.ostatnieLogowanie.toISOString()
})
}Metoda aktualizująca tworzy kontakt, jeśli go nie ma, więc nie trzeba osobno sprawdzać istnienia. Warto natomiast pamiętać o limicie zapytań: pętla po kilkudziesięciu tysiącach rekordów bez opóźnienia zostanie odrzucona w połowie, a przebieg nocny nie ma powodu się spieszyć.
Warto też zdecydować, co robić z użytkownikiem usuniętym. Konto skasowane w aplikacji powinno zniknąć również z listy kontaktów, bo inaczej wiadomość trafi do osoby, która prosiła o usunięcie danych.
export async function usunKonto(uzytkownik: Uzytkownik) {
await db.delete(uzytkownicy).where(eq(uzytkownicy.id, uzytkownik.id))
try {
await loops.deleteContact({ email: uzytkownik.email })
} catch (blad) {
await kolejka.dodaj({ typ: 'usun-kontakt', email: uzytkownik.email })
}
}Obsługa błędu nie jest tu ozdobnikiem. Usunięcie z bazy powiodło się, a wywołanie do usługi zewnętrznej mogło się nie udać, i wtedy adres zostaje na liście bez żadnego śladu w Twoim systemie. Odłożenie ponowienia do kolejki zamyka tę lukę. To jeden z tych błędów, które kosztują więcej niż wynikałoby z ich prostoty.
Ceny
| Plan | Koszt | Co obejmuje |
|---|---|---|
| Free | 0 USD | Tysiąc kontaktów, cztery tysiące wiadomości miesięcznie |
| Płatny od | około 49 USD miesięcznie | Pięć tysięcy kontaktów, wysyłka bez limitu |
| Wyższy próg | około 99 USD miesięcznie | Dziesięć tysięcy kontaktów |
| Enterprise | wycena indywidualna | Większa skala i wymogi organizacyjne |
Rozliczenie idzie za kontakty, nie za wiadomości, i to jest najważniejsza rzecz przy porównywaniu z alternatywami. Przy wysyłce częstej ten model wychodzi tanio, bo liczba wiadomości nie wpływa na rachunek. Przy bazie stu tysięcy adresów, do których piszesz raz na kwartał, wychodzi drogo.
Prosty test przed decyzją: podziel spodziewany miesięczny koszt przez liczbę wysłanych wiadomości i porównaj z rozwiązaniem rozliczanym za wysyłkę. Wynik zwykle rozstrzyga sprawę szybciej niż zestawienie funkcji.
Warto też sprawdzić, jak liczone są kontakty. Adresy nieaktywne, wypisane i odbite mogą, ale nie muszą wliczać się do progu, a przy bazie z długim ogonem to różnica rzędu kilkudziesięciu procent.
Loops kontra alternatywy
| Narzędzie | Mocna strona | Słabość | Kiedy wybrać |
|---|---|---|---|
| Loops | Cykl życia i wiadomości transakcyjne razem, prosty model | Rozliczenie za kontakty przy rzadkiej wysyłce | Aplikacja z użytkownikami i sekwencjami wdrożeniowymi |
| Resend | Rozliczenie za wysyłkę, szablony w kodzie | Mniej narzędzi marketingowych | Wiadomości transakcyjne z aplikacji |
| Mailchimp | Dojrzałość, rozbudowane kampanie | Ciężki, słabo dopasowany do produktu | Newsletter i marketing poza aplikacją |
| Własna wysyłka | Pełna kontrola, niski koszt jednostkowy | Dostarczalność i utrzymanie po Twojej stronie | Duży wolumen z własnym zapleczem |
Wybór między pierwszymi dwoma wierszami zależy od tego, czy potrzebujesz warstwy marketingowej. Jeśli wysyłasz wyłącznie potwierdzenia i resety haseł, rozliczenie za wysyłkę jest tańsze i prostsze. Jeśli budujesz sekwencje zależne od zachowania użytkownika, komplet w jednym miejscu oszczędza łączenia dwóch systemów.
Ostatni wiersz warto rozważyć wyłącznie przy dużym wolumenie i z pełną świadomością, że dostarczalność jest problemem samym w sobie. Konfiguracja uwierzytelniania domeny, reputacja adresu wysyłkowego i obsługa odbić to praca, której nie widać do momentu, w którym wiadomości zaczynają trafiać do niechcianych.
Warto też zauważyć, że wysyłka poczty rzadko jest jedynym kanałem. Powiadomienia w aplikacji, wiadomości w komunikatorze i przypomnienia w interfejsie obsługują część przypadków lepiej, bo docierają natychmiast i nie konkurują z pięćdziesięcioma innymi wiadomościami w skrzynce. Przy budowaniu wdrożenia użytkownika warto rozdzielić, co idzie pocztą, a co ma pojawić się w samej aplikacji, choćby przez proste powiadomienie zbudowane własnymi komponentami na Tailwindzie.
Przy automatyzacjach obejmujących inne systemy, na przykład zapis do arkusza albo powiadomienie zespołu, sensowne bywa połączenie tego narzędzia z Make albo podobnym rozwiązaniem, zamiast budowania wszystkiego w jednym miejscu.
Dostarczalność, czyli czy wiadomość dotrze
Najlepiej napisana sekwencja nic nie da, jeśli wiadomości trafiają do niechcianych. Ta część leży częściowo po stronie usługi, a częściowo po Twojej.
Po Twojej stronie jest uwierzytelnienie domeny. Trzy wpisy w konfiguracji nazw potwierdzają, że usługa ma prawo wysyłać w Twoim imieniu, a ich brak jest najczęstszą przyczyną problemów przy pierwszym wdrożeniu. To zadanie na kwadrans, wykonywane raz.
Drugą rzeczą jest oddzielenie ruchu. Wiadomości transakcyjne i marketingowe wysyłane z tej samej poddomeny dzielą reputację, więc jedna nieudana kampania potrafi wpłynąć na dostarczanie resetów haseł. Osobne poddomeny rozdzielają to ryzyko.
Trzecią jest higiena listy. Adresy odbijające się i nieotwierane od roku obniżają reputację, a przy rozliczeniu za kontakty kosztują też pieniądze. Usuwanie ich raz na kwartał jest tanie i poprawia obie rzeczy naraz.
Czwartą jest treść. Wiadomość z samym obrazkiem, z linkiem skróconym przez zewnętrzną usługę albo z nadmiarem wykrzykników wygląda podejrzanie dla filtrów niezależnie od tego, kto ją wysłał. To brzmi banalnie, a bywa przyczyną, dla której jedna wiadomość z serii nie dociera.
Testowanie i podgląd
Wiadomość wysłana do tysiąca osób z błędem w warunku jest nie do cofnięcia, więc kilka nawyków oszczędza kłopotu.
Pierwszy to wysyłka testowa do siebie przed uruchomieniem sekwencji. Podgląd w edytorze pokazuje układ, a wiadomość w prawdziwej skrzynce pokazuje, jak wygląda w kliencie pocztowym, co bywa czymś innym.
Drugi to sprawdzenie personalizacji na kontakcie bez wypełnionych pól. Powitanie w rodzaju „cześć" bez imienia wygląda źle, a zdarza się przy każdym imporcie danych z niepełnymi rekordami. Wartość zapasowa rozwiązuje to raz.
Trzeci to sprawdzenie odnośników. Odnośnik wskazujący środowisko testowe albo zawierający zmienną, która nie została podstawiona, to najczęstszy błąd w wiadomościach transakcyjnych.
Czwarty to przejrzenie sekwencji od końca. Warunki wyjścia są ważniejsze od warunków wejścia, bo to one decydują, czy użytkownik przestanie dostawać wiadomości po wykonaniu oczekiwanej akcji. Sekwencja bez warunku wyjścia pisze do osoby, która już kupiła.
Typowe błędy
Pierwszy to sekwencja bez sprawdzania stanu. Przypomnienie o kroku, który użytkownik już wykonał, jest najczęstszym powodem wypisania się z komunikacji.
Drugi to wysyłka z testów na prawdziwe adresy. Osobny klucz dla środowiska testowego kosztuje minutę i chroni przed wiadomością wysłaną do klienta przez pomyłkę.
Trzeci to wiadomości marketingowe wysyłane jako transakcyjne. Granica jest cieńsza, niż się wydaje, a konsekwencje leżą po stronie prawnej, nie technicznej.
Czwarty to brak obsługi błędu przy wiadomościach ważnych. Reset hasła, który nie doszedł, powinien trafić do ponowienia, a nie zniknąć w logu, gdzie zauważy go dopiero zgłoszenie od użytkownika.
Piąty to szacowanie kosztu bez sprawdzenia, jak liczone są kontakty. Adresy nieaktywne i wypisane potrafią zmienić rachunek o kilkadziesiąt procent.
Szósty to trzymanie całej logiki decyzyjnej w narzędziu. Warunki biznesowe bez testów i bez historii zmian to miejsce, w którym błąd wychodzi dopiero po wysłaniu wiadomości do wszystkich.
FAQ
Ile kosztuje Loops?
Plan darmowy obejmuje tysiąc kontaktów i cztery tysiące wiadomości miesięcznie. Plany płatne zaczynają się od około czterdziestu dziewięciu dolarów za pięć tysięcy kontaktów, a rozliczenie idzie za liczbę kontaktów, nie za wysłane wiadomości.
Czym różni się od Resend?
Rozliczeniem i zakresem. Tutaj płacisz za kontakty i dostajesz warstwę marketingową z automatyzacjami, tam płacisz za wysłane wiadomości i dostajesz przede wszystkim niezawodną wysyłkę transakcyjną. Przy samych potwierdzeniach i resetach tańsze jest to drugie.
Czy nadaje się do wiadomości transakcyjnych?
Tak, obsługuje je razem z resztą, co usuwa potrzebę utrzymywania dwóch systemów. Przy wiadomościach krytycznych warto natomiast zadbać o obsługę błędów po Twojej stronie, bo nieudane wywołanie interfejsu jest zdarzeniem, na które trzeba zareagować.
Kiedy rozliczenie za kontakty się nie opłaca?
Przy dużej bazie, do której piszesz rzadko. Sto tysięcy adresów przy jednej wiadomości na kwartał kosztuje tyle samo co sto tysięcy adresów przy wysyłce codziennej, więc przy rzadkiej komunikacji model oparty o wysyłkę wypada taniej.
Co z dostarczalnością?
Usługa zajmuje się reputacją adresów wysyłkowych i obsługą odbić, ale konfiguracja uwierzytelniania Twojej domeny należy do Ciebie. To pierwsza rzecz do zrobienia przy wdrożeniu, bo bez niej wiadomości trafiają do niechcianych niezależnie od jakości treści.
Dokumentacja stoi na stronie projektu, a porównania z innymi narzędziami znajdziesz w zestawieniu dostawców poczty.