Strona nie działa? Zadzwoń.

Tworzenie Stron Rzeszów to marka firmy Prownex IT Services Sp. z o.o. (NIP 8161717256). Tworzymy strony internetowe i sklepy dla małych i średnich firm z Rzeszowa i okolic, utrzymujemy je na własnym hostingu prowde All Inclusive oraz naprawiamy, zabezpieczamy i przenosimy istniejące strony WordPress.

Zapytanie z formularza w CRM w niecałą sekundę, bez przepisywania

Każde zapytanie ze strony biuro przepisywało ręcznie z maila do CRM i do arkusza: w naszym modelu 85–111 sekund na jedno zgłoszenie. Zbudowaliśmy prostą wtyczkę, która robi to sama w 0,6–0,7 sekundy, a gdy CRM nie działa, czeka i ponawia, niczego nie gubiąc.

Ilustracja: formularz ze strzałką do bazy danych, z której strzałki prowadzą do kartoteki i koperty, oraz strzałka ponowienia

Opublikowano: 03.11.2026 · stan na listopad 2026 · Redakcja Tworzenie Stron Rzeszów

W skrócie

Strona
firma usługowa, zapytania ofertowe przez formularz kontaktowy
Problem
każde zapytanie trzeba było przepisać z maila do CRM i do arkusza; zapytania czekały, aż ktoś znajdzie czas
Rozwiązanie
wtyczka, która zapisuje zapytanie na stronie, wysyła e-mail do biura i zakłada leada w CRM; gdy CRM nie odpowiada, ponawia później
Efekt
zapytanie w CRM po 0,6–0,7 s; 71–96 s pracy biura mniej na każdym zgłoszeniu (przeczytanie maila zostaje); przy 50 zapytaniach w miesiącu ok. 59–80 minut

Wyzwanie

Formularz kontaktowy wysyłał e-mail i na tym się kończyło. Dalej pracowała osoba w biurze: otwierała wiadomość, zakładała nowego klienta w CRM, przepisywała imię, nazwisko, e-mail, telefon i treść, a na koniec dopisywała wiersz do arkusza z zapytaniami. Każde zgłoszenie to kilka okien, kilkadziesiąt kliknięć i kilkaset znaków. Przy pomyłce w adresie e-mail odpowiedź nie dojdzie, a zapytanie z piątku czeka do poniedziałku.

Warunki: bez płatnych usług pośredniczących, bez ciężkich wtyczek do formularzy, formularz musi zostać dostępny (po wcześniejszej pracy nad dostępnością strony) i odporny na spam.

Co zbudowaliśmy

Prostą wtyczkę do WordPressa, która po kliknięciu „Wyślij”:

  1. sprawdza, czy to nie bot (cztery niewidoczne dla klienta zabezpieczenia),
  2. sprawdza poprawność danych i pokazuje błędy przy polach,
  3. zapisuje zgłoszenie w bazie strony (lista w panelu, z historią),
  4. wysyła e-mail do biura (odpowiedź trafia od razu do klienta),
  5. zakłada leada w CRM; jeśli CRM nie odpowiada, próbuje ponownie po 1, 2, 5, 15 minutach i dłużej, a po kilku godzinach bez skutku powiadamia administratora.

Jak sprawdzaliśmy

Przepływ: 10 zgłoszeń z telefonu i 3 bez JavaScriptu; czas od kliknięcia do zapisu, e-maila i leada w CRM.

  • Awaria CRM: wyłączyliśmy CRM na ponad 3 minuty i wysłaliśmy 3 zgłoszenia; potem CRM „zawiesiliśmy” (włączony, ale nie odpowiada); sprawdziliśmy też, co się dzieje, gdy CRM zapisze zgłoszenie, a odpowiedź nie wróci.
  • Spam: zgłoszenia bez zabezpieczenia, z wypełnionym ukrytym polem, wysłane zbyt szybko, z podrobionym znacznikiem czasu i seria z jednego adresu.
  • Ręczny proces (model): przepisanie zgłoszenia z maila do CRM i arkusza w stałym, przeciętnym tempie człowieka: raz pisząc wszystko z klawiatury, raz kopiując dłuższe pola. Po 3 przebiegi.

Wyniki

