Source: https://cli.nylas.com/guides/feedback-request-agent-account

# Build a Feedback-Request Email Agent

You just completed an event or shipped an order. In 24 hours, half your customers will have moved on to something else. A feedback agent on a dedicated inbox sends the ask while the experience is still fresh, reads every reply, classifies each one, routes negative replies to a human within minutes, and tags positive ones for the testimonials queue. This guide builds that loop from scratch using Nylas CLI agent accounts.

Written by [Prem Keshari](https://cli.nylas.com/authors/prem-keshari) Senior SRE

Updated June 9, 2026

> **TL;DR:** A feedback agent on a dedicated inbox sends requests with `nylas email send`, reads replies with `nylas email list --unread --json`, classifies each reply as `positive`, `negative`, or `feature_request`, and routes negatives to a human — all on one agent account.

## What is a feedback-request agent?

A feedback-request agent is a program that closes the customer loop automatically. It sends a feedback email after a trigger event — a completed purchase, a finished onboarding session, an event attendance — then reads every reply, passes each one to a model for sentiment classification, and acts on the result: positive replies get tagged for the testimonials queue, feature requests go to the product backlog, and negative replies are escalated to a human within minutes. The entire loop runs on a single dedicated agent account, not on any person's inbox.

Timing is the key variable. According to [Mailchimp's email marketing benchmarks](https://www.mailchimp.com/resources/email-marketing-benchmarks/), transactional emails sent within an hour of the trigger event see open rates above 35%. Feedback requests sent 3 days later drop into single digits. An agent running on a 1-minute cron closes that gap by sending the ask while the experience is still in memory.

Feedback-request agent flow: send the request, read the reply, classify it, then route positive replies for testimonials, feature requests to the roadmap queue, and negatives to a human.Send requestemail sendRead replyemail listClassifymodel callRouteemail send / tagPositivetag testimonialFeature req.tag roadmapNegative→ human fast

## Why run a feedback loop on an agent account?

A feedback agent should own a dedicated inbox, not share a human's mailbox. On an agent account, the feedback address belongs to the agent, every classified reply stays in the agent's grant, and the send-and-read loop runs without a live session in the path. You suspend the whole pipeline by pausing one grant — no revoking user credentials, no hunting for OAuth tokens scattered across scripts.

Isolation also gives you a clean audit trail. Every message in the feedback inbox is either a request the agent sent or a reply the agent received — there's no personal email mixed in to complicate data retention or GDPR deletion requests. When a customer asks you to delete their data, you delete one grant and the inbox is gone in under a second. Retention periods, export formats, and access logs all scope to the agent account rather than a shared mailbox where 4 or more people have had access over 3 years.

A feedback inbox also connects all three legs of Simon Willison's [lethal trifecta](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/): private data (the replies), untrusted content (customer text that may contain injection attempts), and external communication (the agent's send permission). An outbound rule on the agent account means the agent can't prompt its way past a rule it doesn't control — containment lives outside the agent's decision loop, on the workspace, not in the system prompt.

## How do I create the feedback inbox?

The [`nylas agent account create`](https://cli.nylas.com/docs/commands/agent-account-create) command provisions a managed inbox in under 2 seconds. The inbox is live immediately — no MX record changes, no SMTP daemon setup, no OAuth consent screen. Export the grant ID so every subsequent command inherits the right identity:

```bash
nylas agent account create feedback@yourapp.nylas.email
export NYLAS_GRANT_ID="$(nylas agent account get feedback@yourapp.nylas.email --quiet)"
```

Point your post-purchase webhook, event-attendance confirmation, or support ticket close at this address as the reply-to. Any system that can trigger a notification can now feed the feedback loop. For a deeper look at the workspace architecture and grant lifecycle, see the [getting started with agent accounts guide](https://cli.nylas.com/guides/getting-started-agent-accounts).

The inbox address is a real email address — customers reply directly, not through a survey link. That's deliberate. Survey links add a click and a page load between the customer's intent and the feedback you receive. A direct reply removes both friction points, which is why plain-text feedback emails outperform survey forms for qualitative signal. The tradeoff is that you need a model to parse free text instead of a form parser to handle structured fields — but the classification step is cheap and fast.

## How does the agent send the feedback request?

The [`nylas email send`](https://cli.nylas.com/docs/commands/email-send) command sends a single feedback request in under 500ms. The body asks one question — a short NPS-style prompt works better than a 10-question form because it fits in a reply rather than requiring a link click. Keep the ask to a single sentence so the model has a clean surface to classify:

```bash
nylas email send \
  --to "customer@example.com" \
  --subject "Quick question about your experience" \
  --body "Hi — how did your recent experience go? One line is enough. We read every reply."
```

In a real pipeline, the customer email, first name, and event description come from your trigger payload — a webhook body, a database row, or a CSV of attendees. Wrap the command in a loop that reads from stdin or a file: 100 requests send in under 60 seconds on a standard connection. Skip customers who already received a request in the last 30 days by maintaining a simple sent log; repeat sends within 30 days drop reply rates significantly and frustrate customers who already responded.

```bash
# Send to a list of customers from a CSV: email,name,context
while IFS=, read -r email name context; do
  nylas email send \
    --to "$email" \
    --subject "Quick question about your $context" \
    --body "Hi $name — how did your recent $context go? One line is enough. We read every reply."
done < customers.csv
```

## How does the agent read and classify replies?

The agent polls for unread replies with [`nylas email list --unread --json`](https://cli.nylas.com/docs/commands/email-list). Projecting only the fields the model needs — sender, subject, and body snippet — keeps the context window small and the classification fast. A structured output contract works better than free-text: ask the model to return exactly one of three categories, nothing else:

```bash
# Read unread replies, project only classification-relevant fields
nylas email list --unread --json \
  | jq '.[] | {id, from: .from[0].email, subject, snippet}'
```

Pass each reply's snippet to your model and request a JSON response with two fields: `category` (`positive`, `negative`, or `feature_request`) and `summary` (one sentence). The routing thresholds belong in code — never let the model decide what happens to a negative reply. Classifying 100 replies with a small model costs under $0.002 at current API rates. At 35% open rate and 10% reply rate on a 1,000-message send, that's roughly 35 replies to classify per batch.

Keep extraction and classification as separate concerns, or combine them in one structured output call. If you combine them, the model returns `category`, `summary`, and optionally a `confidence` score (0–100). Route any reply with confidence below 70 to the negative escalation path rather than discarding it — a low-confidence classification is almost always ambiguous or negative, and it's safer to over-escalate than to miss a frustrated customer.

## How does the agent route each category?

Routing is a deterministic `case` statement — the model classifies, the code acts. Negative replies go to a human via [`nylas email send`](https://cli.nylas.com/docs/commands/email-send) within the same run, so a 1-minute cron means a human sees a negative reply in under 5 minutes. Positive replies are saved to the contacts directory with a testimonial tag so the marketing team can pull them later. Feature requests go to the product inbox with the full summary:

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

# Read one unread reply per run; process in batches by removing the head filter
reply=$(nylas email list --unread --json | jq -r '.[0] // empty')
[ -z "$reply" ] && exit 0

id=$(echo "$reply" | jq -re '.id')
from=$(echo "$reply" | jq -re '.from[0].email')
snippet=$(echo "$reply" | jq -r '.snippet')

# classify_reply is your LLM call; returns JSON: {category, summary}
result=$(classify_reply "$snippet")
category=$(echo "$result" | jq -r '.category // "negative"')
summary=$(echo "$result" | jq -r '.summary // "reply received"')

case "$category" in
  positive)
    nylas contacts create --email "$from" --notes "Testimonial candidate: $summary"
    echo "positive from $from — tagged for testimonials" ;;
  feature_request)
    nylas email send \
      --to product@yourcompany.com \
      --subject "Feature request from $from" \
      --body "$summary"
    echo "feature request from $from — forwarded to product" ;;
  negative)
    nylas email send \
      --to cx-team@yourcompany.com \
      --subject "Negative feedback — reply needed: $from" \
      --body "Customer: $from\nSummary: $summary\n\nReply within the hour."
    echo "negative from $from — routed to CX team" ;;
  *)
    echo "unknown category '$category' from $from — defaulting to negative escalation" >&2
    nylas email send \
      --to cx-team@yourcompany.com \
      --subject "Unclassified reply — review: $from" \
      --body "$snippet" ;;
esac

# Mark the reply as read so the next run doesn't reprocess it
nylas email mark read "$id"
```

The unknown-category arm defaults to escalation rather than silently dropping the reply — model drift surfaces immediately in your CX team's inbox instead of disappearing. The `set -euo pipefail` header ensures any failed CLI call aborts the run rather than continuing with a half-processed reply. For the full human-in-the-loop escalation pattern, see [Build a Human-in-the-Loop Email Agent](https://cli.nylas.com/guides/build-human-in-loop-email-agent).

## How does the agent draft a follow-up for positive replies?

Positive replies don't just go into a contacts note — a well-timed thank-you reinforces the sentiment and increases the chance the customer shares the experience publicly. The [`nylas email drafts create`](https://cli.nylas.com/docs/commands/email-drafts-create) command saves a draft to the agent's outbox for a human to review and send. This keeps the model in the classification role, not the outbound role — a human touches every reply before it goes out, which is the right boundary for customer-facing thank-you messages:

```bash
# After the positive branch: create a draft thank-you for human review
nylas email drafts create \
  --to "$from" \
  --subject "Thank you for your feedback" \
  --body "Thank you for taking a moment to share your experience. We really appreciate it — and we'd love to share your story with others if you're open to it. Reply to let us know."
```

Saving a draft rather than sending directly means a person reviews the thank-you before it leaves. If the model misclassified the reply — a sarcastic positive, for example — the CX team catches it before the outbound goes out. Draft review adds under 30 seconds per positive reply, which is a reasonable cost for customer-facing messages.

## How do I keep the feedback agent contained?

A feedback inbox is untrusted input — a customer reply can contain adversarial text designed to redirect the agent's outbound behavior. Workspace-level rules stop that before the model ever sees the routing decision. An outbound rule that blocks non-company recipient domains means the agent can't be steered into emailing an attacker's address, no matter what the reply body says:

```bash
# Block outbound to any domain that isn't your company
nylas agent rule create \
  --name "Feedback agent: block external 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. Add an `archive` rule for inbound messages that look like vendor pitches, so the classification loop only sees genuine customer replies. The [agent rules and policies guide](https://cli.nylas.com/guides/agent-rules-and-policies) documents every trigger and action, and [Stop Your AI Agent From Going Rogue](https://cli.nylas.com/guides/stop-ai-agent-going-rogue) covers the full containment pattern for agents that touch untrusted inbound content.

One more guard: cap the number of outbound sends per cron run in code. A classification bug that returns `negative` on every reply could flood the CX team's inbox. Processing one reply per run (as the script above does) is the simplest cap. Scale up only once you've verified the classification accuracy on a real sample — a 95% accuracy rate on 35 replies means roughly 2 misrouted messages per batch, which is acceptable; at 80% it's 7.

## How do I verify the pipeline works?

Send a test feedback request to an address you control, reply with known sentiment, and confirm the routing matches. Running this locally takes under 2 minutes and catches classification bugs before you send to real customers. The test cycle below covers all three categories in a single pass:

```bash
# 1. Send a test request to yourself
nylas email send \
  --to you@yourcompany.com \
  --subject "Test feedback request" \
  --body "How did your experience go? One line is enough."

# 2. Reply from your test account with a positive, negative, and feature request
#    (do this manually or with a second agent account)

# 3. Run the classify-and-route script and check the output
bash feedback-loop.sh
# Expected: 3 runs, one per category, each marking the reply read

# 4. Confirm the contacts directory got the positive tag
nylas email list --json | jq 'length'   # should be 0 unread after all 3 runs
```

Tested on Nylas CLI 3.1.16 with a Nylas managed provider on 2026-06-09. Provider-side behavior for other backends is described from documented provider behavior — verify locally before deploying to production. The [lead-capture agent guide](https://cli.nylas.com/guides/lead-capture-agent-account) shows the same read-classify-route loop applied to inbound sales leads, which is a useful cross-check if the classification logic looks unfamiliar.

## Next steps

- [Getting Started with Agent Accounts](https://cli.nylas.com/guides/getting-started-agent-accounts) — the architecture behind the feedback inbox and its workspace
- [Build a Human-in-the-Loop Email Agent](https://cli.nylas.com/guides/build-human-in-loop-email-agent) — require a person to approve every escalation before it goes out
- [Build a Lead-Capture Agent](https://cli.nylas.com/guides/lead-capture-agent-account) — the same read-classify-route pattern applied to inbound sales leads
- [Agent Rules and Policies](https://cli.nylas.com/guides/agent-rules-and-policies) — outbound guardrails and send caps for the feedback agent
- [Stop Your AI Agent From Going Rogue](https://cli.nylas.com/guides/stop-ai-agent-going-rogue) — containment patterns for agents that process untrusted inbound content
- [Full command reference](https://cli.nylas.com/docs/commands) — every `nylas email`, `nylas contacts`, and `nylas agent` flag
- [Nylas v3 API documentation](https://developer.nylas.com/) — the API surface behind these commands
