تخطَّ إلى المستندات
API

القواعد

كل عملية في هذه المجموعة: ما تقبله وما تُرجعه والأخطاء التي قد تردّ بها.

العمليات

What should happen to mail before anybody reads it. Conditions over one inbound message, and what to do with anything matching them.

A rule is a FLAT list of conditions joined by match: "all" or "any", with a per-condition negate. That is AND, OR and NOT, and what it cannot express in one rule is (A AND B) OR C, which is two rules, and two rules is how a person reads it back six months later anyway. The three vocabularies are published as enums on RuleCondition and RuleAction, derived from the schema that validates them, so a generated client can switch on them.

Rules run in position order and a matching rule carrying stopProcessing ends the pass, so ORDER is part of the meaning. GET /rules therefore comes back in evaluation order rather than newest-first, and POST /rules/reorder demands the whole order rather than accepting a partial one.

Two actions cost real money and are worth reading about before you use them. A forward does not relay the original message: it REBUILDS the mail through the send path, so the original DKIM signature and anything above the outbound size ceiling do not survive. It also needs consent from the destination. Until that address has agreed to receive mail forwarded from this mailbox, the action is recorded as refused rather than sent. A reply can loop: two mailboxes each with a reply rule will mail one another forever, so anything carrying Auto-Submitted, Precedence: bulk, a List-Id, a List-Unsubscribe or a null return-path is never answered, and no sender gets more than one automatic reply a day. POST /rules/{id}/test reports both as warnings.

A reject (a 550 at SMTP) may only be written against envelope_from. A refusal decided on a From: header is answered by the mailing list rather than by the sender, which reads it as a bouncing subscriber and unsubscribes the reader.

GET/rules

List rules

الصلاحياتrules:readيقرأ

In EVALUATION order (the order the rules actually run in), and not newest-first like every other list here. Rule 4 exists to run after rule 3, and a list sorted by time would show you a sequence that is not the sequence your mail goes through.

A key limited to particular addresses or domains lists only the rules that can act on mail delivered to them: every rule without a delivered_to condition, and a rule with one when it can match an address the key holds.

Requires the rules:read scope.

معلمات الاستعلام

limitinteger

Rows per page, a whole number from 1 to 100. The server defaults to 25.

على الأقل 1على الأكثر 100الافتراضي25
cursorstring

The nextCursor you were handed, never one you build. It is opaque and holds where the last rule sat in (position, createdAt, id), the same tuple the list is ordered by, because position alone is not unique. A rule deleted, moved or renamed between pages never breaks the walk: the next page starts at the first rule that sorts after the cursor. A value this list did not hand out is a 400 invalid_cursor.

enabledstring

Send the word true or the word false. Worth stating, because the obvious coercion would make ?enabled=false mean true and hand you back exactly the rules you were trying to exclude.

أحد"true""false"

يُرجع

A page, in evaluation order.

الأخطاء

الأخطاء التي يمكن أن تُرجعها أي عملية400401403404422500دليل الأخطاء

متاح أيضًا في

SDK
rules.list()rules.listAll()rules.iterate()
CLI
openemail rules list
MCP
listRules

POST/rules

Create a rule

الصلاحياتrules:writeيغيّر البيانات
يطلب رمز تحقق

The rule appends to the end of the list and is ON unless you say otherwise. position is not accepted here: a new rule goes last, and a caller who wants it somewhere else calls POST /rules/reorder, which is the only operation that can guarantee the result is the order somebody asked for.

A reject action is refused unless the rule also carries an envelope_from condition, and that refusal is not negotiable. A 550 answered on the strength of a From: header is read by a mailing list as a bouncing subscriber, and it unsubscribes the reader, so identity for the purposes of refusing mail means the SMTP envelope and nothing else.

No Idempotency-Key. There is nothing on this resource to claim one against, and a retry after a lost response mints a second rule unless the name collides.

A key limited to particular addresses or domains creates only a rule that acts on nothing but mail delivered to them, so the rule needs a delivered_to condition that no other address in the workspace matches. equals naming one of its addresses, or matches naming a whole domain it holds, does that. Anything else is a 422 capability_unsupported on conditions.

Requires the rules:write scope.

متن الطلب

namestringمطلوب

Display name, 1 to 100 characters after trimming and unique per mailbox.

من 1 إلى 100 من الأحرف
descriptionstring

Free text note, at most 500 characters.

يمكن أن يكون nullحتى 500 من الأحرف
enabledboolean

Whether the rule runs on arriving mail. Defaults to true.

الافتراضيtrue
matchstring

Whether every condition or any single one must hold. Defaults to all.

