Routing invoices through SQS, Lambda, and an AI server
Flowbills is an AI invoice processing platform. Invoices arrive by email, and from there the whole pipeline is a chain of small, decoupled steps: Next.js frontend on Vercel, a NestJS backend on EC2 behind Nginx, a Python Flask/FastAPI AI server on EC2 behind Nginx, S3 for storage, and SQS carrying messages between the moving parts.
It starts with AWS SES. An incoming invoice email lands in SES, which drops it into S3. That S3 write triggers a Lambda function, and Lambda receives the email object itself: sender, attachments, everything it needs to decide what happens next.
Lambda is the router. If the attachment is an XML invoice, Lambda processes it directly. Structured data does not need a language model, and parsing it in code is faster and cheaper. Anything else, a picture of an invoice, a PDF, gets routed onward: Lambda puts a message on SQS for the Python AI server.
The AI server picks up the SQS message, fetches the file, and sends it to the ChatGPT Responses API with custom prompting to extract the invoice fields. Once the invoice is processed, the AI server does not call the backend directly. It forwards the result to the NestJS server through SQS as well.
SQS sits between Lambda, the AI server, and the NestJS server deliberately. Lambda never waits on the AI server, the AI server never waits on the backend, and every hop gets retries for free. If the AI call fails or the backend is deploying, the message just waits its turn instead of being lost.
The pattern I keep reusing: let the dumb, fast path (XML parsing in Lambda) stay dumb and fast, reserve the expensive AI call for the files that actually need it, and put a queue between anything that can fail independently.