# Inbox rules

Deterministic filters for one Cooper inbox. No model call and no change to plan prices.

## Match mail without a model

A rule is stored on one inbox and runs on the server when mail arrives. Conditions are combined with AND and compared without regard to letter case. from is an email address, @domain, or a domain. to is the recipient address as it was received. subject_contains and body_contains look for that text. The body scan uses the stored text and extracted text, each capped at 8,000 characters. has_attachment is true or false. auth is pass or fail and uses the DMARC result already on the message. list is true when List-Id or List-Unsubscribe is present.

A rule needs at least one condition and at least one action. String fields are at most 256 characters. auto_reply text is at most 2,000 characters. An inbox stores at most 20 rules. The next create returns HTTP 409 rule_limit. That cap is technical. It does not change Free, Starter, or Pro. A rule that does not match the schema returns HTTP 400 invalid_rule. Read uses message_read. Write uses message_update. An inbox-scoped key only sees its own inbox. Another account's inbox is HTTP 404 inbox_not_found.

## Archive newsletters

list true matches a message that carries List-Id or List-Unsubscribe. folder archive files the thread through the same folder path as PATCH thread, including purge_after when the folder is trash. stop_processing keeps later rules from running.

```bash
curl -s -X POST "https://cooperemail.com/api/v1/inboxes/$INBOX/rules" \
  -H "Authorization: Bearer $COOPER_API_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"Archive newsletters","conditions":{"list":true},"actions":{"folder":"archive"},"stop_processing":true}'
```

## Label receipts

add_labels uses the same label rules as PATCH message. read and unread can change. Other system labels cannot. subject_contains is enough on its own.

```bash
curl -s -X POST "https://cooperemail.com/api/v1/inboxes/$INBOX/rules" \
  -H "Authorization: Bearer $COOPER_API_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"Label receipts","conditions":{"subject_contains":"receipt"},"actions":{"add_labels":["receipt"]}}'
```

## Forward invoices to a teammate inbox

forward_to is one address. It must be another inbox on the same account, or a verified owner of this inbox. Any other address is HTTP 400 forward_target_not_allowed. The address is checked again when the message arrives. If the owner was removed, the forward is skipped and the rest of the rule still runs. The forward uses the same send path as POST forward, including quotas, suppressions, sending health, and send_mode. When the inbox is in approval mode, the forward is a pending draft. It is not sent until a verified owner approves it.

```bash
curl -s -X POST "https://cooperemail.com/api/v1/inboxes/$INBOX/rules" \
  -H "Authorization: Bearer $COOPER_API_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"Forward invoices","conditions":{"subject_contains":"invoice"},"actions":{"forward_to":"books@example.com"}}'
```

## Acknowledge support mail

auto_reply sends one plain-text reply through the normal send path. subject defaults to Re: plus the original subject. The reply sets Auto-Submitted: auto-replied and In-Reply-To and References from the original message. cooldown_hours defaults to 24, and must be from 1 to 720. The same sender does not get another auto-reply from that rule until the cooldown ends.

Forward and auto-reply are skipped when the message was blocked by a list, held or marked suspicious by screening, failed DMARC, came from this inbox or any address on the same account, has Auto-Submitted set to anything other than no (or X-Autoreply, X-Autorespond, or X-Auto-Response-Suppress All, OOF, or AutoReply), has Precedence bulk, junk, list, or auto_reply, has List-Id or List-Unsubscribe, has an empty Return-Path, or the sender local part is mailer-daemon, postmaster, no-reply, noreply, do-not-reply, donotreply, bounce, or bounces. An inbox sends at most 30 rule forwards, auto-replies, and approval drafts per rolling hour. Past that, the action is skipped as rate_limited. A skipped action is recorded. The inbound message is still stored.

```bash
curl -s -X POST "https://cooperemail.com/api/v1/inboxes/$INBOX/rules" \
  -H "Authorization: Bearer $COOPER_API_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"Acknowledge support","conditions":{"to":"support@example.com"},"actions":{"auto_reply":{"text":"We got your message.","cooldown_hours":24}}}'
```

## Order, stop, and test

Rules run from lowest position to highest. The same position uses created_at. A matching rule with stop_processing true ends the run. A rule that throws is logged on last_error and does not drop the mail.

POST /api/v1/inboxes/{id}/rules/test accepts {message_id} or a rule body plus message_id. The response lists the rules that would match, the actions that would run, and skipped actions with a reason. Nothing is written. Message JSON includes rules_applied when a rule matched. The field is rule_id, actions, and skipped. It is omitted when no rule matched.

rule.matched is stored in the event log. The data is inbox_id, message_id, thread_id, rule_id, actions, and skipped. It has no message body. A webhook receives it only when its events list names rule.matched. A hook created without an events list still receives only message.received. MCP tools are cooper_list_rules, cooper_create_rule, cooper_update_rule, cooper_delete_rule, and cooper_test_rule.

Operator: Avatar 8 LLC (ops@avatar33.com).