أحد"all""any"الافتراضي"all"
conditionsobject[]مطلوب

1 to 20 conditions, each { field, op, value } with optional header (at most 128 characters) and negate. value is at most 512 characters.

من 1 إلى 20 من العناصر
fieldstringمطلوب
أحد"from""from_domain""envelope_from""to""cc""bcc""recipient""reply_to""delivered_to""subject""body""header""list_id""attachment_name""attachment_type""has_attachment""attachment_size""message_size""spam""hour""weekday"
opstringمطلوب
أحد"matches""contains""equals""starts_with""ends_with""gt""lt"
valuestringمطلوب
حتى 512 من الأحرف
headerstring
حتى 128 من الأحرف
negateboolean
الافتراضيfalse
actionsobject[]مطلوب

1 to 10 actions, each { type, value }, with value at most 320 characters.

من 1 إلى 10 من العناصر
typestringمطلوب
أحد"label""remove_label""archive""mark_read""star""spam""trash""forward""reply""block_sender""reject"
valuestring
حتى 320 من الأحرف
stopProcessingboolean

When true, no later rule runs on a message this rule matched. Defaults to false.

الافتراضيfalse

يُرجع

201Rule

Created, at the end of the list.

الأخطاء

403

The key lacks the scope, or may not send as that address.

step_up_required: the call was made with an OAuth access token that has not been verified in the last 60 minutes. Ask for a code with POST /security/step-up, send it to POST /security/step-up/verify, then repeat the call. The person can also choose Allow changes for 60 minutes on the app in Account settings, Connected apps, on the OpenEmail website. An API key is never asked for a code.

409

rule_name_taken. Names are unique per mailbox.

422

invalid_rule with param naming the offending path, reject_needs_envelope, workspace_limit_reached when the workspace is at its rule limit, or capability_unsupported on conditions when a key limited to particular addresses or domains creates a rule that could act on mail to an address outside them.

الأخطاء التي يمكن أن تُرجعها أي عملية400401404500دليل الأخطاء

متاح أيضًا في

SDK
rules.create()
CLI
openemail rules create
MCP
createRule

GET/rules/runs

What rules have actually done

الصلاحياتrules:readيقرأ

The log of every rule that matched an inbound message, with what it did and what it was refused. Nothing here is deleted on a timer.

It survives the rule: ruleName and actions are copied onto the row when the rule runs, so a renamed or deleted rule still reads correctly here. Deleting a rule does not delete its history, because what happened does not stop being true.

A key limited to particular addresses or domains reads only the runs of rules it can list in GET /rules, so the runs of a rule deleted since, whose conditions are gone, are left out for such a key.

Requires the rules:read scope.

معلمات الاستعلام

limitinteger

Rows per page, a whole number from 1 to 100. The server defaults to 25.

على الأقل 1على الأكثر 100الافتراضي25
cursorstring

The nextCursor from the previous page. Never build one yourself.

ruleIdstring

Narrow to one rule. Works for a rule that has since been deleted.

threadIdstring

The other question people ask: why did THIS message end up here.

يُرجع

The audit, newest first.

الأخطاء

الأخطاء التي يمكن أن تُرجعها أي عملية400401403404422500دليل الأخطاء

متاح أيضًا في

SDK
rules.listRuns()rules.listAllRuns()rules.iterateRuns()
CLI
openemail rules list-runs
MCP
listRuleRuns

POST/rules/reorder

Set the whole order

الصلاحياتrules:writeيغيّر البيانات

The body IS the order, so it must name every rule on the mailbox. Applied in one statement inside a transaction: either the whole order lands or none of it does, and there is no window in which half the rules have moved.

A key limited to particular addresses or domains names every rule GET /rules lists for it, and nothing else. It may move the rules that act on nothing but mail delivered to its addresses, anywhere among the others. Every other rule has to stay in the order it is in, and the rules it cannot list keep their places around them, so mail to any other address meets the same rules in the same order as before. The answer lists only the rules the key can see.

Idempotent by construction (applying the same body twice lands in the same place), which is why this is the one write on the resource that is safe to retry.

Requires the rules:write scope.

متن الطلب

ruleIdsstring[]مطلوب

EVERY rule on the mailbox, in the order you want them evaluated. A list that omits one is refused with incomplete_order and nothing is written. The omitted rules would have to be put somewhere and there is no right answer for where.

من 1 إلى 100 من العناصر

يُرجع

Every rule, in its new order.

الأخطاء

422

incomplete_order, or capability_unsupported when a key limited to particular addresses or domains moves a rule that can act on mail to an address outside them. An id that is not on this mailbox, or that the key cannot list, is a 404 and nothing moves.

