-
-
Architecture: SES, End User Messaging SMS and Social as the channels, a Strands agent on Bedrock AgentCore, deterministic policy, DynamoDB
-
Live dashboard on the deployed stack: the receivables pipeline for the synthetic demo tenant
-
Live: one invoice thread across channels, a Dutch reply understood as a promise to pay, then kept
-
Live dashboard, Evaluation tab: the blind set of 90 synthetic replies, scored against a keyword baseline
Inspiration
At Rui Codex we build ERP and Odoo integrations for small Belgian companies. With almost every client we see the same thing. Invoices are sent on time, but they are not paid on time, and chasing them is a job nobody owns. It is usually done late in the day by the founder or a part-time bookkeeper. They copy a reminder template, guess which language the customer prefers, and lose track of who promised to pay on Friday.
Our clients are not the only ones. In a 2024 background note, the Council of the EU states that "late payments account for 25% of bankruptcies in the EU nowadays". The European Commission's EU Payment Observatory reported that in 2023 "half of Belgian companies reported facing issues due to late payments, which is above the EU average". Intrum's European Payment Report 2026 surveyed 8,385 businesses in 20 countries. It found that the B2B payment gap widened from 16 days in 2023 to 20 days, and that 62% of businesses pay their own suppliers late because their customers pay them late. The law is already on the creditor's side. The EU Late Payment Directive (2011/7/EU) and the Belgian Act of 2 August 2002 cap B2B payment terms and make late-payment interest due automatically. The law does not chase anybody, though. Someone still has to write, read the answer and follow up.
Most of that work is a conversation, not bookkeeping. Customers answer reminders in free text: "we betalen vrijdag", "la facture 2026/0142 est contestée, la ligne 3 n'a pas été livrée", "please send it to accounts@ instead". That is exactly the kind of work an agent can take over, as long as it knows when to stop and ask a person.
What it does
PayLoop is an accounts-receivable agent that runs the follow-up conversation on the channels people actually answer:
- It sends the reminder. Every morning PayLoop picks up overdue invoices from the SME's system of record: Odoo over XML-RPC, or a synthetic demo ledger. It sends a short, polite reminder by email through Amazon SES. The reminder states the invoice number, amount, due date, IBAN, the Belgian structured reference (OGM) and how to opt out.
- It understands the answer. The debtor replies however they like:
- by email, received by SES on
pay.ruicodex.com; - by two-way SMS, through AWS End User Messaging SMS;
- or, optionally, on WhatsApp, through AWS End User Messaging Social.
- by email, received by SES on
They can write in Dutch, French or English. PayLoop works out what the reply means: a promise to pay with a date ("next Friday", "fin du mois"), a dispute, a payment claim ("already paid on the 12th"), a request for a payment plan, a contact change, an out-of-office, an opt-out, or a question about the invoice.
- It acts, within rules. A promise within 30 days is recorded, confirmed in the debtor's language, and turned into a one-time follow-up. A payment claim is checked against the payments in the ledger, by OGM and amount. A dispute pauses all reminders and opens a case with a summary and a suggested reply. A change of contact to a different email domain is never applied automatically, because redirecting invoices to a new domain is a classic invoice-fraud pattern.
- It follows up on the promised date. If the promised payment has not arrived by the morning after the promised date, PayLoop sends a short SMS nudge. It only does so with consent, inside Belgian quiet hours, and never on Sundays or public holidays. The debtor can answer that SMS, and the conversation continues in the same thread that started by email.
- It hands the delicate cases to a person. Disputes, payment plans and anything below 0.70 confidence go to a review queue in a small dashboard. A person can read the whole cross-channel timeline there, edit the drafted reply and send it with one click.
PayLoop never adds fees or interest, never agrees a payment plan by itself and never threatens. For invoices to consumers it follows Book XIX of the Belgian Code of Economic Law: the first reminder is free of charge and leaves at least 14 calendar days before any further step.
How we built it
Everything runs in eu-west-1 (Ireland) and is deployed from one AWS CDK (Python) stack.
The three CDS services (all called at runtime)
- Amazon SES (send and receive). We send with
sesv2.send_email, through a configuration set. Each reminder carries a per-invoiceReply-Totoken (ar+<token>@pay.ruicodex.com), so a reply can be matched to its invoice even when the subject is changed. The subdomain is verified with DKIM and has its own inbound MX, so the company's existing mail on the apex domain is untouched. A receipt rule stores the raw MIME in S3, with a 30-day lifecycle expiry, and invokes theinbound_emailLambda. That Lambda strips quoted history and signatures from Outlook, Gmail and Apple Mail replies, and detects auto-replies. SES delivery, bounce and complaint events flow from the configuration set to EventBridge and onto the invoice timeline. - AWS End User Messaging SMS (two-way). We use the
pinpoint-sms-voice-v2client:send_text_messagefor confirmations and promised-date nudges, and a two-way number whose inbound messages are published to an SNS topic and handled by theinbound_smsLambda. STOP / STOPPEN / ARRET is honoured at once, both in EUM's opt-out list and in our own record. Delivery events from the SMS configuration set feed the same timeline. Belgium supports two-way long codes but not alphanumeric sender IDs, so we designed around a number. - AWS End User Messaging Social (WhatsApp, optional). We use the
socialmessagingclient'ssend_whatsapp_message. Inbound WhatsApp messages arrive through SNS at aninbound_whatsappLambda that shares the same pipeline. Channel status for this submission: code path implemented and unit-tested (send, inbound parsing, SNS wiring in the CDK stack), not connected to a WhatsApp Business account for this submission.
The agent
- Amazon Bedrock AgentCore Runtime hosts a Strands Agents agent (
agentcore/main.py). The inbound Lambdas invoke it withinvoke_agent_runtime. WithAGENT_BACKEND=inprocessthe same code runs inside the Lambda, which keeps local tests and the evaluation reproducible. - Amazon Bedrock AgentCore Memory keeps one conversation thread per invoice across channels, so an SMS that says "betaald" is read in the context of the email exchange before it.
- Claude Haiku 4.5 on Amazon Bedrock (EU cross-region inference profile), called through the
Converse API with a single forced tool. The call returns a typed
Understanding: intent, language, promised date and amount, a confidence score and a one-line summary. Amazon Nova 2 Lite is configured as a fallback model. - Amazon Bedrock Guardrails checks every outbound text for threats, insults and PII leaks.
- Deterministic policy (
payloop/agent/policy.py) sits between the model and every tool:record_promise,open_dispute,register_payment_claim,request_payment_plan,update_contact,opt_out,snooze,escalate_to_humanandsend_reply. The policy, not the model, decides:- which promise dates are acceptable;
- whether an amount is within the balance;
- whether an SMS may be sent now, given quiet hours and Belgian public holidays;
- whether a contact change is safe;
- when to escalate.
Relative dates in three languages ("vrijdag", "fin de mois", "next Tuesday") are resolved against the message date in Europe/Brussels time.
State, time and people
- Amazon DynamoDB (single table, on-demand) holds invoices, the event timeline, cases, thread tokens, contact lookups and idempotency keys with a TTL. A message delivered twice is processed once.
- Amazon EventBridge Scheduler runs the daily sweep, and creates one one-time schedule per
promise that fires the
followupLambda on the promised date. No polling loop runs between events. - AWS Lambda runs everything else: the sweep, followup, inbound email, SMS and WhatsApp, events, and the dashboard API.
- The dashboard is a dependency-free single-page app with a JSON API. Both are served by a Lambda Function URL, and the bearer token is kept in AWS Systems Manager Parameter Store (SecureString). It shows the receivables pipeline, expected cash in, the human review queue, every invoice's cross-channel timeline and the evaluation results.
- Amazon CloudWatch and AgentCore Observability give us logs, metrics and traces for every agent turn.
- Odoo connector: the system of record is pluggable. The Odoo
SourceLedgerreads open customer invoices and payments over XML-RPC.
Evaluation
We wrote two labelled evaluation sets of synthetic debtor replies in Dutch (including Flemish
phrasing), Belgian French, English and mixed language: a main set of 198 messages and a blind
held-out set of 90 messages written by a separate author after the prompt was frozen, without
access to the prompt or its errors. They cover all nine intents, relative dates, partial amounts,
auto-replies and fraud-shaped contact changes. Each message has an expected intent, date, amount,
action set and escalation flag. The harness (eval/run_eval.py) runs the real understanding call and
the real policy against every message, and a stratified subset through the full Strands agent turn
with tools. Results next to a keyword/regex baseline:
| metric | PayLoop, blind set (90) | PayLoop, main set (198) | keyword baseline (blind / main) |
|---|---|---|---|
| intent accuracy | 93.3 % | 97.5 % | 57.8 % / 72.2 % |
| macro-F1 | 0.939 | 0.971 | - / 0.730 |
| intent accuracy NL / FR / EN | 91.4 / 90.0 / 100 % | 96.1 / 98.4 / 98.1 % | - / 74.0, 69.8, 69.2 % |
| promised date exact match | 94.4 % (17/18) | 93.5 % (43/46) | 88.9 % / 91.3 % |
| escalation precision / recall | 0.977 / 1.000 | 0.963 / 1.000 | - |
| action set exact match (policy plan) | 92.2 % | 96.5 % | - |
| full agent turn: executed tools = label | 92.6 % (27 turns) | 97.8 % (45 turns) | - |
| understanding latency p50 / p95 | 1,396 / 2,605 ms | 1,343 / 1,906 ms | - |
| model cost per 1,000 replies (understanding) | 3.44 USD | 3.37 USD | 0 |
The main-set numbers are after one round of prompt and policy fixes made from that set's own errors (before any fix: 96.5 % intent accuracy), so the blind set is the fairer figure. Amazon Nova 2 Lite, the fallback model, scores 91.9 % on the main set at 1.23 USD per 1,000 replies. Prices come from the AWS Price List API for eu-west-1.
End-to-end on the deployed stack, in our live test a reply e-mail was answered 8 seconds after SES received it (inbound at 20:17:08 UTC, answer delivered at 20:17:16 UTC, one run); simulated replies through the dashboard take 10-17 s including the AgentCore round trip.
Challenges we ran into
- Replies are messy. An email answer arrives as quoted history, signatures, disclaimers and sometimes HTML only, in Latin-1 or UTF-8. Getting the debtor's actual sentence out of an Outlook-French or Gmail-Dutch reply took real fixtures and tests, not one regular expression.
- Dates are language and calendar problems. "Vrijdag" written on a Friday, "fin du mois", "na het verlof" and Belgian public holidays all need a fixed reference date and a time zone. Otherwise the same message means different things on different days, and the evaluation is not reproducible.
- Deciding what the agent may not do. Some things are easy for a model and wrong for a business: agreeing a payment plan, accepting a new bank address, sending an SMS at 22:00. We moved those decisions into deterministic policy that the model cannot override.
- Channel constraints in Belgium. Belgium has two-way long codes but no alphanumeric sender IDs, and it is not an RCS launch country. New SES and SMS accounts also start in a sandbox. We designed the demo around what works for a judge today, and we say so in the testing instructions.
- Odoo moved under us. Odoo's public demo server now hands out Odoo 20 databases, where the classic XML-RPC endpoint answers 404. The connector therefore speaks both XML-RPC (Odoo 16-19) and the newer JSON-2 API (API key, Odoo 19+), and picks one automatically. The live check also showed that an invoice "in payment" can still carry an open balance, which a naive filter would have silently dropped from the chase.
- Retries must not double-send. Our first fallback re-ran a turn in-process whenever the AgentCore runtime returned an error - but a runtime error can happen after the reply was already sent. We now fall back only when the runtime never started the turn, and tool failures are reported back to the agent instead of aborting the turn half-way.
Accomplishments that we're proud of
- One conversation thread across email, SMS and (optionally) WhatsApp. A promise made by email is nudged by SMS on the right morning, and the SMS answer lands in the same timeline.
- An honest evaluation: a published synthetic dataset, a keyword baseline next to every number, and per-language results instead of one flattering average.
- Guard rails that are code, not prompts. They cover quiet hours, Belgian holidays, opt-outs on every channel, the B2C first-reminder rule, and fraud-shaped contact changes that always go to a person.
- A stack that a judge can reproduce: one CDK deployment, no servers, close to zero cost when idle.
What we learned
- The cheapest place to make an agent safe is between the model and its tools. The model reads and drafts; policy decides.
- AgentCore Memory keyed by invoice is a natural fit for multi-channel work: the channel becomes a transport, not a separate conversation.
- EventBridge Scheduler one-time schedules turn "remind me on Friday" into infrastructure, with no cron table to maintain.
- Writing the evaluation set before tuning the prompt kept us honest about which intents were actually hard: on the blind set the weakest intents were already_paid (F1 0.889) and dispute / promise_to_pay (0.900) - mostly replies that mix a payment with a remark about the amount.
What's next
- Pilot with one or two Belgian SMEs that use Odoo. We would measure the hypothesis below on their own data, with their consent.
- Native connectors for the accounting tools common in Belgium. We also want to read structured e-invoices directly: these have been compulsory between Belgian VAT-liable companies since 1 January 2026.
- A payment link and bank-feed matching (CODA / CAMT.053), so that "paid" can be confirmed without a person.
- Voice: let the agent offer a call-back through Amazon Connect when a debtor writes "call me".
- Listing PayLoop as a SaaS product on AWS Marketplace for accounting firms and ERP integrators.
Impact (stated as a hypothesis, with how we would measure it)
We have not run PayLoop with a real customer yet, so we make no claim about results. Our hypothesis is that an agent that replies within minutes in the debtor's language, and follows up exactly on the promised date, shortens the time to cash and frees the owner's time. In a pilot we would measure:
- DSO (days sales outstanding) for the pilot SME, for 90 days before and 90 days after.
- The share of promises kept: invoices paid on or before the date the debtor promised.
- Human minutes per overdue invoice: time logged on chasing, before and after; with PayLoop, only cases in the review queue need a person.
- The escalation rate and override rate: how often a person changes the agent's suggested reply.
Current status: no pilot yet. An opportunity with one existing Rui Codex client (a stone distributor in the United States) has been registered with AWS; that client has not used PayLoop.
Disclosure
- PayLoop was built by Rui Codex during the submission period of the AWS CDS Agentic AI Partner Hackathon (code started on 2026-10-17). No pre-existing code was reused.
- We used AI coding assistants while building PayLoop, which the rules allow. The code is covered by 221 automated tests, and the deployed stack was tested end to end.
- The evaluation set is synthetic. Every message was written with AI assistance and validated by scripts
(schema, dates, weekdays, policy rules); no native-speaker review. It contains no real debtor, customer or personal data. Names use invented
companies and the reserved
.exampledomain. The demo ledger is synthetic too. - No customer results are claimed. The impact section above is a hypothesis together with the method we would use to measure it.
- WhatsApp channel status: code path implemented and unit-tested (send, inbound parsing, SNS wiring in the CDK stack), not connected to a WhatsApp Business account for this submission.
Sources (all read on 2026-10-17)
- Council of the European Union, Presidency background note ST 6586/24, 16 February 2024, interinstitutional file 2023/0323(COD): "late payments account for 25% of bankruptcies in the EU nowadays". https://data.consilium.europa.eu/doc/document/ST-6586-2024-INIT/en/pdf
- European Commission (EISMEA), EU Payment Observatory Annual Report 2024, Belgium chapter: "In 2023, half of Belgian companies reported facing issues due to late payments, which is above the EU average." https://cdn.ceps.eu/wp-content/uploads/2024/12/EU-Payment-Observatory_Annual-Report-2024_EA-01-24-061-EN-C.pdf
- Intrum, "European Payment Report 2026" press release, 21 April 2026: 8,385 businesses in 20 countries; B2B payment gap from 16 days (2023) to 20 days; 62% pay their own suppliers late because of late customer payments. https://www.intrum.com/press/press-releases/press-release-article/?id=3a52db9f-c792-4738-a5e5-36c89a72f1e8
- Directive 2011/7/EU on combating late payment in commercial transactions, as adopted:
- Art. 3(5): B2B payment terms do "not exceed 60 calendar days, unless otherwise expressly agreed";
- Art. 2(6): statutory interest is the reference rate plus at least eight percentage points;
- Art. 6(1): fixed compensation of at least EUR 40.
https://www.legislation.gov.uk/eudr/2011/7/adopted
- Altius, "The Act of 2002 on combating late payment in commercial transactions undergoes further modifications", 21 October 2021: the Belgian Act of 2 August 2002 caps B2B payment terms at 60 calendar days from 1 February 2022, and interest runs from the day after the due date without notice of default. https://www.altius.com/en/news/the-act-of-2002-on-combating-late-payment-in-commercial-transactions-undergoes-further-modifications-to-fight-companies-liquidity-problems/
- Deloitte Legal Belgium, "New B2C rules regarding overdue or unpaid invoices": Book XIX CEL, the free first reminder and the 14-calendar-day remedy period, in force from 1 September 2023. https://www.deloittelegal.be/lg/en/blogs/new-b2c-rules-regarding-overdue-or-unpaid-invoices.html
- FPS Finance, einvoice.belgium.be: "Since 1 January 2026, all Belgian enterprises liable to VAT will have to use structured electronic invoices." https://einvoice.belgium.be/en
Built With
- agentcore-memory
- amazon-bedrock
- amazon-bedrock-agentcore
- amazon-bedrock-guardrails
- amazon-cloudwatch
- amazon-dynamodb
- amazon-eventbridge
- amazon-eventbridge-scheduler
- amazon-ses
- amazon-sns
- amazon-web-services
- aws-cdk
- aws-end-user-messaging-sms
- aws-end-user-messaging-social
- aws-lambda
- aws-systems-manager
- boto3
- claude
- javascript
- odoo
- python
- strands-agents

Log in or sign up for Devpost to join the conversation.