القواعد
كل عملية في هذه المجموعة: ما تقبله وما تُرجعه والأخطاء التي قد تردّ بها.
العمليات
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
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.
معلمات الاستعلام
limitintegerRows per page, a whole number from 1 to 100. The server defaults to 25.
على الأقل 1على الأكثر 100الافتراضي25cursorstringThe
nextCursoryou 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, becausepositionalone 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 400invalid_cursor.enabledstringSend the word
trueor the wordfalse. Worth stating, because the obvious coercion would make?enabled=falsemean true and hand you back exactly the rules you were trying to exclude.أحد"true""false"
يُرجع
A page, in evaluation order.
الأخطاء
الأخطاء التي يمكن أن تُرجعها أي عملية400401403404422500دليل الأخطاء
متاح أيضًا في
POST/rules
Create a rule
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 من الأحرفdescriptionstringFree text note, at most 500 characters.
يمكن أن يكون nullحتى 500 من الأحرفenabledbooleanWhether the rule runs on arriving mail. Defaults to true.
الافتراضيtruematchstringWhether every condition or any single one must hold. Defaults to
all.أحد"all""any"الافتراضي"all"conditionsobject[]مطلوب1 to 20 conditions, each
{ field, op, value }with optionalheader(at most 128 characters) andnegate.valueis 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 }, withvalueat most 320 characters.من 1 إلى 10 من العناصرtypestringمطلوب- أحد
"label""remove_label""archive""mark_read""star""spam""trash""forward""reply""block_sender""reject" valuestring- حتى 320 من الأحرف
stopProcessingbooleanWhen true, no later rule runs on a message this rule matched. Defaults to false.
الافتراضيfalse
يُرجع
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 withPOST /security/step-up, send it toPOST /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_rulewithparamnaming the offending path,reject_needs_envelope,workspace_limit_reachedwhen the workspace is at its rule limit, orcapability_unsupportedonconditionswhen a key limited to particular addresses or domains creates a rule that could act on mail to an address outside them.
الأخطاء التي يمكن أن تُرجعها أي عملية400401404500دليل الأخطاء
متاح أيضًا في
GET/rules/runs
What rules have actually done
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.
معلمات الاستعلام
limitintegerRows per page, a whole number from 1 to 100. The server defaults to 25.
على الأقل 1على الأكثر 100الافتراضي25cursorstringThe
nextCursorfrom the previous page. Never build one yourself.ruleIdstringNarrow to one rule. Works for a rule that has since been deleted.
threadIdstringThe other question people ask: why did THIS message end up here.
يُرجع
The audit, newest first.
الأخطاء
الأخطاء التي يمكن أن تُرجعها أي عملية400401403404422500دليل الأخطاء
متاح أيضًا في
POST/rules/reorder
Set the whole order
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_orderand 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, orcapability_unsupportedwhen 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دليل الأخطاء
متاح أيضًا في
GET/rules/{id}
Retrieve a rule
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.
يُرجع
The rule.
الأخطاء
الأخطاء التي يمكن أن تُرجعها أي عملية400401403404422500دليل الأخطاء
متاح أيضًا في
PATCH/rules/{id}
Update a rule
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.
متن الطلب
namestringReplacement name, 1 to 100 characters and unique per mailbox.
من 1 إلى 100 من الأحرفdescriptionstringReplacement note of at most 500 characters, or null to clear it.
يمكن أن يكون nullحتى 500 من الأحرفenabledbooleanTurns the rule on or off without moving it.
الافتراضيtruematchstringWhether 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 من الأحرف
stopProcessingbooleanWhen true, no later rule runs on a message this rule matched.
الافتراضيfalsepositionintegerMove 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. UsePOST /rules/reorderwhen you care about the exact sequence.على الأقل 0على الأكثر 1000000
يُرجع
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 withPOST /security/step-up, send it toPOST /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
- 422
invalid_ruleorreject_needs_envelope, withparamnaming the field, orcapability_unsupportedwhen 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دليل الأخطاء
متاح أيضًا في
DELETE/rules/{id}
Delete a rule
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.
يُرجع
Deleted.
objectstring- أحد
"rule" idstringdeletedboolean- أحد
true
الأخطاء
الأخطاء التي يمكن أن تُرجعها أي عملية400401403404422500دليل الأخطاء
متاح أيضًا في
POST/rules/{id}/test
See what a rule would catch
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 من العناصرdaysintegerHow far back the window reaches. Ignored when
threadIdsis given.على الأقل 1على الأكثر 365الافتراضي30limitintegerHow 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دليل الأخطاء
متاح أيضًا في
الكائنات
Ruleobject
objectstring- أحد
"rule" idstringThe durable handle,
rul_+ 24 hex.namestringUnique 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
enabledbooleanA disabled rule is skipped at delivery and keeps its position.
POST /rules/{id}/teststill works on one, which is the point: write it, see what it would have caught, then turn it on.positionintegerWhere 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 withPATCH, set them all withPOST /rules/reorder.matchstringAND or OR across the conditions. NOT is per condition, as
negate.أحد"all""any"conditionsRuleCondition[]actionsRuleAction[]stopProcessingbooleanEnd 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 matchCountintegerHow 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[]hasMorebooleanTrue when another page follows. Pass
nextCursorback ascursorto read it.nextCursorstringAn opaque cursor for the next page, or null on the last page. Pass it back unchanged.
يمكن أن يكون null
RuleRunobject
objectstring- أحد
"rule_run" idstringrrun_+ 24 hex.ruleIdstringThe rule as it was then. It may since have been deleted; this is not a link.
ruleNamestringThe rule's name AT THE TIME, not its name now.
threadIdstringmessageIdstring- يمكن أن يكون null
senderstringLower-cased and trimmed, so it can be counted on.
subjectstringTruncated at 500 characters. A header is unbounded.
actionsstring[]What was APPLIED, as
typeortype: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" dataRuleRun[]hasMorebooleannextCursorstring- يمكن أن يكون null
RuleTestobject
objectstring- أحد
"rule_test" ruleIdstringscannedintegerMessages actually examined, one per thread. Lower than
limitwhen the window held fewer threads, or when a thread would not load and was skipped rather than failing the run.matchedintegerwouldApplystring[]The rule's actions as
typeortype:value, which is what it would do to each match, in declaration order. The same strings the MCPtestRuletool prints, from the same function, so the two cannot describe one rule differently.messagesobject[]The matches, newest first.
threadIdstringfromstringsubjectstringreceivedAtstring- يمكن أن يكون nullالتنسيق
date-time
warningsobject[]Read these before reading
matched. Treat an unrecognised code as a warning.codestringfield_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 undernegate: truematches 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 forfield_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, sohas_attachment,attachment_name,attachment_typeandattachment_sizecan report lower here. A rule tested over a month of HTML newsletters whose only part is acid:signature logo matches nothing here and then archives every one of them. Andhourandweekdayare scored from the sender-suppliedDate: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 thebody, 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. Acontainscannot hold on such a message however plainly the word is written inside it, and anegate: trueholds 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-signedandsmime-signedcarry 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"valuestringThe field name, or the address, depending on the code.