E-podpis — case-sensitive email match v cron syncu propadá podpisy
import { Aside } from ‘@astrojs/starlight/components’;
Symptom
Sekce “Symptom”Cron pro periodickou synchronizaci stavu e-podpisu běží každých ~5 minut a sleduje smlouvy ve stavu SENT_TO_CLIENT. Logy konzistentně vrací:
[E-sign Cron] Checked 6/7, updated 0I když klient v UI poskytovatele e-podpisu podepsal (envelope.status = completed, recipient status = signed, signedAt = 2026-06-03T07:07:24), aplikace ji nepřevedla do FULLY_SIGNED. Důsledek: status workflow se zasekl, navazující kroky (auto-vystavení zálohové faktury, notifikace, nárůst commission) se nikdy nespustily. Klienti volají s dotazem „kde je má zálohovka”.
Root cause
Sekce “Root cause”Recipient match v cron handleru porovnával email jako case-sensitive string:
// BUGconst clientRecipient = recipients.find(r => r.email === contract.client.email);Poskytovatel e-podpisu normalizuje email na lowercase (RFC 5321 local-part je technicky case-sensitive, ale v praxi 99 % MTA/služeb lowercaseuje). V CRM se ale email uložil tak, jak ho zadal obchodník — s velkými písmeny v local-partu (např. Klient.User@example.com). Po odeslání API vrátilo klient.user@example.com, === selhalo, clientRecipient byl undefined, clientSigned = false, žádná změna stavu.
Bug byl latentní — postihnul jen klienty, kteří mají v emailu velká písmena. U 1/7 smluv v daný den.
Řešení
Sekce “Řešení”Normalizovat email na obou stranách porovnání. Stačí lower-case, ale lépe i .trim():
const clientEmail = (contract.client.email || "").trim().toLowerCase();const ownerEmail = (companyEmail || "").trim().toLowerCase();
const clientRecipient = recipients.find( r => (r.email || "").trim().toLowerCase() === clientEmail);const ownerRecipient = recipients.find( r => (r.email || "").trim().toLowerCase() === ownerEmail);Fix musí být na všech místech, kde se recipient párujte:
- periodický cron (
GET /api/contracts/cron/e-sign-sync) - manuální resync endpoint (
POST /api/contracts/[id]/e-sign-sync) - webhook handler (callback z poskytovatele)
- onsite/offline branch (signed dokument uploadnut ručně)
Pokud byste fixli jen cron, manuální „Synchronizovat” tlačítko v UI by stále padalo.
Backfill (uvíznuté smlouvy)
Sekce “Backfill (uvíznuté smlouvy)”Po nasazení fixu nestačí čekat na další cron tick — uvíznuté smlouvy už jsou ve stavu, který cron nepřechytí (nebo přechytí, ale match teď konečně sedí). Doporučený postup:
- Najít všechny smlouvy s
digiSignId IS NOT NULL AND status IN ('SENT_TO_CLIENT', 'CLIENT_SIGNED') - Pro každou stáhnout
recipientsz API a porovnat case-insensitive - Pokud
clientSigned && ownerSigned→ nastavitstatus = FULLY_SIGNED, vyplnitclientSignedAt/ownerSignedAtz API - Manuálně dohnat finanční navazující kroky — auto-trigger na status změnu vystaví ZF jen v API route, ne při raw DB updatu. Buď je třeba zavolat ručně service
createAndSendDepositInvoice(), nebo přechod znovu protlačit přes oficiální endpoint.
Prevence
Sekce “Prevence”- Vždy lower-case + trim při srovnávání emailů proti externí službě. Nepředpokládej, že druhá strana zachovává case.
- Validace na zápisu: při ukládání emailu klienta normalizovat (nebo aspoň přidat unique index na
lower(email)). Tím se zbavíš duplicit typuUser@example.comvsuser@example.com. - Logy musí být akční:
Checked N/N, updated 0v živé produkci s aktivními pending smlouvami je podezřelé. Když by log obsahoval imatch statusper smlouvu (např.clientSigned=false, found 0/2 expected recipients), bug by se odhalil dřív. - Alert na latency: smlouva
SENT_TO_CLIENTstarší než X dní bez postupu by měla notifikovat obchodníka. Pokud klient skutečně neportál, je to lead pro follow-up; pokud podepsal, jde o tenhle bug. - Monitoring „očekávaný outcome”: pokud máš v jiné tabulce ZF, jejíž počet má korelovat s počtem FULLY_SIGNED smluv, sleduj poměr. Tento bug se projevil prudkým propadem ZF/FULLY_SIGNED ratio.
Související
Sekce “Související”- Webhook handler (z DigiSignu) má stejnou past — viz
webhook-name-match-attaches-lead-to-wrong-contact(stejný princip: případově citlivá identifikace entity z externí služby). - DigiSign signing flow — viz
digisign-envelope-signing-flow.