Shop email reply drafter for returns and shipping
Python bot that reads customer emails, sorts returns and shipping questions, and saves draft replies for human review without sending anything.
Built 1 time, downloaded 0 times.
Install from your terminal
Run this in your project folder. It signs you in, shows what changes and asks before it overwrites a file.
npx claude-lab add shop-email-reply-drafter-for-returns-and-shipping-43f85dCLAUDE.md# Shop reply drafter
## Purpose
- This project reads customer emails from `inbox/` and writes draft replies to `drafts/` for a human to approve.
- The bot never sends email.
- Replies cover returns and shipping questions. Anything unclear goes to `needs_human/`.
## Stack
- Python 3.12, with a virtual environment in `.venv`.
- Packages: `anthropic` SDK and `pytest`.
- This stack was chosen as a default because the owner did not name one. If the owner changes it, update this section.
## Commands
- Create the environment: `python -m venv .venv`
- Activate it (Mac or Linux): `source .venv/bin/activate`
- Install packages: `pip install -r requirements.txt`
- Run tests: `python -m pytest -q`
- Dry run with sample emails (no network): `python -m drafter.run --sample --dry-run`
- Real run (needs an API key): `python -m drafter.run --inbox inbox/`
## Hard rules
- Never add code that sends email, issues refunds, or changes orders.
- Never commit real customer emails, `.env`, `inbox/`, `drafts/` or `needs_human/`.
- Never hardcode an API key. Read it from the `ANTHROPIC_API_KEY` environment variable.
- Fixtures and test data must be invented.
## Policy and reply rules
- Policy text lives in `policies/returns.md` and `policies/shipping.md`.
- A reply may only state facts found in those files.
- If unsure, set the category to `needs_human`.
- Reply style: friendly, under 150 words.
- Do not promise refunds or dates unless the policy file states them.
- Each draft should name the policy section it used.
## Workflow
- Work on one phase from `PLAN.md` at a time.
- Run `/clear` before each phase.
- Use plan mode for the design of Phase 2 and Phase 3.
- Run the `verify` skill before saying a phase is done.
- Give the owner a plain-language summary of each step, because the owner is new to programming.
## Review
- Before accepting any change, check it against the list in `REVIEW.md`.
@REVIEW.md
## Glossary
- `inbox/`: raw sample or real emails.
- `drafts/`: draft replies written by the bot.
- `needs_human/`: queue for emails a person must handle.
.claude/settings.json{
"model": "sonnet",
"permissions": {
"defaultMode": "default",
"allow": [
"Bash(python -m pytest:*)",
"Bash(python -m drafter.run --sample:*)",
"Bash(pip install -r requirements.txt)",
"Bash(git status)",
"Bash(git diff:*)"
],
"deny": [
"Read(./.env)",
"Read(./inbox/**)",
"Read(./drafts/**)",
"Read(./needs_human/**)",
"Bash(rm:*)",
"Bash(git push:*)",
"Bash(curl:*)",
"Bash(wget:*)"
]
}
}
.claude/skills/verify/SKILL.md---
name: verify
description: Run after every code change and before saying a phase is done, to prove the shop reply drafter still tests, dry-runs, and keeps secrets and customer data out of git.
---
# Verify
Run these steps in order from the project root. Do not skip a step. If a step fails, stop, report it, and fix it before continuing.
## Step 1: Set up the environment
1. Activate the virtual environment:
- Mac or Linux: `source .venv/bin/activate`
- Windows: `.venv\Scripts\activate`
2. Install pinned dependencies: `pip install -r requirements.txt`
If `.venv` does not exist, tell the owner to create it with `python -m venv .venv` and stop.
## Step 2: Run the tests
Run `python -m pytest -q`.
Record the pass and fail counts from the last line of output. Any failure means FAIL.
## Step 3: Run the dry run
Run `python -m drafter.run --sample --dry-run`.
Check both of these:
- The exit code is 0.
- The output reports zero network calls. If it does not report a count, FAIL and say the dry run must print its network call count.
## Step 4: Scan for secrets
Search tracked files for API key literals. Exclude `.venv`, `.git`, and `.env.example`:
```
grep -rn --exclude-dir=.venv --exclude-dir=.git --exclude=.env.example -e "sk-" -e "ANTHROPIC_API_KEY=" .
```
Any match means FAIL. Report the file and line number, but never print the key value itself.
Also confirm `.env` is not tracked: `git ls-files .env` must print nothing.
## Step 5: Check ignore rules
Confirm `.gitignore` lists `inbox/` and `drafts/`:
```
grep -qx "inbox/" .gitignore && grep -qx "drafts/" .gitignore
```
Exit code 0 means PASS. Anything else means FAIL.
## Output
Give the owner a short table:
| Step | Result | Detail |
|------|--------|--------|
| 1 Setup | PASS or FAIL | |
| 2 Tests | PASS or FAIL | passed/failed counts |
| 3 Dry run | PASS or FAIL | exit code, network calls |
| 4 Secrets | PASS or FAIL | file and line if found |
| 5 Ignore rules | PASS or FAIL | |
For any FAIL, add the failing command and the first line of its error output. Then give the overall result: PASS only if every step passed.
Explain each result in plain words, because the owner is new to programming.
.gitignore# Secrets
.env
.claude/settings.local.json
# Real customer data and bot output
inbox/
drafts/
needs_human/
# Python generated files
.venv/
__pycache__/
.pytest_cache/
*.log
INSTALL.md# Install and first run
This guide takes you from an empty computer to a first dry run. Each step says why it matters. You do not need programming experience, but you will type commands into a terminal.
## What you are building
A small program that reads customer emails, sorts them into returns or shipping questions, and saves a draft reply for you to check. It never sends email. You stay in control of every reply.
## Step 1: Install Python 3.12
1. Go to python.org and download Python 3.12.
2. On Windows, tick "Add python.exe to PATH" before you click Install.
3. Open a terminal and run:
```
python --version
```
You should see `Python 3.12` followed by a version number. The bot needs this version.
## Step 2: Install Claude Code and log in
1. Follow the official install guide on Anthropic's website.
2. Run `claude` once and log in when asked.
Claude Code is the assistant that writes and runs code with you. It reads CLAUDE.md in this project for its rules.
Permission modes: with no settings, Claude Code starts in auto mode, where it makes changes with fewer prompts. This project's settings file switches the default to ask first, so you approve each edit and command. Keep it that way while you learn.
## Step 3: Create the project folder
```
mkdir shop-reply-bot
cd shop-reply-bot
git init
```
`git init` starts a record of every change. If something goes wrong, you can see what changed and undo it.
## Step 4: Copy in the project files
Copy these into the `shop-reply-bot` folder:
- CLAUDE.md
- SPEC.md
- PLAN.md
- REVIEW.md
- .gitignore
- the `.claude` folder
Copy `.gitignore` before anything else gets committed. It stops secrets and customer data from entering git.
## Step 5: Create and activate a virtual environment
```
python -m venv .venv
```
Activate it:
- Windows: `.venv\Scripts\activate`
- Mac or Linux: `source .venv/bin/activate`
A virtual environment is a private set of packages for this project only. When it is active, your prompt starts with `(.venv)`. Activate it again each time you open a new terminal.
## Step 6: Get an API key and set a spending limit
1. Sign in to the Anthropic console and create an API key.
2. In the billing settings, set a monthly spending limit. Start small, for example $10, while you learn.
Cost in plain words: you pay for what the AI reads and writes. The price depends on email length and the model. The default model here is Sonnet, which is low cost per email. The sample dry run costs nothing because it makes no network calls. The spending limit caps your total so there are no surprises.
## Step 7: Save your key in a .env file
Create a file named exactly `.env` in the project folder. Put one line in it:
```
ANTHROPIC_API_KEY=YOUR_API_KEY
```
Replace `YOUR_API_KEY` with your real key. Do not add spaces or quotes. On Windows, make sure the file is not saved as `.env.txt`.
Rules for this file:
- Git ignores `.env`, so it will not be committed.
- Never paste the key into CLAUDE.md, a chat message, or any file you might share.
## Step 8: Install the two plugins
Start Claude Code inside the project folder:
```
claude
```
Then type these two commands in the chat:
```
/plugin install security-guidance@claude-plugins-official
/plugin install code-review@claude-plugins-official
```
- security-guidance flags risky code, such as hardcoded keys.
- code-review gives a second look at each phase before you accept it.
## Step 9: Start Phase 1
Paste this kickoff prompt into Claude Code:
```
/goal Phase 1 of PLAN.md is complete when all of these are true.
Folders drafter/, tests/fixtures/, policies/, inbox/, drafts/ and
needs_human/ exist. requirements.txt pins anthropic and pytest.
tests/fixtures/ holds 20 invented customer emails plus expected
categories in a JSON file. policies/returns.md and policies/shipping.md
contain placeholder text marked PLACEHOLDER. python -m pytest -q passes,
and python -m drafter.run --sample --dry-run exits with code 0 and makes
no network calls. The verify skill reports PASS.
Constraints: no code that sends email or calls a refund or order API;
no real key or customer data in any file; only the anthropic and pytest
packages; keep each file small and readable.
Stop after 25 turns and summarize what is blocked and what you tried.
```
`/goal` sets a finish line. Claude keeps working until a small, fast model judges that the condition is met, then it stops. To stop early at any time, type `/goal clear`.
Watch what Claude asks to do. Approve edits and commands you understand. If something is unclear, ask Claude to explain it before you approve.
## Step 10: Run the dry run
When Phase 1 finishes, install the packages and run the sample:
```
pip install -r requirements.txt
python -m drafter.run --sample --dry-run
```
What this means:
- The sample uses 20 invented emails, not real customer mail.
- `--dry-run` means no network calls and no API charges.
- Exit code 0 means the program finished without errors.
After a dry run, look in `drafts/` and `needs_human/`. Each draft file shows:
- **category**: return_request, shipping_status, tracking_missing, damaged_item or other
- **confidence**: high, medium or low
- **summary**: what the customer asked, in one or two lines
- **draft reply**: the text you would send after you check it
Files in `needs_human/` are emails the bot could not sort with confidence, or emails where an error occurred. A person handles those.
## Step 11: Run the remaining phases
For each phase after Phase 1:
1. Type `/clear` in Claude Code to start with a fresh context.
2. Open PLAN.md and copy that phase's `/goal` prompt.
3. Paste it and let Claude work.
Phases 2 and 3 are design-heavy. PLAN.md suggests plan mode for them. Plan mode lets Claude propose a design before it edits files.
Each prompt has a turn limit. If Claude stops and says what is blocked, read the summary and decide the next step with Claude.
After Phase 5, run the check:
```
/verify
```
The verify skill runs every check and prints a PASS or FAIL table.
## Step 12: Privacy before you use real email
Read this section before you process a single real customer email.
- Use only the invented sample emails until the redaction phase (Phase 4) is done and its tests pass.
- Keep `inbox/` on your own computer. It is git-ignored, so it is never committed.
- Do not share `drafts/` or `needs_human/` publicly. They can contain customer details.
- Check your email provider's terms and your customer agreement or privacy policy. Some agreements limit sending customer messages to outside AI services.
## Troubleshooting
**"command not found" or "python is not recognized"**
Python is not on your PATH. Reinstall Python and tick the PATH box on Windows. On Mac, try `python3 --version`. Close and reopen the terminal after installing.
**"missing API key" or "API key not set"**
Check that `.env` is in the project folder, not a subfolder. The line must read `ANTHROPIC_API_KEY=` followed by your key, with no spaces. Check that the file name is not `.env.txt`. Then ask Claude: "The run says the API key is missing. Check how the code reads ANTHROPIC_API_KEY and tell me what to fix."
**Tests are failing**
Run `python -m pytest -q`. Copy the full error text, then paste it into Claude with this request: "Read this error, find the cause, fix it and explain the change in plain words."
**Claude asks permission over and over**
That is the project setting working as intended. Approve what you understand. If you are unsure what a command does, deny it and ask Claude to explain.
**How to ask Claude to read an error**
Copy the whole error, including the line that says what command you ran. Paste it into Claude Code and say what you were trying to do. Never paste your API key.
PLAN.md# Build plan
Run `/clear` before each phase so each one starts with a clean context.
Use plan mode for the design work in Phase 2 and Phase 3. Switch back to normal mode to write the code.
Each phase below ends in a `/goal` prompt. A `/goal` runs until its end state is met, or until the stop rule triggers.
## Stop rule (all phases)
Stop after 30 turns. Then summarize what is done, what is blocked, and what you tried.
To stop a goal early, run `/goal clear`.
---
## Phase 1: Scaffold
**Goal:** Create the folder layout, dependencies, sample emails and placeholder policies. Nothing calls the API yet.
**Files likely touched:**
- `drafter/` (empty package with `__init__.py` and a stub `run.py`)
- `tests/fixtures/` (20 invented emails and an expected-categories JSON file)
- `policies/returns.md`, `policies/shipping.md` (placeholder text marked PLACEHOLDER)
- `inbox/`, `drafts/`, `needs_human/` (empty, with `.gitkeep`)
- `requirements.txt` (pins `anthropic` and `pytest`)
**`/goal` prompt:**
```
/goal Phase 1 of PLAN.md is complete when all of these are true: folders drafter/, tests/fixtures/, policies/, inbox/, drafts/ and needs_human/ exist. requirements.txt pins anthropic and pytest. tests/fixtures/ holds 20 invented emails (returns, shipping status, missing tracking, damaged item, unrelated) and an expected-categories JSON file. policies/returns.md and policies/shipping.md contain placeholder text marked PLACEHOLDER. python -m pytest -q passes. python -m drafter.run --sample --dry-run exits 0 with no network calls. The verify skill reports PASS. Constraints: no code that sends email or calls a refund or order API; no real keys or customer data in files; only the anthropic and pytest packages; keep files small and readable. Show the output of the verify skill and both commands. Stop after 30 turns and summarize what is blocked.
```
**Stop rule:** 30 turns, then summarize.
---
## Phase 2: Classify
**Goal:** One function sends one email to Claude and returns JSON with `category`, `confidence` and `summary`. The JSON is checked against a schema.
**Design note:** Use plan mode first. Consider `/effort high` for this design step only, then drop back to the default.
**Files likely touched:**
- `drafter/classify.py`
- `drafter/schema.py`
- `drafter/api.py` (thin wrapper around the Anthropic client, so tests can mock it)
- `tests/test_classify.py`
- `tests/fixtures/recorded/` (recorded API responses for the fixture set)
**`/goal` prompt:**
```
/goal Phase 2 of PLAN.md is complete when all of these are true: drafter/classify.py sends one email to Claude and returns JSON with category, confidence and summary. The JSON is validated against a schema in drafter/schema.py, and invalid output is rejected. Unit tests with a mocked API client pass. A recorded test shows at least 18 of the 20 fixture emails classified correctly. python -m pytest -q passes. The verify skill reports PASS. Constraints: the API key is read from ANTHROPIC_API_KEY only; no keys in files; no sending email; no refund or order API calls. Show the test output. Stop after 30 turns and summarize what is blocked.
```
**Stop rule:** 30 turns, then summarize.
---
## Phase 3: Draft
**Goal:** Generate a reply for each classified email. The reply reads `policies/` and is written to a Markdown file in `drafts/`.
**Design note:** Use plan mode first to decide how policy sections are split and cited.
**Files likely touched:**
- `drafter/draft.py`
- `drafter/policies.py` (loads and splits the policy files)
- `drafter/output.py` (writes the Markdown draft)
- `tests/test_draft.py`
- `tests/fixtures/expected_drafts/` (expected output shapes, no real amounts or dates)
**`/goal` prompt:**
```
/goal Phase 3 of PLAN.md is complete when all of these are true: drafter/draft.py generates a reply from the classified email and the policy files. Each draft is written as a Markdown file in drafts/ with category, confidence, summary, reply and the policy section cited. Every draft cites at least one policy section. The fixtures produce no invented amounts or dates, and a test checks this. Replies are under 150 words. python -m pytest -q passes. The verify skill reports PASS. Constraints: no sending email; no refund or order API calls; policy facts only from policies/. Show the test output and one sample draft. Stop after 30 turns and summarize what is blocked.
```
**Stop rule:** 30 turns, then summarize.
---
## Phase 4: Safety and queue
**Goal:** Redact names and addresses from logs. Route low-confidence emails and errors to `needs_human/`. Print a token estimate before large runs.
**Files likely touched:**
- `drafter/redact.py`
- `drafter/queue.py` (routing to `needs_human/` with an error note)
- `drafter/estimate.py` (token estimate and the 200-email confirmation)
- `drafter/run.py` (wires everything together and keeps going after errors)
- `tests/test_redact.py`, `tests/test_routing.py`
**`/goal` prompt:**
```
/goal Phase 4 of PLAN.md is complete when all of these are true: names and addresses are redacted from all log output, and a test proves it. Low-confidence results and API errors are written to needs_human/ with an error note, and the run continues to the next email. A run over 200 emails prints an estimated token count before calling the API. Tests cover redaction and routing. No secrets appear in git-tracked files. python -m pytest -q passes. The verify skill reports PASS. Constraints: no sending email; no refund or order API calls; the API key is read from ANTHROPIC_API_KEY only. Show the test output. Stop after 30 turns and summarize what is blocked.
```
**Stop rule:** 30 turns, then summarize.
---
## Phase 5: Evaluate and document
**Goal:** Build an evaluation script that reports accuracy by category. Write a beginner README with run steps.
**Files likely touched:**
- `drafter/evaluate.py` (prints a table of accuracy by category)
- `README.md` (plain-language run steps)
- `tests/test_evaluate.py`
**`/goal` prompt:**
```
/goal Phase 5 of PLAN.md is complete when all of these are true: python -m drafter.evaluate prints a table of accuracy by category for the fixture set. README.md gives beginner run steps that match the real commands. python -m pytest -q passes. The verify skill runs end to end and reports PASS. Constraints: no sending email; no refund or order API calls; no real keys or customer data in files. Show the evaluation table and the verify output. Stop after 30 turns and summarize what is blocked.
```
**Stop rule:** 30 turns, then summarize.
---
## After Phase 5
1. Run `/verify` once more.
2. Read `REVIEW.md` and check each item.
3. Only then consider using the bot on real email, and only after reading the privacy section in `INSTALL.md`.
REVIEW.md# Review checklist
Use this list before accepting any change. Claude checks it too.
## Safety
- [ ] Nothing sends email.
- [ ] Nothing calls a refund API or an order API.
- [ ] Nothing writes outside `drafts/` and `needs_human/`.
## Secrets and customer data
- [ ] No API keys, `.env` contents, customer names, emails or addresses appear in code, tests, fixtures or logs.
- [ ] Test fixtures are invented, not copied from real mail.
- [ ] The code reads `ANTHROPIC_API_KEY` from the environment only.
## Draft quality
- [ ] Every draft reply uses only facts from `policies/`.
- [ ] No invented amounts, dates or promises appear in any draft.
- [ ] Every draft names the policy section it used.
## Errors and routing
- [ ] Errors and low-confidence emails go to `needs_human/`.
- [ ] One failed email does not crash the whole run.
## Tests and dependencies
- [ ] `python -m pytest -q` passes.
- [ ] The dry run `python -m drafter.run --sample --dry-run` exits with code 0 and makes zero network calls.
- [ ] Every package version is pinned in `requirements.txt`.
## Plain-language summary
- [ ] The change has a short summary written for a non-programmer: what changed, why, and what the owner needs to check.
SPEC.md# Shop reply drafter: spec
## Goal
Save a draft reply for each incoming email about returns or shipping. Each draft comes with a category and a confidence level, so the owner can approve it in a few minutes.
The bot never sends email.
## Inputs
- Plain text (.txt) or .eml files in `inbox/`.
- A sample set of 20 invented emails in `tests/fixtures/`. They cover:
- returns
- shipping status
- missing tracking
- damaged item
- unrelated mail
- Policy text in `policies/returns.md` and `policies/shipping.md`.
## Categories
- `return_request`
- `shipping_status`
- `tracking_missing`
- `damaged_item`
- `other`
Anything not clearly one of the first four goes to `needs_human`.
## Outputs
One Markdown file per email in `drafts/`. Each file contains:
- category
- confidence: high, medium or low
- a short summary of the customer's question
- the draft reply
- the policy section the reply used
Low confidence goes to `needs_human/` instead.
## Reply rules
- Friendly tone, under 150 words.
- Only state facts found in `policies/`.
- No refund amounts, dates or promises unless the policy file states them.
- If unsure, the category becomes `needs_human`.
## Out of scope for v1
- Sending email
- Gmail or Shopify connections
- Refunds
- Order edits
- Payment data
- Multi-language replies
## Privacy
- Customer names and addresses are redacted from logs.
- The `drafts/` folder stays on the owner's computer.
- No customer data goes into git.
- Fixtures are invented.
## Failure behavior
- If an API call fails, write the email to `needs_human/` with the error note.
- Then keep going with the next email.
- One bad email never stops the run.
## Cost guard
- Default model: Sonnet.
- A run over 200 emails prints an estimated token count first and waits for a yes.
## Acceptance
- At least 18 of the 20 fixture emails get the correct category.
- Zero draft replies contain invented refund amounts or dates.
- `python -m pytest -q` passes.
- `python -m drafter.run --sample --dry-run` exits 0 with no network calls.
## Success measures for the owner
- Time to review a day's drafts is a few minutes, not an hour.
- The owner never finds a made-up policy fact in a draft.
- Anything unclear shows up in `needs_human/`, not in a draft.