RęcznieAutomatycznie
Od zgłoszenia do leada w CRMgdy ktoś usiądzie do maila, potem 85–111 s pracy0,61–0,69 s
Praca biura na jedno zgłoszenie (bez przeczytania maila)71 s (kopiuj-wklej) do 96 s (przepisywanie)0 s
CRM nie działazgłoszenie czeka w skrzyncezgłoszenie w bazie i w skrzynce, w CRM po powrocie (3 z 3, bez duplikatów)

Oszczędność czasu biura (założenie: liczba zapytań w miesiącu; czas z pomiaru, bez czytania maila, które zostaje):

Zapytań w miesiącuPrzepisywanie (96 s na zgłoszenie)Kopiuj-wklej (71 s)
2032 min24 min
5080 min59 min
100161 min118 min

Nie liczyliśmy kosztu pomyłek przy przepisywaniu (literówka w adresie e-mail), bo model ich nie przewiduje; automat ich nie robi.

Co nie było oczywiste

  1. Awaria CRM wydłużała wysyłkę formularza o sekundę. Wtyczka przekazuje zgłoszenie do CRM w osobnym procesie, żeby klient nie czekał. Mimo to w czasie awarii potwierdzenie pojawiało się po 1,5 s zamiast 0,65 s: mechanizm, który uruchamia ten osobny proces, czeka na niego do sekundy. Dłużej klient nie czeka nigdy.
  2. Zgłoszenie może być w CRM, choć strona dostała błąd. Gdy CRM zapisze dane, a odpowiedź nie dotrze, zwykłe ponowienie założyłoby drugiego leada. Wtyczka przed zapisem sprawdza, czy zgłoszenie o tym numerze już jest; w teście: „rekord już był w CRM”, bez duplikatu.
  3. CRM odrzuca zgłoszenie od stałego klienta. EspoCRM traktuje drugie zapytanie od tej samej osoby jako możliwy duplikat i odmawia zapisu. Wtyczka zapisuje to w historii zgłoszenia i zakłada leada mimo to, żeby zapytanie nie zginęło.
  4. Limit zgłoszeń z jednego adresu może zablokować wszystkich. W naszym środowisku wszystkie zgłoszenia przychodziły z jednego adresu pośrednika. Na serwerze za takim pośrednikiem (proxy, CDN) limit trzeba liczyć z prawdziwego adresu klienta, inaczej po kilku zapytaniach formularz przestałby działać dla wszystkich.
  5. Bez ruchu na stronie kolejka stoi. Ponowienia w WordPressie uruchamiają się przy odwiedzinach strony. W nocy, bez odwiedzin, nikt by ich nie uruchomił; dlatego na serwerze kolejkę co minutę uruchamia systemowy harmonogram.
  6. Najprostsze zabezpieczenia wystarczyły. Ukryte pole, minimalny czas wypełnienia i limit odrzuciły wszystkie testowe boty bez CAPTCHA, która przeszkadza ludziom.

Efekt dla biura

PrzedPo
Czas od zgłoszenia do CRMdo pierwszej wolnej chwili biura0,61–0,69 s
Praca biura na zgłoszenie85–111 s (z przeczytaniem maila)przeczytanie maila (ok. 14 s w modelu)
Zgłoszenia zgubione przy awarii CRM w teścienie dotyczy0 z 3
Zgłoszenia od botów zapisane w teścienie dotyczy0

Wnioski dla właściciela strony

Najwięcej czasu oszczędza się na tym, czego nikt nie liczy: przepisywaniu tych samych danych w dwa, trzy miejsca.

  • Integracja musi zakładać, że druga strona czasem nie odpowiada. Kolejka i historia zgłoszenia są ważniejsze niż sam „przycisk wyślij do CRM”.
  • Prosta wtyczka bez zależności jest łatwa w utrzymaniu i nie spowalnia strony; płatne pośredniki nie są do tego konieczne.
  • Liczby z modelu warto sprawdzić u siebie: wystarczy zmierzyć stoperem przepisanie trzech zapytań.

Formularz pochodzi z audytu dostępności według WCAG 2.2, a cała strona z redesignu mobile-first. Jeśli dziś wiadomości z formularza nie dochodzą, zacznij od sytuacji nie dochodzą maile z formularza. Opisz, jak obsługujesz zapytania, w formularzu, a zaproponujemy rozwiązanie dla Twojej strony firmowej.