الأخطاء التي يمكن أن تُرجعها أي عملية400401403404500دليل الأخطاء

متاح أيضًا في

SDK
rules.reorder()
CLI
openemail rules reorder
MCP
reorderRules

GET/rules/{id}

Retrieve a rule

الصلاحياتrules:readيقرأ

A key limited to particular addresses or domains gets a 404 for a rule that cannot act on mail delivered to them, the same rules GET /rules leaves out for it.

Requires the rules:read scope.

معلمات المسار

idstringمطلوب

A rul_ id. Rules have no slug. The name is editable and not a handle.

يُرجع

200Rule

The rule.

الأخطاء

الأخطاء التي يمكن أن تُرجعها أي عملية400401403404422500دليل الأخطاء

متاح أيضًا في

SDK
rules.get()
CLI
openemail rules get
MCP
getRule

PATCH/rules/{id}

Update a rule

الصلاحياتrules:writeيغيّر البيانات
يطلب رمز تحقق

conditions and actions are REPLACE-WHOLE. Sending conditions replaces all of them; there is no way to add or remove one, and there will not be. Both arrays are ordered, match and stopProcessing are read against all of them at once, and an index-addressed patch would be a lost update the moment two tabs are open.

The patch is merged onto the stored rule and the WHOLE result revalidated, so an omitted field keeps its stored value rather than collecting a default. A PATCH that only renames a rule cannot switch a disabled one back on. It is also the only way the cross-field checks can run at all: a patch carrying nothing but a reject action has no conditions of its own to check it against.

A key limited to particular addresses or domains changes only the rules that act on nothing but mail delivered to them: a rule it creates, and a rule after it patches one, needs a delivered_to condition that no other address in the workspace matches, and anything else is a 422 capability_unsupported. A rule the key cannot list in GET /rules is a 404.

Requires the rules:write scope.

معلمات المسار

idstringمطلوب

A rul_ id. Rules have no slug. The name is editable and not a handle.

متن الطلب

namestring

Replacement name, 1 to 100 characters and unique per mailbox.

من 1 إلى 100 من الأحرف
descriptionstring

Replacement note of at most 500 characters, or null to clear it.

يمكن أن يكون nullحتى 500 من الأحرف
enabledboolean

Turns the rule on or off without moving it.

الافتراضيtrue
matchstring

Whether every condition or any single one must hold.

أحد"all""any"الافتراضي"all"
conditionsobject[]

Complete replacement list of 1 to 20 conditions.

من 1 إلى 20 من العناصر
fieldstringمطلوب
أحد"from""from_domain""envelope_from""to""cc""bcc""recipient""reply_to""delivered_to""subject""body""header""list_id""attachment_name""attachment_type""has_attachment""attachment_size""message_size""spam""hour""weekday"
opstringمطلوب
أحد"matches""contains""equals""starts_with""ends_with""gt""lt"
valuestringمطلوب
حتى 512 من الأحرف
headerstring
حتى 128 من الأحرف
negateboolean
الافتراضيfalse
actionsobject[]

Complete replacement list of 1 to 10 actions.

من 1 إلى 10 من العناصر
typestringمطلوب
أحد"label""remove_label""archive""mark_read""star""spam""trash""forward""reply""block_sender""reject"
valuestring
حتى 320 من الأحرف
stopProcessingboolean

When true, no later rule runs on a message this rule matched.

الافتراضيfalse
positioninteger

Move this rule without renumbering the others. Positions are not unique and the order is total on (position, createdAt, id), so landing on another rule leaves the tie broken by age. Use POST /rules/reorder when you care about the exact sequence.

على الأقل 0على الأكثر 1000000

يُرجع

200Rule

Saved.

الأخطاء

403

The key lacks the scope, or may not send as that address.

step_up_required: the call was made with an OAuth access token that has not been verified in the last 60 minutes. Ask for a code with POST /security/step-up, send it to POST /security/step-up/verify, then repeat the call. The person can also choose Allow changes for 60 minutes on the app in Account settings, Connected apps, on the OpenEmail website. An API key is never asked for a code.

422

invalid_rule or reject_needs_envelope, with param naming the field, or capability_unsupported when a key limited to particular addresses or domains patches a rule that can act on mail to an address outside them, or would leave it able to.

الأخطاء التي يمكن أن تُرجعها أي عملية400401404500دليل الأخطاء

متاح أيضًا في

SDK
rules.update()
CLI
openemail rules update
MCP
setRuleEnabledupdateRule

DELETE/rules/{id}

Delete a rule

الصلاحياتrules:writeيحذف

