Powiadomienia systemowe (Automatyczne)
Zestawienie wszystkich sytuacji, w których system samodzielnie (bez Twojej ingerencji) komunikuje się z klientem za pomocą e-maili i SMS-ów.
👤 Instrukcja dla pracownika (Recepcja / Administracja)
System AcePark realizuje proces zautomatyzowanego monitorowania terminów oraz statusów kont klienckich. Optymalizuje to pracę administracyjną, redukując konieczność ręcznego weryfikowania i powiadamiania uczestników. Poniżej przedstawiono wykaz scenariuszy, w których komunikacja wychodząca (e-mail, SMS) generowana jest całkowicie przez system.
Znajomość poniższych mechanizmów stanowi wsparcie podczas udzielania odpowiedzi na standardowe pytania klientów (np. dotyczące braku potwierdzeń czy trybu postępowania przy zaległościach finansowych).
Powiadomienia Transakcyjne (Generowane w czasie rzeczywistym)
Wiadomości wysyłane są bezzwłocznie w odpowiedzi na określoną akcję w systemie:
- Rejestracja na zajęcia cykliczne (grupowe)
- Wyzwalacz: Przypisanie klienta do grupy przez pracownika lub w wyniku samodzielnej rejestracji klienta.
- Zawartość komunikatu: E-mail powitalny zawierający szczegóły organizacyjne (termin, kort, trener), instrukcje przygotowawcze oraz regulamin (m.in. wymóg regulowania opłat do 7. dnia danego miesiąca).
- Anulowanie zajęć przez klienta
- Wyzwalacz: Usunięcie obecności z poziomu aplikacji klienckiej.
- Zawartość komunikatu: E-mail potwierdzający rezygnację z określonego terminu oraz instrukcja procedury wykorzystania zajęć "Do odrobienia".
- Zapis na termin odrabiania
- Wyzwalacz: Rejestracja uczestnika posiadającego niewykorzystane zajęcia w nowej, otwartej grupie.
- Zawartość komunikatu: E-mail z formalnym potwierdzeniem jednorazowego terminu odrabiania zajęć.
Powiadomienia Harmonogramowe (Generowane cyklicznie)
Wiadomości dystrybuowane na podstawie algorytmów weryfikujących warunki o ściśle określonych porach doby:
- Powiadomienie przed zajęciami próbnymi
- Wyzwalacz: Codziennie rano (7:00), weryfikacja klientów zarejestrowanych na zajęcia próbne w dniu następnym.
- Zawartość komunikatu: Standardowy komunikat e-mail pełniący funkcję informacyjno-przypominającą (zalecenia dotyczące stroju sportowego, obuwiu oraz przybyciu przed czasem).
- Powiadomienie przed wynajmem kortu
- Wyzwalacz: Zależnie od konfiguracji lokalnej, np. 24h przed planowaną rezerwacją.
- Zawartość komunikatu: Przypomnienie e-mail/SMS kierowane do osoby dokonującej rezerwacji.
- Upomnienie o braku płatności (1 dzień po terminie)
- Wyzwalacz: 8. dzień miesiąca w godzinach porannych (przy założeniu wymagalności płatności do 7. dnia).
- Zawartość komunikatu: Oficjalny e-mail informujący o przekroczeniu terminu płatności z instrukcją opłacenia należności przez portal oraz informacją o potencjalnej utracie możliwości odrabiania zajęć.
- Ostateczne wezwanie do zapłaty (7 dni po terminie)
- Wyzwalacz: 14. dzień miesiąca w godzinach porannych.
- Zawartość komunikatu: Ostateczne wezwanie w formie wiadomości e-mail oraz powiadomienia SMS na zarejestrowany numer telefonu. Komunikat zawiera informację o natychmiastowym wstrzymaniu możliwości udziału w zajęciach do momentu uregulowania należności.
- Wezwanie do wystawienia opinii (Camp Feedback)
- Wyzwalacz: Ustaloną liczbę dni po zakończeniu turnusu (domyślnie: 1 dzień).
- Zawartość komunikatu: Wiadomość e-mail z anonimowym linkiem do ankiety oceniającej półkolonie (w skali 1-5). Istnieje możliwość wygenerowania tego wezwania ręcznie z poziomu karty turnusu w przypadku awarii wysyłki automatycznej.
🛠️ Dokumentacja techniczna
Szczegóły funkcjonowania kolejek powiadomień i harmonogramów. Przeznaczone do testów i debugowania (QA / Devs).
Zaplanowane zadania (Crons)
Powiadomienia cykliczne opierają się na Cloudflare Workers Cron Triggers. Aby je przetestować lokalnie:
- Otwórz środowisko workera:
yarn dev:worker - Wywołuj konkretne trigger endpointy z CLI.
Wywołania Curl:
- Przypomnienie o testach na jutro (oraz prośby o opinię po campie) – wywoływane w cronie dziennym o 7:00:
curl "http://localhost:3000/__scheduled?cron=0%207%20*%20*%20*" - Miękkie przypomnienie o płatności – 8 dzień miesiąca:
curl "http://localhost:3000/__scheduled?cron=0%207%208%20*%20*" - Pilne przypomnienie – 14 dzień miesiąca (w tym przypadku odpalany jest Provider SMS, warunek: posiadanie przez ownera telefonu w Auth0
user_metadata.phone_number):curl "http://localhost:3000/__scheduled?cron=0%207%2014%20*%20*"
| Element | Wymagana treść |
|---|---|
| Nagłówek | "Potwierdzamy zapisanie na zajęcia!" |
| Szczegóły | Dane uczestnika, rodzaj zajęć, nazwa serii (sekcja „Dzień”), data startowa serii (sekcja „Godzina”), trener i miejsce |
| Regulamin | Zasada 24h odwołania, płatność do 7. dnia miesiąca |
| Informacje | Zmiana grupy, dni wolne, procedura rezygnacji |
Kroki testowe:
- Jako admin, utwórz serię zajęć cyklicznych
- Dodaj uczestnika (nie-admin) do całej serii
- Powiadomienie zostanie wysłane automatycznie
🚫 Odwołanie zajęć
Wyzwalacz: Odwołanie terminu przez uczestnika w Portalu Klienta
| Element | Wymagana treść |
|---|---|
| Nagłówek | "Właśnie odwołałeś zajęcia!" |
| Szczegóły | Dane odwołanych zajęć (uczestnik, rodzaj, dzień, godzina, trener, kort) |
| Odrabianie | Informacja o zakładce "Odwołane zajęcia" |
| Płatność | Wymagania dotyczące opłat za zajęcia odrabiające |
Kroki testowe:
- Utwórz zajęcia z zarejestrowanym uczestnikiem
- Zaloguj się jako uczestnik i odwołaj termin z poziomu Portalu Klienta
- Powiadomienie zostanie wysłane automatycznie
Ręczne usunięcie uczestnika w panelu admina wysyła alternatywny szablon „Zmiana w grze” (player_removed_from_game).
🔄 Zapis na odrabianie
Wyzwalacz: Dodanie uczestnika do zajęć odrabiających
| Element | Wymagana treść |
|---|---|
| Nagłówek | "Właśnie zapisałeś się na odrabianie zajęć!" |
| Szczegóły | Dane zajęć odrabiających |
| Rezygnacja | Procedura ponownego odwołania (najpóźniej 24h przed) |
| Status | Wymagania dotyczące statusu płatności |
Kroki testowe:
- Jako admin, utwórz zajęcia odrabiające
- Dodaj uczestnika który wcześniej odwołał zajęcia
- Powiadomienie zostanie wysłane automatycznie
⏰ Powiadomienia zaplanowane
Powiadomienia wysyłane w określonych terminach przez zadania cron.
🔔 Przypomnienie o zajęciach próbnych (24h)
Harmonogram: Codziennie o 7:00 (0 7 * * *)
curl "http://localhost:3000/__scheduled?cron=0%207%20*%20*%20*"
| Element | Wymagana treść |
|---|---|
| Nagłówek | "Widzimy się już jutro!", "Przypomnienie o Twoich zajęciach tenisowych" |
| Szczegóły | Dane uczestnika, rodzaj zajęć, data/godzina, trener, kort |
| Przygotowanie | Sekcja "Spakuj sprzęt", "Przyjdź wcześniej" (5-10 minut) |
| Kontakt | Numery: Opole (570 386 869), Legionowo (516 793 180), Lublin (730 706 030) |
Kroki testowe:
- Jako admin, utwórz zajęcia na jutro z dowolnym
activity_type - Dodaj uczestnika z
type: 'skill-assessment'w JSON - Uruchom zaplanowane zadanie powyższym poleceniem curl
📅 Przypomnienia o rezerwacjach kortu
Harmonogram: Co godzinę (0 * * * *). Progi w RESERVATION_REMINDER_CONFIG.hoursBeforeReservation (np. 24h, 12h, 2h przed startem).
Test lokalny:
- Uruchom aplikację przez workera (inaczej cron i D1 nie działają):
yarn dev:worker
- Wywołaj symulację crona (hourly = przypomnienia o rezerwacjach):
curl "http://localhost:3000/__scheduled?cron=0%20*%20*%20*%20*"
Żeby przypomnienie faktycznie się wysłało: w bazie musi być rezerwacja (tabela booking + game), której start_time mieści się w oknie dla danego progu. Dla domyślnego [24] i windowMinutes: 30 okno to 23,5h–24,5h od bieżącej chwili. Łatwiejszy test: tymczasowo ustaw w lib/notification-config.ts np. hoursBeforeReservation: [0.5] (30 min) i utwórz rezerwację zaczynającą się za ok. 30 minut; po wywołaniu curl przypomnienie powinno pójść.
SMS: Aby dostać SMS, użytkownik (owner rezerwacji) musi mieć w Auth0 w user_metadata.phone_number ustawiony numer. W ustawieniach powiadomień muszą być włączone kanały SMS oraz typ „Przypomnienia o rezerwacjach”. W pliku .env (lub u workerze) musi być NOTIFICATIONS_ENABLED=true.
💰 Przypomnienie o płatności (dzień po terminie)
Harmonogram: 8. dnia miesiąca o 9:00 CEST (0 7 8 * *)
curl "http://localhost:3000/__scheduled?cron=0%207%208%20*%20*"
| Element | Wymagana treść |
|---|---|
| Nagłówek | "Termin płatności minął" |
| Informacja | "wczoraj minął termin płatności za zajęcia za bieżący miesiąc" |
| Portal | Odniesienie do "Portal Klienta" |
| Pomoc | "Nie możesz dokonać płatności?" - "odwiedź nas w Opolu" |
| Benefity | Informacja o zaletach terminowych płatności (zajęcia odrabiające) |
Kroki testowe:
- Utwórz rekord płatności z
due_dateustawionym na wczoraj - Ustaw
status: 'pending' - Uruchom zaplanowane zadanie powyższym poleceniem curl
⚠️ Pilne przypomnienie o płatności (7 dni po terminie)
Harmonogram: 14. dnia miesiąca o 9:00 CEST (0 7 14 * *)
curl "http://localhost:3000/__scheduled?cron=0%207%2014%20*%20*"
:::warning Podwójne powiadomienie Ten scenariusz wysyła jednocześnie email i SMS z tego samego zadania cron. :::
📧 Email
| Element | Wymagana treść |
|---|---|
| Nagłówek | "Pilne przypomnienie o zaległej płatności za zajęcia" |
| Termin | "Termin płatności minął 7 dni temu", "Minął już tydzień od terminu" |
| Konsekwencje | Lista: wstrzymanie zajęć, utrata benefitów |
| Działanie | "skontaktuj się z nami jeszcze dziś", odniesienie do Portalu Klienta |
📱 SMS
Pilne przypomnienie! Minął tydzień od terminu płatności za zajęcia. Prosimy o natychmiastowe uregulowanie należności. Centrum Tenisowe AcePark.
Kroki testowe:
- Utwórz rekord płatności z
due_dateustawionym na 7 dni temu - Ustaw
status: 'pending' - Uruchom zaplanowane zadanie powyższym poleceniem curl
⭐ Prośba o opinię po półkolonii
Harmonogram: Codziennie o 7:00 (0 7 * * *). Wysyłka daysAfter dni po zakończeniu turnusu (domyślnie 1; konfigurowalne w panelu Powiadomienia → zadanie camp_feedback_request, parametr params.daysAfter).
curl "http://localhost:3000/__scheduled?cron=0%207%20*%20*%20*"
Do każdego opłaconego opiekuna (deduplikacja po e-mailu) trafia e-mail z linkiem …/feedback/{token} do wystawienia oceny 1–5 + komentarza. Szczegóły działania, strona publiczna i widok w panelu: zob. Półkolonie → Opinie po zakończonej półkolonii.
Żeby prośba faktycznie się wysłała: w bazie musi istnieć turnus z end_date = dziś − daysAfter oraz rejestracja status = 'paid'. Można też wywołać ręcznie przyciskiem „Wyślij prośbę o opinię" na stronie turnusu.
🔐 Kod PIN do drzwi po opłaconej rezerwacji
Dla pracownika: Po zaksięgowaniu płatności za rezerwację kortu kod PIN do drzwi pokazuje się na ekranie potwierdzenia — zarówno w Portalu Klienta, jak i na publicznej stronie płatności (rezerwacja z kalendarza bez logowania). Ten sam kod klient dostaje w SMS-ie/mailu potwierdzającym płatność (booking_payment_success); osobna wiadomość z samym PIN-em nie jest wysyłana. Jeśli klient dzwoni, że „nie ma PIN-u", sprawdź, czy rezerwacja ma przypisany PIN i czy włączona jest flaga dostarczania PIN-ów.
Technicznie:
- Cała funkcja jest bramkowana flagą
DOOR_PIN_DELIVERY_ENABLED=true(lib/feature-flags.ts). - Ekrany sukcesu:
app/pay/success/page.tsx(publiczna, PIN zpayment_link→booking.door_pin) iapp/(dashboard)/dashboard/payments/success/page.tsx(Portal Klienta, PIN dociągany przezgetDoorPinsByGameIdsdla płatności typucourt_reservation). Oba renderują wspólny komponentcomponents/payments/DoorPinBanner.tsx, który sam ukrywa się przy wyłączonej fladze. - W treści SMS-ów PIN siedzi w warunkowym fragmencie
{{#if doorPin}}…{{/if}}— jeśli rezerwacja nie ma PIN-u albo flaga jest wyłączona, fragment nie renderuje się wcale. Dotyczy typów:booking_payment_success,player_added_to_reservation,existing_player_added_to_reservation,reservation_reminder. - Treść z bazy wygrywa nad plikami
messages/*.json.resolveNotificationConfigużywa JSON-a tylko jako fallbacku, gdy wnotification_textsnie ma wiersza — a zasiew (seedNotificationConfig) dla istniejących typów jestINSERT OR IGNORE, więc zmiana treści w JSON-ie nie dociera do wdrożonych tenantów. Każda zmiana istniejącego szablonu wymaga migracji aktualizującejnotification_texts(albo ręcznej edycji w panelu). Fragment z PIN-em dopisany do JSON-a w lipcu 2026 nie miał takiej migracji i przez to nie wychodził w SMS-ach — nadrabia to0198_backfill_door_pin_in_reservation_sms.sql. - E-maile transakcyjne renderuje SendGrid ze swojego dynamic template (dla potwierdzenia płatności:
d-67bd1a6fea1343db9d7413856aee763e); kod przekazuje tylkodynamic_template_data, w tymdoorPin. Kopia HTML wlib/mail-templates.generated.tssłuży wyłącznie podglądowi w panelu — jeśli PIN ma zniknąć/pojawić się w mailu, zmiana idzie po stronie szablonu w SendGridzie.
🧪 Wytyczne testowe
📋 Lista kontrolna
- Zaplanowane powiadomienia: Używaj poleceń curl do wyzwalania zadań cron lokalnie
- Automatyczne powiadomienia: Sprawdź natychmiastowe wysyłanie po wystąpieniu zdarzenia
- Weryfikacja kontaktów: Powiadomienia o zajęciach próbnych, płatnościach i odrabianiu zawierają numery dla Opola, Legionowa i Lublina; e-maile „Zmiana w grze” i „Gra jutro!” udostępniają jedynie numer ogólny (570 386 869)
- Kontrola designu: Szablony email z niebieskim gradientem i brandingiem AcePark
- Limit znaków SMS: Dokładne dopasowanie do podanego tekstu
- Podwójne dostarczanie: Scenariusz pilnego przypomnienia wysyła email + SMS jednocześnie
📞 Wymagane kontakty
| Lokalizacja | Numer telefonu |
|---|---|
| Opole | 570 386 869 |
| Legionowo | 516 793 180 |
| Lublin | 730 706 030 |
| kontakt@acepark.pl |
:::tip Zawartość w języku polskim Cała zawartość musi dokładnie odpowiadać polskiemu tekstowi podanemu w wymaganiach systemowych. :::
Ważne aspekty techniczne:
- Konfiguracja progu przypomnień rezerwacji: Określana w
RESERVATION_REMINDER_CONFIG.hoursBeforeReservation(pliklib/notification-config.ts). - Gated Crons (
runGatedCron): Poszczególne daily zadania są bramkowane, aby zapobiec duplikacji w przypadku opóźnień lub redundancji Cloudflare. - SMS Integration: Jeśli flaga
NOTIFICATIONS_ENABLED=truejest obecna, bramki SMS (np. SMSAPI) pobierają treść i adresatów. Należy ostrożnie wywoływać ręczne crony z prod-db, by uniknąć przypadkowego zaspamowania bazy SMS. Wszystkie numery testowe i podglądy są dostępne w zakładce Dashboard -> Powiadomienia w adminie. - Brak kontekstu Cloudflare w cronach:
getCloudflareContext()działa tylko w obsłudzefetch(kontekst ustawia wrapper OpenNext). Handlerscheduled(worker-crons.ts) go nie ma, więc każda funkcja wołana z crona musi przyjmowaćdb(env.DB) parametrem i przekazywać go dalej — dotyczy to m.in.getUserPhoneNumber,getUserCity,listUsersFromDb. Pominięcie parametru kończy się błędemgetCloudflareContext has been called without having called initOpenNextCloudflareForDevw logach workera admina.