Opis techniczny: przepływ, awaria CRM, antyspam i pułapki

Dla administratorów: pełny przebieg z dokładnymi liczbami, poleceniami i konfiguracją. Wersje: stan na listopad 2026.

W skrócie

ElementSzczegóły
ŚrodowiskoWordPress 7.1.4, PHP 8.3.36 (Apache, mod_php), MariaDB 11.4.13, Elementor 4.3.5; EspoCRM 10.0.11 (PHP 8.4.27, MariaDB 11.4), Mailpit 1.31 (stan na 2026-11)
Wtyczkaleady (PHP + czysty JS, bez zależności): tabele wp_leady i wp_leady_zdarzenia, REST leady/v1/zgloszenie i /token, admin-post.php bez JS, WP-Cron, panel WP_List_Table
Przepływantyspam → walidacja → INSERT → wp_mail → wp_schedule_single_event + spawn_cron → GET /api/v1/Lead?where…cWpLeadId → POST /api/v1/Lead z X-Api-Key
Odpornośćkolejka: odstępy 1, 2, 5, 15, 30, 60, 120 min, po 8. próbie „porzucono” + e-mail; zajęcie rekordu jednym UPDATE; idempotencja cWpLeadId; 409 → X-Skip-Duplicate-Check
Efektlead w CRM 607–693 ms po kliknięciu; awaria CRM 3 min 18 s: 3/3 dostarczone, 0 duplikatów; ręczny proces 85,1–110,8 s na zgłoszenie

Kontekst

Strona z case study o dostępności: formularz motywu (wp_mail) zastąpiony formularzem wtyczki o tym samym, dostępnym markupie (komunikaty przy polach, podsumowanie błędów, role="status"). Shortcode [rb_formularz] w widgecie tekstowym Elementora, więc strona dalej nie ładuje skryptów frontendu Elementora. CRM to otwartoźródłowy EspoCRM w Dockerze: prawdziwe REST API z kluczem, rolami i wykrywaniem duplikatów, więc test pokazuje zachowanie realnej integracji, a nie atrapy webhooka.

Konfiguracja CRM (crm-konfiguracja.sh): pole wpLeadId w encji Lead (EspoCRM nadaje mu nazwę cWpLeadId), rola „Strona www (API)” z prawem tylko do tworzenia i odczytu leadów, użytkownik typu API z kluczem.

Diagnoza: jak mierzyliśmy

Przepływ: telefon 390 px, otwarcie /kontakt/, odczekanie 3,3 s (minimalny czas antyspamu), wypełnienie, kliknięcie; czasy względem kliknięcia z dziennika wtyczki (wp_leady_zdarzenia.czas, datetime(3), UTC), z Mailpit (Created) i z CRM. Zegar jeden (ten sam host).

Awaria: docker stop crm (CRM wyłączony), docker pause crm (CRM włączony, nie odpowiada), utrata odpowiedzi symulowana cofnięciem statusu zgłoszenia już zapisanego w CRM.

Antyspam: curl na /wp-json/leady/v1/zgloszenie z tokenem z /token i bez niego.

Proces ręczny (model): ekran 1440×900; przygotowanie poza pomiarem (zalogowany CRM, otwarta skrzynka i arkusz), potem: kliknięcie najnowszej wiadomości w Mailpit, czytanie (200 słów/min), przełączenie do EspoCRM, #Lead/create, wypełnienie imienia, nazwiska, e-maila, telefonu i opisu, zapis, przełączenie do arkusza (lokalna strona z tabelą), wiersz: data, imię i nazwisko, e-mail, telefon, źródło, temat, status. Wariant A: wszystko z klawiatury (0,3 s/znak). Wariant B: pola dłuższe niż 12 znaków przez kopiuj-wklej (przełączenie 2 s, zaznaczenie 1,5 s, Ctrl+C 0,5 s, przełączenie, kliknięcie pola 1 s, Ctrl+V 0,5 s). Po 3 przebiegi.

Wyniki

Przepływ, z JavaScriptem (10 zgłoszeń), ms od kliknięcia:

EtapMinMaks
zapis w bazie438509
e-mail wysłany (dziennik) / w Mailpit489 / 485560 / 556
lead utworzony w CRM (1. próba)607693
komunikat „Dziękujemy” na ekranie628713

