Source: https://cli.nylas.com/guides/order-status-reply-agent-account

# Build an Order-Status Reply Agent

"Where's my order?" is the single most common support email an online store gets, and it almost always has the same shape: an order number and a question. An agent account turns that into an automated reply. This guide builds an order-status agent that reads the request, extracts the order ID, looks it up in your system, and replies in-thread — with the one control that matters most: it answers only the verified owner of the order, so the agent can never leak one customer's shipping details to another.

Written by [Hazik](https://cli.nylas.com/authors/hazik) Director of Product Management

Updated June 8, 2026

> **TL;DR:** An order-status agent reads requests with `nylas email list --unread --json`, a model extracts the order ID, your system looks it up and confirms the requester owns it, and the agent replies in-thread with `nylas email send --reply-to`. It answers the owner and no one else.

## What is an order-status reply agent?

An order-status reply agent answers the "where's my order?" email automatically. It reads a customer's request, pulls out the order number, looks the order up in your fulfillment system, and replies with the current status — all on a dedicated agent account that owns the inbox and the conversation. It's a bridge between an email inbox and your order data, turning a repetitive support question into a one-message round trip.

What makes this different from a general support agent is the shape of the work. Every request is the same template — an order ID plus "where is it?" — so the agent doesn't need open-ended reasoning; it needs reliable extraction and a lookup. The hard part isn't the reply. It's making sure the agent only tells you about an order that's actually yours.

Order-status flow: read the request, extract the order ID, look it up and verify ownership, reply in-threadRead requestemail listExtract IDmodel parseLook upverify ownerReplyin-thread

## Why run it on an agent account?

An order-status agent should own a dedicated inbox like `orders@` — the address customers already write to about shipping. On an agent account, that inbox is the agent's own, every reply goes out from the same identity, and the agent can be paused or retired by managing one grant. Customers see a single, consistent point of contact for order questions.

The agent account also keeps the bridge contained. The agent reads untrusted customer mail and replies, but it can only ever reply to senders and is bounded by the workspace rules — it can't be talked into emailing order details somewhere else. Containment lives outside the agent's decision loop, which matters when the inbox is full of strangers asking about orders.

## How do I set up the orders inbox?

Create the orders identity with one command. The [`nylas agent account create`](https://cli.nylas.com/docs/commands/agent-account-create) call returns a working inbox in under 2 seconds. Point your store's order-support address at it:

```bash
nylas agent account create orders@yourapp.nylas.email
```

Every "where's my order?" email now lands in the agent's inbox, and every reply leaves from the same address — the customer sees one continuous thread about their order, not a relay between systems.

## How does the agent extract the order ID?

The agent reads new requests with `nylas email list --unread --json` and a model extracts the order ID from the free-text message. Order numbers come in noisy — "order #4821", "my order 4821", "ref 4821" — so extraction is exactly the messy parsing a model handles well. Project the sender, message ID, and body the model needs with [jq](https://jqlang.github.io/jq/manual/):

```bash
nylas email list --unread --json \
  | jq '.[] | {id, from: .from[0].email, subject, snippet}'
```

The model returns a structured order ID; your code does everything after. Keep the extraction narrow — pull the ID, nothing else — so a request that doesn't contain an order number is routed to a human instead of guessing. The sender's email and the extracted ID are the two facts the next step needs.

## How does the agent look up the order and verify ownership?

The lookup is the security-critical step, and it has two parts: find the order, then confirm the requester owns it. Your system returns the order's status and the email it belongs to; the agent compares that to the sender. If they don't match, the agent does not reveal the status — it hands off to a human instead. This is the control that stops the agent from leaking one customer's order to another who guessed an order number:

```bash
# lookup_order is your fulfillment API/DB call; it returns the order's
# status AND the email the order belongs to.
order=$(lookup_order "$order_id")
owner=$(echo "$order" | jq -re '.owner_email')   # fail loud if absent
status=$(echo "$order" | jq -re '.status')       # never send a null status

# Authorization: only answer the verified owner of the order
if [ "$owner" != "$sender" ]; then
  echo "owner mismatch for $order_id — escalating" >&2
  exit 2
fi
```

Order numbers are guessable — sequential IDs especially — so replying based on the number alone is a [broken-object-level-authorization](https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/) flaw: anyone who guesses an ID gets someone else's shipping details. Matching the order's owner against the sender before replying is the fix, and it belongs in deterministic code, not the model's judgment.

## How does the agent reply with the status?

Once ownership is verified, the agent replies in-thread with `nylas email send --reply-to`, using the original message ID so the answer lands on the same conversation. The recipient is the sender — the verified owner — and never an address derived from the order itself:

```bash
nylas email send \
  --to "$sender" \
  --reply-to "$msg_id" \
  --subject "Re: your order $order_id" \
  --body "Your order $order_id is: $status. Track it at the link in your shipping email."
```

Replying to `$sender` — the address that wrote in and passed the ownership check — keeps the status going only to the person entitled to it. After the reply, mark the request read so the next run doesn't answer it twice.

## How do I run the loop?

The full loop is a cron job: read the first unread request the CLI returns, extract the ID, look it up, verify ownership, reply, and mark it read. The model does the extraction; everything else is deterministic and fails loud under `set -euo pipefail`:

```bash
#!/usr/bin/env bash
set -euo pipefail

req=$(nylas email list --unread --json | jq -r '.[0] // empty')
[ -z "$req" ] && exit 0
msg_id=$(echo "$req" | jq -re '.id')
sender=$(echo "$req" | jq -re '.from[0].email')
body=$(echo "$req" | jq -r '.snippet')

order_id=$(extract_order_id "$body")   # your model call
[ -z "$order_id" ] && { echo "no order id — escalate" >&2; exit 1; }

order=$(lookup_order "$order_id")       # your fulfillment lookup
owner=$(echo "$order" | jq -re '.owner_email') \
  || { echo "lookup missing owner for $order_id — escalating" >&2; exit 1; }
status=$(echo "$order" | jq -re '.status') \
  || { echo "lookup missing status for $order_id — escalating" >&2; exit 1; }

# Authorization gate: only the verified owner gets the status
if [ "$owner" != "$sender" ]; then
  echo "owner mismatch for $order_id — escalating to a human" >&2
  exit 2
fi

nylas email send --to "$sender" --reply-to "$msg_id" \
  --subject "Re: your order $order_id" \
  --body "Your order $order_id is: $status."

nylas email mark read "$msg_id"
```

The ownership check sits between the lookup and the reply, so a mismatch exits before any status is sent. Marking the message read only on a successful reply means a request that escalated stays unread for a human to pick up. Add audit logging around the lookup and reply — the [audit guide](https://cli.nylas.com/guides/audit-ai-agent-activity) covers structured logging for agent actions.

## Keeping the agent safe

Two controls carry the safety of an order-status agent. The first is the ownership check above — the deterministic gate that keeps order details flowing only to the customer they belong to. The second is the agent account's workspace: an outbound rule and a policy send cap bound where and how much the agent can email, so even a crafted request can't turn the agent into a channel for exfiltrating order data:

```bash
# The agent only replies to senders — block a flagged exfil domain
nylas agent rule create \
  --name "Block flagged order-agent outbound" \
  --trigger outbound \
  --condition recipient.domain,is,exfil.example \
  --action block
```

Because the rule lives on the workspace, the agent can't prompt its way past it. Keep the reply body minimal — a status, not a full order record — so even a verified reply doesn't over-share. The full containment pattern is in [Stop Your AI Agent From Going Rogue](https://cli.nylas.com/guides/stop-ai-agent-going-rogue), and the rule reference is in [Agent Rules and Policies](https://cli.nylas.com/guides/agent-rules-and-policies).

## Next steps

- [Build an AI Customer-Support Inbox](https://cli.nylas.com/guides/ai-support-inbox-agent-account) — the general-purpose triage agent this specializes
- [Getting Started with Agent Accounts](https://cli.nylas.com/guides/getting-started-agent-accounts) — the architecture behind the orders inbox
- [Trigger Agents on Inbound Email with Webhooks](https://cli.nylas.com/guides/agent-account-webhooks) — answer order requests in real time instead of polling
- [Agent Rules and Policies](https://cli.nylas.com/guides/agent-rules-and-policies) — the outbound rules and send caps that contain the agent
- [Stop Your AI Agent From Going Rogue](https://cli.nylas.com/guides/stop-ai-agent-going-rogue) — containment beyond the ownership check
- [Full command reference](https://cli.nylas.com/docs/commands) — every `nylas email` and `nylas agent` flag
- [Nylas v3 API documentation](https://developer.nylas.com/) — the messages API behind these commands
