Inspiration

HOP is a network of neighbourhood pickup points in Argentina. When a package reaches a pickup point, the customer has a few days to collect it. Today those customers are reached through CSV campaigns pushed through a middleware, and the existing chatbot only ever covered the pickup points' own questions, never the end customer. "Where is my package?", "I can't make it until Friday", "can I pick it up somewhere else?" all end in a call or a support ticket. We wanted the customer to sort that out in the chat they already use.

What it does

An agentic WhatsApp assistant for HOP customers, built on AWS End User Messaging Social and Amazon Bedrock AgentCore:

  • Proactive notice. When the shipping system reports a package as ready at a pickup point, the customer receives an approved WhatsApp template: "your package is ready, you have N days".
  • Tracking. "Where is my package?" is answered with status, pickup point, address, opening hours and deadline, plus a WhatsApp location message with the pickup point's map pin.
  • Self-service resolution. Replying to the notice, the customer can extend the deadline, change the pickup point, reschedule the delivery or return the package. Every change is previewed and confirmed before it is written.
  • Sending (demo). "I want to send a package" registers a package in transit; "I dropped it off" marks it ready and starts the deadline.

The agent only sees the packages linked to the phone it is talking to, confirms every change before writing it, and never invents a location: the map pin is built by the server from the database. All data is synthetic.

How we built it

  • AWS End User Messaging Social handles WhatsApp end to end: approved templates for business-initiated messages, text and location messages for replies, and every inbound webhook (messages and delivery statuses) published to Amazon SNS.
  • Amazon Bedrock AgentCore Runtime runs the agent with direct code deploy (Node 22): an esbuild bundle uploaded as a CDK asset, no container, one isolated session per conversation. The agent is written with Strands Agents (TypeScript) on Claude through a Bedrock inference profile, with tools over DynamoDB.
  • AWS Lambda does the rest: an idempotent status hook behind Amazon API Gateway (the shipping system's entry point) and an inbound router that parses the webhook, deduplicates each message with a lease, opens the 24-hour customer-service window, calls the agent and replies.
  • Amazon DynamoDB holds packages, pickup points, conversations and the inbound-message dedupe table.
  • AWS CDK (TypeScript) defines the whole thing as one stack, with least-privilege IAM, cdk-nag on every build, JSON logs with masked phone numbers, a metric and an alarm for every failure path, and GitHub Actions deploying through OIDC.

Challenges we ran into

  • Deploying an agent without containers. AgentCore Runtime's direct code deploy let us ship a single bundle as a CDK asset, but the SDK loaded two modules through createRequire, which esbuild does not follow. The bundle worked on a laptop and would have failed in the runtime. A smoke test that loads the bundle from a folder without node_modules now runs in CI.
  • WhatsApp's rules. Business-initiated messages must be templates, free text only works inside the 24-hour window, the destination needs the + while Meta's own webhook sends it without, and the IAM ARN of a phone number does not repeat the ID's prefix. Each one is a test now.
  • Exactly once, never twice. SNS and Lambda retry; a reply that goes out twice, or a message that is silently dropped, is worse than a slow one. A lease per WhatsApp message id, explicit handling of "done" versus "still in progress", and the rule that nothing is retried after the first send took most of the design time.
  • Keeping the model honest. We let the model return only a pickup-point id and build the location on the server, and bound every tool to the phone of the session, so prompt injection cannot reach another customer's package or produce a fake pin.

Accomplishments that we're proud of

  • The full loop works on a real WhatsApp number: template on arrival, tracking with a map pin, and the four resolution actions, each confirmed in the chat and verified in DynamoDB.
  • A production-shaped delivery for a hackathon: one CDK stack, least-privilege IAM checked by tests, cdk-nag, alarms on every failure path, CI on every branch and deploy from main through OIDC.
  • Every pull request got an adversarial review by a second model before merging; the findings (the bundle that would not load, the wrong IAM ARN, the duplicate-notice bug) were real and fixed before anyone deployed.

What we learned

  • End User Messaging Social removes the Meta app and webhook plumbing entirely: templates, sends and inbound events are all AWS APIs and IAM.
  • AgentCore Runtime with direct code deploy fits a TypeScript team well, as long as the bundle is tested the way the runtime loads it.
  • With current Claude models, asking for JSON in the prompt and parsing tolerantly beats forcing structured output; forced tool choice and non-default sampling are rejected.

What's next

  • Strict ordering of messages from the same customer (an SQS FIFO queue per phone in front of the Lambda).
  • Per-phone rate limiting and a VPC-attached runtime for production.
  • Connecting the status hook to HOP's real shipping events, and retiring the demo-only "send a package" tools.
  • The second half of the original brief: an operations assistant for the pickup points themselves (billing, holidays, marketplace packages).

Built With

Share this project:

Updates

Submission history