Bez JavaScriptu (3 zgłoszenia, POST i przekierowanie): zapis 78–107 ms, e-mail 135–161 ms, CRM 284–334 ms, komunikat po przeładowaniu 368–440 ms. Różnica względem JS to dodatkowe żądanie GET /wp-json/leady/v1/token (świeży nonce, patrz pułapki).

Awaria CRM (wyłączony 3 min 18 s, 3 zgłoszenia):

22:05:17.623  zapis  ok     zapis w bazie po 10 ms
22:05:17.678  mail   ok     do biuro@…
22:05:18.695  kolejka ok    przekazano do kolejki CRM, odpowiedź po 1082 ms
22:05:25.749  crm    blad   próba 1: http_request_failed: cURL error 28: Resolving timed out after 5001 milliseconds; następna za 1 min
22:06:32.715  crm    blad   próba 2: http_request_failed: cURL error 28: Resolving timed out after 5001 milliseconds; następna za 2 min
22:08:29.075  (CRM uruchomiony)
22:08:43.617  crm    ok     utworzono Lead … (próba 3)

Pozostałe dwa zgłoszenia: lead w CRM 75 s po uruchomieniu CRM (termin trzeciej próby). W CRM 3 rekordy z cWpLeadId …-16, …-17, …-18, żadnego duplikatu. E-mail do biura wyszedł w każdym przypadku od razu.

CRM zawieszony: http_request_failed: cURL error 28: Operation timed out after 5002 milliseconds with 0 bytes received, druga próba po minucie udana.

Utracona odpowiedź (symulacja): zgłoszenie już w CRM, status w WordPressie cofnięty na „błąd” → kolejka: rekord już był w CRM: 6ae90aa091379cd1d (bez duplikatu), w CRM 1 rekord.

Duplikat: drugie zgłoszenie z tym samym imieniem i e-mailem → EspoCRM 409 Conflict z listą podobnych rekordów → zdarzenie duplikat → ponowny POST z X-Skip-Duplicate-Check: true → lead utworzony.

Antyspam:

PróbaOdpowiedźZapis w bazie
brak nonce400 {"status":"blad","kod":"nonce"}nie
wysłane < 3 s po wyświetleniu201 {"status":"ok"}nie (zdarzenie za-szybko)
wypełnione pole-pułapka strona_www201 {"status":"ok"}nie (pulapka)
podrobiony znacznik czasu201 {"status":"ok"}nie (czas-podpis)
6. zgłoszenie z jednego IP w 10 min429 {"status":"blad","kod":"limit"}nie

Proces ręczny (3 przebiegi na wariant):

WariantCzas łącznieCzytanie mailaCRMArkuszZnakiKliknięciaPrzełączenia okien
A przepisywanie110,2–110,8 s14,2–14,3 s52,6 s40,7 s26992
B kopiuj-wklej85,0–85,4 s14,2–14,4 s29,6 s38,2 s36916

Praca do zastąpienia automatem (bez czytania maila, które zostaje): A 96,5 s, B 70,7 s na zgłoszenie.