There is no undo, and mail already filed stays where the rule put it. PATCH with enabled: false is the reversible version of this, and it keeps the rule where it is in the order.

A key limited to particular addresses or domains deletes only a rule that acts on nothing but mail delivered to them. Another rule it can list is a 422 capability_unsupported, and one it cannot list is a 404.

The run history is NOT deleted with it. GET /rules/runs still shows what this rule did, under the name it had at the time.

A tombstone rather than a 204, matching the rest of the API: the id comes back so a log line can name what went.

Requires the rules:write scope.

معلمات المسار

idstringمطلوب

A rul_ id. Rules have no slug. The name is editable and not a handle.

يُرجع

200object

Deleted.

objectstring
أحد"rule"
idstring
deletedboolean
أحدtrue

الأخطاء

الأخطاء التي يمكن أن تُرجعها أي عملية400401403404422500دليل الأخطاء

متاح أيضًا في

SDK
rules.delete()
CLI
openemail rules delete
MCP
deleteRule

POST/rules/{id}/test

See what a rule would catch

الصلاحياتrules:readيقرأ

A dry run. It changes nothing, sends nothing and files nothing, which is the whole promise of the endpoint, and why it takes rules:read rather than rules:write. Works on a disabled rule, which is the intended order of operations: write it, test it, then turn it on.

A key limited to particular addresses or domains tests only a rule it can list, and only against conversations delivered to its addresses: threadIds naming any other conversation are skipped.

The body is optional; posting none means the defaults. Read warnings before reading matched. A stored message no longer carries the SMTP envelope or any header, so a rule leaning on those is being tested against a question that was never asked, and under negate that reads as a match rather than as a miss. A rule reading the body can hit the same trap by a different road: mail that arrived already encrypted is scanned like any other message, but its body is withheld from the engine here exactly as it is at delivery, so body_encrypted means "this could not be evaluated against those messages" rather than "this would not have matched them".

It is a sample of what is currently in the inbox, not a replay of history: a message an existing rule already archived is not in the inbox and will not appear here. GET /rules/runs is the record of what actually happened.

There is deliberately no POST /rules/{id}/run. Applying a rule retroactively across a mailbox is unbounded, irreversible and has no undo, and the filing path it would go through rewrites each thread it touches and bumps it to the top of the inbox, so "tidy up my old receipts" would present as every old receipt arriving again.

Requires the rules:read scope.

معلمات المسار

idstringمطلوب

A rul_ id. Rules have no slug. The name is editable and not a handle.

متن الطلب

threadIdsstring[]

Specific threads to try, instead of the recent window. A thread that will not load is skipped rather than failing the request, so an id that no longer exists costs nothing.

حتى 50 من العناصر
daysinteger

How far back the window reaches. Ignored when threadIds is given.

على الأقل 1على الأكثر 365الافتراضي30
limitinteger

How many threads to read. Bounded because every one is a round trip and a Worker has a wall-clock budget.

على الأقل 1على الأكثر 200الافتراضي50

يُرجع

What it would have caught, and what it would do.

الأخطاء

الأخطاء التي يمكن أن تُرجعها أي عملية400401403404422500دليل الأخطاء

متاح أيضًا في

SDK
rules.test()
CLI
openemail rules test
MCP
testRule

الكائنات

Ruleobject

objectstring
أحد"rule"
idstring

The durable handle, rul_ + 24 hex.

namestring

Unique per mailbox. Creating a second rule with the same name is a 409, not a silent duplicate. It is also what the audit records, so a rule renamed after the fact keeps the old name on the runs it already produced.

descriptionstring
يمكن أن يكون null
enabledboolean

A disabled rule is skipped at delivery and keeps its position. POST /rules/{id}/test still works on one, which is the point: write it, see what it would have caught, then turn it on.

positioninteger

Where it sits in the pass. Not unique and not necessarily contiguous. The order is total on (position, createdAt, id), so two rules sharing a position are broken by age rather than arbitrarily. Move one with PATCH, set them all with POST /rules/reorder.

matchstring

AND or OR across the conditions. NOT is per condition, as negate.

أحد"all""any"
conditionsRuleCondition[]
stopProcessingboolean

End the pass here when this rule matches. Later rules do not run at all, which is how a catch-all at the bottom of the list stays a catch-all.

lastMatchedAtstring
يمكن أن يكون nullالتنسيقdate-time
matchCountinteger

How many messages this rule has caught, ever. The cheap answer to "is this rule doing anything": a count still at zero after a month is a rule whose conditions never hold, and finding that out should not cost a walk through GET /rules/runs.

createdAtstring
التنسيقdate-time
updatedAtstring
التنسيقdate-time

