Logi SMS
👤 Instrukcja dla administratora
Panel → Panel administratora → Logi SMS (/dashboard/sms-logs) pokazuje każdą wiadomość SMS, którą aplikacja próbowała wysłać: powiadomienia o zajęciach, kody weryfikacyjne, linki do regulaminu, linki do dokumentów sprzedaży.
Cztery kafelki u góry podsumowują całą historię:
| Kafelek | Co pokazuje |
|---|---|
| Łącznie SMS-ów | wszystkie próby wysyłki, pod spodem liczba z ostatnich 24 godzin |
| Wysłane | wiadomości przyjęte przez bramkę JustSend |
| Błędy wysyłki | wiadomości odrzucone lub takie, które nie dotarły do bramki |
| Części SMS | szacowana liczba części — to po nich rozlicza się operator |
Tabelę filtruje się po statusie, źródle (np. notifications, phone-login), treści lub numerze telefonu oraz zakresie dat. „Rozwiń" przy wierszu pokazuje pełną treść wiadomości, odpowiedź bramki i — przy błędzie — jego opis.
Typowe zastosowania
- Klient twierdzi, że nie dostał powiadomienia. Wpisz jego numer w pole „Szukaj". Wiersz ze statusem Wysłany znaczy, że bramka przyjęła wiadomość — dalsze losy są po stronie operatora. Brak wiersza znaczy, że aplikacja nigdy nie próbowała wysłać: sprawdź preferencje powiadomień klienta i przełącznik kanału SMS w danym typie powiadomienia.
- Rachunek za SMS-y jest wyższy niż zwykle. Ustaw zakres dat i sprawdź „Części SMS": polskie znaki diakrytyczne dzielą wiadomość co 70 znaków zamiast co 160, więc jedna wiadomość potrafi kosztować jak trzy.
- Nagle nic nie wychodzi. Filtr statusu Błąd i kolumna „Odpowiedź bramki" pokażą powód wprost — najczęściej wyczerpane środki na koncie JustSend albo nieprawidłowy klucz
JUSTSEND_APP_KEY.
Kafelki nad tabelą liczą to samo, co pokazuje tabela: po zawężeniu filtrów „Nieudane" i procent błędów dotyczą wybranego zakresu, a nie całego logu.
Co widać, a co jest zamaskowane
Log pokazuje treść wiadomości dokładnie tak, jak dostał ją klient — z PIN-em do bramki i z pełnymi linkami (regulamin, dokończenie zapisu, dokument sprzedaży). O to właśnie chodzi przy telefonie od klienta: recepcja odczytuje z wiersza to samo, co on ma na ekranie.
Wyjątkiem są poświadczenia, które same w sobie otwierają czyjeś konto — zastępuje je ••••••:
| Wysyłka | Co znika z zapisanej treści |
|---|---|
| kod weryfikacyjny (OTP) | sam kod |
| hasło z resetu w panelu | hasło |
Wiersz nadal potwierdza, że SMS wyszedł, i pokazuje resztę treści.
Przyciski „Wyczyść stare (90+ dni)" i „Wyczyść wszystkie" kasują wiersze bezpowrotnie i każdy pyta o potwierdzenie własną treścią — „wszystkie" mówi wprost, że usuwa też dzisiejsze wiadomości. Log rośnie o jeden wiersz na wysłaną wiadomość, więc czyszczenie starszych niż 90 dni raz na jakiś czas w zupełności wystarcza; automatycznego kasowania nie ma.
Dostęp do widoku ma domyślnie wyłącznie rola ADMIN; można go rozszerzyć w Dostęp do ścieżek (/dashboard/route-access, wpis dashboardSmsLogs).
🛠️ Dokumentacja techniczna
Punkt przechwytu
Wszystkie wysyłki przechodzą przez sendSms w lib/sms.ts — to jedyne miejsce w kodzie, które woła API JustSend. Dlatego log powstaje właśnie tam i żadna ścieżka wysyłki nie może go ominąć.
recordSmsLog (lib/sms-log.ts) zapisuje wiersz w trzech momentach: po odpowiedzi 2xx (SENT), po odpowiedzi błędnej (FAILED, z kodem HTTP i treścią odpowiedzi) oraz gdy żądanie w ogóle nie dojdzie do bramki (FAILED, z komunikatem wyjątku). Każdy błąd samego zapisu jest połykany — audyt nigdy nie może być powodem, dla którego SMS nie wychodzi.
Tabela sms_log
Migracja 0259_create_sms_log.sql.
| Kolumna | Znaczenie |
|---|---|
msisdn, sender, bulk_variant | dokładnie to, co poszło do JustSend |
content | treść wiadomości; PIN-y i linki jawnie, OTP i hasło zamaskowane |
status | SENT albo FAILED |
context | źródło wysyłki, np. notifications, statute-acceptance/request |
notification_type | typ powiadomienia, gdy wysyłka szła pipeline'em powiadomień |
segments | szacowana liczba części (GSM-7 160/153, UCS-2 70/67) |
provider_status, provider_response | kod HTTP i pierwsze 500 znaków odpowiedzi bramki |
error_message | opis błędu przy FAILED |
Dwa indeksy, oba pokrywające (COVERING INDEX w planie zapytania):
(tenant_id, created_at, status, segments)— sortowanie listy oraz cały kafelkowy agregat bez sięgania do wierszy tabeli,(tenant_id, status, created_at)— lista zawężona filtrem statusu.
Wyszukiwanie po numerze i treści to LIKE '%…%', którego żaden indeks nie obsłuży; przy tej wielkości tabeli to skan po pokrywającym indeksie i nie ma potrzeby dokładać FTS.
created_at jest w UTC, a filtr dat przychodzi z kalendarza jako dzień polski, więc granice zakresu przelicza getDateFilterUTC z lib/date.ts. Bez tego wiadomość wysłana kwadrans po północy czasu warszawskiego trafiałaby do poprzedniego dnia. Wiersze i agregat czyta jeden db.batch z tym samym WHERE; totalCount z agregatu służy zarazem za licznik stronicowania.
Kontekst wysyłki
sendSms przyjmuje opcjonalny piąty argument SmsLogContext:
await sendSms(msisdn, message, 'AcePark', 'PRO', {
context: 'notifications',
notificationType,
db: database
});
db przekazują wywołania z cronów, które i tak mają już uchwyt do bazy; poza nimi używany jest getCloudflareContext().env.DB. contentForLog podmienia treść zapisywaną w logu.
Maskuje maskInContent(content, secret) z lib/sms-log.ts, któremu podaje się wartość, którą się już ma — nigdy wzorzec zgadywany z tekstu, bo przeredagowany szablon by go ominął. Woła się go w dokładnie dwóch miejscach: sendVerificationSms (kod OTP) i resetUserPassword w lib/actions/users.ts (nowe hasło).
Powiadomienia (sendSMSNotification w lib/notifications.ts) nie podają contentForLog w ogóle — treść z PIN-em do bramki czy linkiem trafia do logu tak, jak poszła do klienta.
Odczyt
lib/actions/sms-logs.ts — getSmsLogsPage i clearSmsLogs. Obie sprawdzają isRouteAllowed('/dashboard/sms-logs'), więc widok jest chroniony także wtedy, gdy ktoś wywoła akcję z pominięciem strony.
getSmsLogsPage wysyła jednym db.batch trzy zapytania — stronę wyników, COUNT(*) do paginacji i agregat do kafelków — czyli jeden round-trip do D1 na wejście na stronę. Paginacja to LIMIT/OFFSET po 50 wierszy, sortowanie created_at DESC, id DESC (drugi człon daje stabilną kolejność, gdy dwie wiadomości mają ten sam znacznik czasu).
Szacowanie liczby części
countSmsSegments sprawdza, czy cała treść mieści się w alfabecie GSM-7 (znaki rozszerzone liczą się podwójnie). Jeśli tak — 160 znaków dla jednej części, 153 dla wieloczęściowej. Jeśli nie — UCS-2, czyli 70 i 67. To szacunek: zakłada, że bramka nie transliteruje polskich znaków przy wysyłce.