Co nie było oczywiste

  1. spawn_cron() nie jest w pełni nieblokujący. WordPress wywołuje wp-cron.php żądaniem z blocking => false i timeout => 0.01, a mimo to przy awarii CRM odpowiedź formularza przyszła po 1530–1547 ms zamiast 628–713 ms; zdarzenie „kolejka” po 1078–1082 ms. Uruchomiony cron od razu łączy się z CRM i blokuje na rozwiązywaniu nazwy, a żądanie wywołujące czeka na niego ok. 1 s. W normalnej pracy cron kończy się szybciej (lead w CRM 607–693 ms jeszcze przed odpowiedzią do przeglądarki). Granica to ok. 1 s, nie 5 s limitu CRM; gdyby i to było za dużo: bez spawn_cron(), tylko systemowy cron (lead w CRM do 60 s).
  2. Zatrzymany kontener to nie „connection refused”. Po docker stop nazwa usługi przestaje się rozwiązywać w sieci Dockera i cURL czeka na DNS: cURL error 28: Resolving timed out after 5000 milliseconds. Po docker pause połączenie wisi: Operation timed out after 5002 milliseconds with 0 bytes received. Oba przypadki to błąd 28 po limicie 5 s (ustawienie wtyczki „Limit czasu odpowiedzi CRM”, domyślnie tyle co w wp_remote_request). Odwiedzający na ten limit nie czeka, bo wywołanie CRM idzie przez cron; czeka tylko na opóźnienie z punktu 1.
  3. Ponowienie bez idempotencji tworzy duplikaty. Jeśli CRM zapisze rekord, a odpowiedź przepadnie (limit czasu, zerwane połączenie), kolejne POST założy drugi rekord. Pole cWpLeadId (<host>:<port>-<id>) i GET przed POST to rozwiązują; w teście: „rekord już był w CRM”.
  4. Dwa procesy, jedno zgłoszenie. Zdarzenie jednorazowe (leady_crm_jeden) i przegląd kolejki co minutę mogą trafić na to samo zgłoszenie. Zajęcie jednym zapytaniem: UPDATE … SET status_crm='w-toku' WHERE id=%d AND status_crm IN ('oczekuje','blad'); kto nie zmienił wiersza, nie wysyła. „W toku” dłużej niż 5 minut wraca do kolejki (proces przerwany).
  5. EspoCRM odrzuca duplikaty kodem 409. Domyślne wykrywanie duplikatów leadów (ta sama osoba / e-mail) zwraca 409 Conflict z tablicą podobnych rekordów zamiast tworzyć leada. Bez obsługi tego kodu każde kolejne zapytanie stałego klienta kończyłoby się w kolejce jako „porzucone” po 8 próbach.
  6. Nonce w HTML i strony z pamięci podręcznej. Nonce WordPressa żyje 12–24 h; strona kontaktu podana z cache po dobie ma nieważny nonce i formularz odrzuca ludzi (400). Dlatego JS pobiera świeży nonce z GET /token (Cache-Control: no-store) tuż przed wysłaniem; koszt: ok. 350 ms (438–509 ms do zapisu z JS wobec 78–107 ms bez JS).
  7. Limit na IP za pośrednikiem. W labie każde żądanie ma REMOTE_ADDR bramki Dockera (192.168.48.1), więc 6. zgłoszenie od kogokolwiek dostało 429. Za reverse proxy lub CDN limit musi liczyć adres z zaufanego nagłówka proxy (tylko od znanych adresów pośrednika), inaczej blokuje wszystkich. IP przechowujemy tylko jako HMAC (hash_hmac('sha256', $ip, wp_salt())).
  8. EspoCRM 10 i montowanie katalogu. Montowanie całego /var/www/html jako wolumenu daje przy starcie „LEGACY INSTALLATION METHOD DETECTED … No further EspoCRM upgrades will be available.”; od wersji 10 montuje się osobno data, custom i client/custom.

Rozwiązanie

Przyjęcie zgłoszenia (includes/formularz.php, ta sama funkcja dla REST i admin-post.php):

$spam = leady_sprawdz_spam( $post );           // nonce, pułapka, czas (HMAC), limit na skrót IP
if ( $spam ) { … return in_array( $spam, [ 'nonce', 'limit' ], true ) ? [ 'blad', $spam ] : [ 'ok', 0 ]; } // bot dostaje „ok”
$b = leady_waliduj( $d );                       // te same komunikaty co w JS
$wpdb->insert( leady_tabela(), [ … ] );         // zapis
leady_wyslij_mail( leady_pobierz( $id ) );   // wp_mail, Reply-To klienta
wp_schedule_single_event( time(), 'leady_crm_jeden', [ $id ] );
spawn_cron();                                      // CRM w osobnym żądaniu

Przekazanie do CRM (includes/crm.php):

// 1. czy rekord już jest
leady_crm_api( 'GET', 'Lead?' . http_build_query( [ 'select' => 'id', 'maxSize' => 1,
  'where' => [ [ 'type' => 'equals', 'attribute' => 'cWpLeadId', 'value' => $klucz ] ] ] ) );
// 2. utworzenie; 409 = możliwy duplikat → zapis z pominięciem sprawdzania
leady_crm_api( 'POST', 'Lead', $rekord );                                        // nagłówek X-Api-Key
leady_crm_api( 'POST', 'Lead', $rekord, [ 'X-Skip-Duplicate-Check' => 'true' ] );
// błąd: proby+1, nastepna_proba = now + [1, 2, 5, 15, 30, 60, 120][proby-1] min; po 8. próbie „porzucono” + e-mail