RuleActionobject

typestringمطلوب
أحد"label""remove_label""archive""mark_read""star""spam""trash""forward""reply""block_sender""reject"
valuestring
حتى 320 من الأحرف

RuleConditionobject

fieldstringمطلوب
أحد"from""from_domain""envelope_from""to""cc""bcc""recipient""reply_to""delivered_to""subject""body""header""list_id""attachment_name""attachment_type""has_attachment""attachment_size""message_size""spam""hour""weekday"
opstringمطلوب
أحد"matches""contains""equals""starts_with""ends_with""gt""lt"
valuestringمطلوب
حتى 512 من الأحرف
headerstring
حتى 128 من الأحرف
negateboolean
الافتراضيfalse

RuleListobject

objectstring
أحد"list"
dataRule[]
hasMoreboolean

True when another page follows. Pass nextCursor back as cursor to read it.

nextCursorstring

An opaque cursor for the next page, or null on the last page. Pass it back unchanged.

يمكن أن يكون null

RuleRunobject

objectstring
أحد"rule_run"
idstring

rrun_ + 24 hex.

ruleIdstring

The rule as it was then. It may since have been deleted; this is not a link.

ruleNamestring

The rule's name AT THE TIME, not its name now.

threadIdstring
messageIdstring
يمكن أن يكون null
senderstring

Lower-cased and trimmed, so it can be counted on.

subjectstring

Truncated at 500 characters. A header is unbounded.

actionsstring[]

What was APPLIED, as type or type:value. Only the actions that actually took effect.

failuresstring[]

What was refused, as type: reason. Read this before concluding a rule did nothing. The commonest entry is an auto-reply the loop guard declined to send, and without this column "the rule replied" and "the rule was not allowed to reply" look identical from outside.

createdAtstring
التنسيقdate-time

RuleRunListobject

objectstring
أحد"list"
hasMoreboolean
nextCursorstring
يمكن أن يكون null

RuleTestobject

objectstring
أحد"rule_test"
ruleIdstring
scannedinteger

Messages actually examined, one per thread. Lower than limit when the window held fewer threads, or when a thread would not load and was skipped rather than failing the run.

matchedinteger
wouldApplystring[]

The rule's actions as type or type:value, which is what it would do to each match, in declaration order. The same strings the MCP testRule tool prints, from the same function, so the two cannot describe one rule differently.

messagesobject[]

The matches, newest first.

threadIdstring
fromstring
subjectstring
receivedAtstring
يمكن أن يكون nullالتنسيقdate-time
warningsobject[]

Read these before reading matched. Treat an unrecognised code as a warning.

codestring

field_unevaluable: the rule asks about something a STORED message no longer carries (the SMTP envelope, any header, which alias it arrived at, the size on the wire), so that condition was not answered here and the dry run is quieter than delivery would be. Worse in the other direction: an unanswered condition under negate: true matches everything in the sample and a fraction of real mail. field_approximate: the condition WAS evaluated, and answered off stored mail rather than off the wire, so the number beside it can differ from what real delivery does. Deliberately a separate code rather than a second meaning for field_unevaluable: that one says the condition was SKIPPED, and a caller told a condition was skipped when it was in fact answered discounts the count in the wrong direction. Two things read differently. Images embedded in a message body are dropped from the stored attachment list and counted by delivery, so has_attachment, attachment_name, attachment_type and attachment_size can report lower here. A rule tested over a month of HTML newsletters whose only part is a cid: signature logo matches nothing here and then archives every one of them. And hour and weekday are scored from the sender-supplied Date: header, which is the only time a stored message keeps, where delivery scores them from our own receipt clock. Neither is recoverable from a stored message, so the field is named rather than silently answered wrongly. body_encrypted: the rule reads the body, and at least one message in the sample arrived already encrypted. A third statement again, and not a weaker version of either of the two above: a sealed body is withheld from the engine HERE AND AT DELIVERY ALIKE, so the condition really was evaluated and the answer really is the one real mail will get. What it was evaluated against is nothing. A contains cannot hold on such a message however plainly the word is written inside it, and a negate: true holds on every one of them. Fires only when both halves were measured: the rule asks about the body, and the scan met a sealed message. A SIGNED message is not one of these; pgp-signed and smime-signed carry their body in the clear and are read normally. forward_unverified: the destination is not an address we host, so the copy is REBUILT by the send path rather than relayed and the original DKIM signature does not survive. forward_loop: following the chain comes back to this mailbox.

أحد"field_unevaluable""field_approximate""body_encrypted""forward_unverified""forward_loop"
valuestring

The field name, or the address, depending on the code.