Skip to main content

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ę:

KafelekCo pokazuje
Łącznie SMS-ówwszystkie próby wysyłki, pod spodem liczba z ostatnich 24 godzin
Wysłanewiadomości przyjęte przez bramkę JustSend
Błędy wysyłkiwiadomości odrzucone lub takie, które nie dotarły do bramki
Części SMSszacowana 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łkaCo znika z zapisanej treści
kod weryfikacyjny (OTP)sam kod
hasło z resetu w paneluhasł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.

KolumnaZnaczenie
msisdn, sender, bulk_variantdokładnie to, co poszło do JustSend
contenttreść wiadomości; PIN-y i linki jawnie, OTP i hasło zamaskowane
statusSENT albo FAILED
contextźródło wysyłki, np. notifications, statute-acceptance/request
notification_typetyp powiadomienia, gdy wysyłka szła pipeline'em powiadomień
segmentsszacowana liczba części (GSM-7 160/153, UCS-2 70/67)
provider_status, provider_responsekod HTTP i pierwsze 500 znaków odpowiedzi bramki
error_messageopis 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.tsgetSmsLogsPage 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.