Kolejka (includes/kolejka.php): własny interwał rb_minuta (60 s), zdarzenie leady_kolejka, zajęcie rekordu jednym UPDATE, na serwerze systemowy cron: wp cron event run --due-now co minutę (w labie kontener lab-cron).

Panel (includes/panel.php): „Zgłoszenia” (lista: data, osoba, e-mail, telefon, status e-mail i CRM, liczba prób, ostatni błąd, „Ponów teraz”), szczegóły z dziennikiem przepływu, ustawienia (adres i klucz CRM albo stała RB_LEADY_CRM_KEY w wp-config.php, limit czasu, limity antyspamu).

Dostępność i jakość: formularz wtyczki ma ten sam markup co po audycie WCAG (komunikaty przy polach, podsumowanie błędów, role="status" z fokusem po wysłaniu, pole-pułapka aria-hidden z tabindex="-1"); axe 0 na stronie kontaktu i w stanie z błędami, detektor impeccable 0.

Rezultaty

EtykietaWartość
Od kliknięcia do leada w CRM607–693 ms (10/10 w pierwszej próbie)
Od kliknięcia do e-maila w skrzynce485–556 ms
Ręczne przepisanie zgłoszenia (mail → CRM → arkusz)85,0–85,4 s (kopiuj-wklej), 110,2–110,8 s (przepisywanie)
Oszczędność przy 50 zapytaniach w miesiącu (założenie)59 min (kopiuj-wklej) do 80 min (przepisywanie)
Awaria CRM 3 min 18 s3/3 zgłoszenia w CRM po powrocie (14,5 s i 75 s), 0 duplikatów, e-maile bez opóźnienia
Odpowiedź formularza przy awarii CRM1530–1547 ms (normalnie 628–713 ms)
Antyspam w teście4 rodzaje prób odrzucone, 0 zapisów od „botów”, 1 × 429 przy limicie

Wnioski

  • Integracja formularza z CRM to kilkaset wierszy PHP bez zależności; większość z nich obsługuje sytuacje, gdy coś nie działa.
  • Mierzalna korzyść to czas biura (71–96 s na zgłoszenie w modelu) i czas reakcji (lead w CRM w niecałą sekundę).
  • Odporność trzeba sprawdzić awarią: wyłączyć CRM, zawiesić go, zgubić odpowiedź; dopiero wtedy widać duplikaty i opóźnienia.
  • Antyspam bez CAPTCHA wystarczył na testowe boty; przy prawdziwym ruchu warto po miesiącu przejrzeć dziennik zdarzeń spam.

Gdy zapytania ze strony giną albo trzeba je przepisywać

  • Strona firmowa

    Dla firm z kilkoma usługami i realizacjami do pokazania, których klienci przed telefonem porównują oferty w Google.

    Wycena indywidualna

    • osobna podstrona dla każdej usługi, pod frazy wpisywane przez Twoich klientów
    • realizacje ze zdjęciami i krótkim opisem
    • formularz zapytania z wyborem tematu
    • projekt strony głównej do akceptacji przed budową całości
    • podstawy SEO, certyfikat SSL i wersja na telefon

    Co dostajesz

    Bezpłatną wycenę w 24 h: listę podstron, funkcje, termin i cenę netto. Cena zależy od zakresu, dlatego wyceniamy indywidualnie.

  • Bezpłatny przegląd strony

    Gdy masz stronę i nie wiesz, w jakim jest stanie: czy jest bezpieczna, aktualna i czy pomaga zdobywać klientów.

    bezpłatnie

    • certyfikat SSL i ostrzeżenia przeglądarki
    • wersje WordPressa, PHP, wtyczek i motywu
    • kopie zapasowe: czy są i gdzie
    • wygląd i szybkość na telefonie
    • podstawy widoczności w Google

    Co dostajesz

    Krótki raport e-mailem: co działa, co poprawić najpierw i od czego zacząć.

Zadzwoń i powiedz, czego potrzebuje Twoja strona

Rozmawiamy o nowej stronie, hostingu albo problemie z obecną stroną. Wycenę nowej strony dostajesz bezpłatnie w 24 h.

Telefon (polski) +48 501 250 220

Dostępni: poniedziałek–piątek, 9:00–17:00