async function sendInvoiceToAnafWithRetry(invoiceId: string, retryCount = 0): Promise<AnafResponse> {
const token = await getValidAnafToken(invoiceId);
const xmlPayload = await generateUblXml(invoiceId);
try {
const response = await fetch('https://api.anaf.ro/prod/FCD/v1/upload/standard', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/xml',
},
body: xmlPayload,
});
if (!response.ok) {
throw new Error(`ANAF Error HTTP ${response.status}`);
}
return await response.json();
} catch (err) {
if (retryCount < 5) {
const delayMs = Math.pow(2, retryCount) * 1000;
await new Promise((res) => setTimeout(res, delayMs));
return sendInvoiceToAnafWithRetry(invoiceId, retryCount + 1);
}
throw err;
}
}Am scos pe piață un micro-SaaS dedicat freelancerilor din România care vor facturi simple și sincronizare automată cu e-Factura fără să-și prindă urechile în interfețe de anii 2000. În prezent am ajuns la 89 de abonați plătitori și o factură de infrastructură care nu sare de $22 pe lună. Treci mai jos prin arhitectură, stack-ul ales și de ce am evitat infrastructura complicată la început.
Stack-ul și de ce n-am folosit Kubernetes
Când lansezi ceva solo, ultimul lucru pe care ți-l dorești e să fii DevOps la 2 noaptea. Am mers pe variante serverless și PaaS managed unde e posibil:
- Frontend & API: Next.js App Router găzduit pe Vercel Pro ($20/lună pentru dureri de cap în minus cu domeniile și SSL-ul).
- Baza de date: PostgreSQL gestionat pe Supabase (tier-ul gratuit acoperă încă liniștit volumul actual).
- Background Jobs: Upstash Redis + BullMQ pentru procesarea XML-urilor de la ANAF.
- Generare PDF: Puppeteer rulat într-un AWS Lambda containerizat doar la cerere.
La început făceam render la PDF direct în API Route-ul din Next.js, dar pe Vercel ai limita de 10-15 secunde pe funcții serverless. Când 5 useri generau PDF-uri simultan, Vercel crăpa pe timeout. Mutarea pe AWS Lambda m-a salvat și mă costă sub $1 pe lună.
Integrarea cu e-Factura: Câtă bătaie de cap e cu adevărat?
Dacă ai lucrat vreodată cu API-ul ANAF, știi deja că răspunsurile de tip "Internal Server Error 500" fără niciun detaliu sunt la ordinea zilei. Am învățat repede că nu poți trimite o factură sincron în timp ce userul așteaptă în interfață.
Am construit un sistem de reîncercări (retry cu exponential backoff) în fundal. Când utilizatorul apasă "Trimite în e-Factura", aplicația salvează starea pending_submission și bagă job-ul în coada din Upstash. Un worker verifică starea și încearcă de maxim 5 ori la intervale crescătoare. Dacă ANAF pică marți la prânz (ceea ce se întâmplă des), job-ul se execută noaptea când serverele lor sunt mai libere.
Matematica costurilor lunare
Aici sunt cifrele exacte pentru luna trecută, la 89 de utilizatori activi:
- Vercel Pro: $20 (aș fi putut rămâne pe Hobby, dar am vrut analytics și timeout mai mare pe API)
- Supabase: $0 (suntem încă sub limita de 500MB DB și 1GB storage pentru PDF-uri arhivate)
- AWS Lambda & S3: ~$1.20 (generare PDF și stocare temporară)
- Upstash Redis: $0 (tier gratuit, sub 10.000 comenzi/zi)
- Resend (Emailuri transacționale): $0 (sub limita de 3.000/lună)
- Total lunar: ~$21.20
La un abonament mediu de 7 EUR/lună per utilizator, marja de profit este uriașă pe hârtie. Însă costul real nu e serverul, ci timpul pierdut cu suportul tehnic când ANAF schimbă schemă XML fără să anunțe pe nimeni.
Trade-off-uri și ce aș schimba
Next.js App Router a fost o alegere excelentă pentru viteza de dezvoltare, dar un mare minus este stocarea de sesiuni OAuth 2.0 pentru token-urile de la ANAF (care expiră la 90 de zile). A trebuit să scriu o logică destul de întortocheată pentru refresh automat de token-uri înainte de fiecare job din fundal.
Dacă ar fi să o iau de la capăt, aș separa complet backend-ul de procesare facturi într-un microserviciu mic în Go rulat pe un VPS de $5 de la Hetzner. Node.js mănâncă cam multă memorie când parsează XML-urile alea uriașe de răspuns.
Voi ce stack folosiți pentru proiectele personale care au nevoie de background processing de fișiere?