Inspiration
A buyer's list is only the start of a procurement decision. A request for 100 helmets also needs a matching specification, a supplier that can cover the quantity, a credible needed-by date, a price and an auditable reply. Picking the cheapest catalogue row can produce a convincing quote the business cannot fulfil.
We built Buyer Desk around the Procure It procurement workflow. The agent does the repeated reading, comparison and document preparation. The owner inspects the commercial assumptions and decides whether to release the reply. The entry uses synthetic buyer and supplier data. It claims no measured customer savings and no verified supplier stock.
What it does
A buyer sends an RFQ by email (the primary live channel) or as a WhatsApp text (a live-verified secondary channel). Requests carry product quantities and an optional ISO needed-by date. Email also accepts bounded CSV and text-PDF lists.
A Strands agent has six tools: parse_inbound_rfq, lookup_suppliers, lookup_catalogue, draft_quote, create_invoice and check_status. The write tools re-read catalogue prices and supplier state and bind the request to the channel-verified buyer. Model arguments cannot decide the price, recipient, acceptance or approval state.
For each product, sourcing compares matching active offers, available quantity and estimated dates before price. The owner sees the compared offers, VAT, expiry and the stock snapshot. Unpriced lines, short stock, unknown availability, missed dates and unconfirmed substitutions block release. Stock is not reserved and lead times are estimates, not supplier commitments.
Approval queues a dispatch; the model never sends. Email replies go out through Amazon SES v2 with a threaded PDF. WhatsApp replies go through AWS End User Messaging Social as free-form text. An explicit "I accept QT-xxxx. Please invoice." creates an invoice draft only for the same buyer's unchanged, sent and unexpired quote, and the invoice needs a separate owner approval. STOP blocks the channel. No payment is collected.
How we built it
Amazon SES receiving writes inbound mail to private S3 and invokes an intake Lambda, which checks SES authentication, spam and virus verdicts and sender headers before any agent work. WhatsApp events arrive through AWS End User Messaging Social, SNS and a Lambda, scoped to the configured phone number and WABA, with deduplication. Verified identity is passed outside the model, so text inside a file cannot take over an existing quote.
The agent runs on Amazon Bedrock AgentCore Runtime with Strands Agents and Amazon Nova, with an in-process deterministic fallback. A DynamoDB-backed approval state machine, conditional writes, SQS and a send worker separate reasoning from dispatch. A durable attempt is recorded before a live send, and an uncertain provider outcome stops for owner review rather than a resend. This reduces duplicate risk but does not guarantee exactly-once delivery. CloudWatch alarms, a dead-letter queue and a daily send ceiling make failures visible. Everything is defined in AWS CDK (Python) in eu-west-1, with a FastAPI owner console.
Challenges we ran into
Email authentication had to come before agent action so a spoofed From header could not authorise a reply. Procurement writes needed durable claims, so a model failure after creating a document cannot make the fallback create it again.
WhatsApp took three live attempts. Two failed at send, and the logs showed two integration issues: AWS rejects Meta API version v24.0 ("Invalid message version"), and it requires the recipient in E.164 form with a leading +. Both were fixed and redeployed, and the third attempt passed. On that run Bedrock returned a stream error and the deterministic fallback drafted the quote, so we do not claim that a model drove it.
Accomplishments that we're proud of
Live email test, 26 October 2026, 16:23 to 16:36 SAST: one real request produced quote QT-1013 with a PDF 22 seconds later, in the same thread. An acceptance reply created a separate pending invoice, and after owner approval invoice INV-1003 arrived with its PDF in the same thread: total ZAR 4,594.25 including 15% VAT, unpaid.
Live WhatsApp test, 26 October 2026: a final recorded round trip at 19:10 to 19:11 SAST, through AWS End User Messaging Social on business number +27 67 255 3994, produced quote QT-1017 and, after owner approval, invoice INV-1005. WhatsApp replies are text only, with no PDF, and only inside the buyer's 24-hour window. STOP is honoured by the code path but was not exercised live.
The synthetic urgent basket picks an offer with enough stock and an earlier date over a cheaper short-stock offer. An offline, deterministic 26-case evaluation records 26/26 exact case agreement, 25/25 selected-line agreement, 8/8 blocked release attempts prevented, four invalid or ambiguous cases with no document writes and 14 safe dry-run releases. It is not a live run, and it does not measure model accuracy, delivery or latency. Each live result is one controlled run, not production traffic, and no measured customer savings are claimed.
What we learned
Identity, commercial facts, acceptance and dispatch belong in tools and state, not in the model. Live testing found problems that offline contract tests did not, so we keep the failed attempts in the evidence log. A useful owner desk also needs recovery for incomplete requests and uncertain operations.
What's next for Procure It Buyer Desk
Supplier confirmation for unavailable lines, owner review from a phone, a live trace showing the model driving a run, and broader intake validation (photos, scans). The proposed next customer is a South African B2B distributor handling RFQs by email, with WhatsApp later. Pricing is a future validation question. These are plans, not claims.
Pre-existing work and permissions
The Procure It WordPress site, theme and plugins pre-date this entry. The new work is the Python agent, channels, AWS infrastructure, tests and owner workflow; the repository's PROVENANCE.md separates the two. AI coding assistants were used. The code is the owner's own and no client holds rights to it. Third-party assets are confirmed by the submitter. No end-customer identities or results are asserted.
WhatsApp scope
WhatsApp is a live-verified secondary channel, not the primary one. It uses AWS End User Messaging Social: events reach SNS and Lambda, and after the commercial approval checks the send worker calls SendWhatsAppMessage. It is text only, inside the 24-hour window, with no templates and no out-of-window sends. The evidence is controlled round trips from the owner's own phone, not production traffic. We do not assert that this entry wins or qualifies for the Meta Best WhatsApp Solution prize; that is for the judges to decide from the code and evidence. Email (SES receiving and SES v2) is the primary channel. WHATSAPP_ENABLED=0 turns WhatsApp off.
Video note
The demo video combines real screen recordings (the owner console, the real email thread and PDFs, and a live WhatsApp round trip) with a few short AI-generated illustrative clips made with Gemini. Those clips are captioned on screen and are not product footage.
Testing instructions for judges
Console: https://procureit.culltron.app/ (a separate judge sandbox with synthetic data). The access token for the "Unlock the buyer desk" dialog is in JUDGE_ACCESS.md at the root of the private repository, which is shared with the judging account aws-cds-partner; full steps are in docs/JUDGES.md. Email route: send "Please quote 20 boxes of nitrile gloves and 10 safety helmets. Needed by 2026-11-20." to [email protected], then reply "I accept . Please invoice.", then approve the pending invoice in the console. Optional WhatsApp route (text only): message +27 67 255 3994 the same request. No payment is collected, and no AWS account is needed.
Built With
- amazon-bedrock
- amazon-bedrock-agentcore-runtime
- amazon-cloudwatch
- amazon-dynamodb
- amazon-ecr
- amazon-nova
- amazon-ses
- amazon-sns
- amazon-sqs
- amazon-web-services
- aws-cdk
- aws-end-user-messaging-social-(whatsapp)
- aws-lambda
- aws-secrets-manager
- boto3
- fastapi
- python
- strands-agents
- wordpress

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