replylayer 0.22.1__tar.gz → 0.23.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {replylayer-0.22.1 → replylayer-0.23.0}/PKG-INFO +8 -8
- {replylayer-0.22.1 → replylayer-0.23.0}/README.md +7 -7
- {replylayer-0.22.1 → replylayer-0.23.0}/pyproject.toml +1 -1
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/__init__.py +1 -1
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/_http.py +1 -1
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/mailboxes.py +17 -13
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/types.py +26 -24
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_hitl_review_types.py +1 -1
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_instruction_trust.py +1 -1
- {replylayer-0.22.1 → replylayer-0.23.0}/.gitignore +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/__main__.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/_client.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/_pagination.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/errors.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/py.typed +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/__init__.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/account.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/api_keys.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/attachments.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/domains.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/drafts.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/health.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/inbound_blocklist.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/legal_holds.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/messages.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/recipients.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/suppressions.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/threads.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/replylayer/resources/webhooks.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/__init__.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_async.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_attachments.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_client.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_domains.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_drafts.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_governed_email_effect.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_http.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_messages_idempotency.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_readme_resource_parity.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_resources.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_threads.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_version.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_web_risk_types.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_webhooks.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/tests/test_ws1_ws6.py +0 -0
- {replylayer-0.22.1 → replylayer-0.23.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: replylayer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.23.0
|
|
4
4
|
Summary: Official Python SDK for ReplyLayer — email for AI agents
|
|
5
5
|
Project-URL: Homepage, https://replylayer.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/replylayer/rly
|
|
@@ -187,12 +187,12 @@ if draft["worst_decision"] == "allow":
|
|
|
187
187
|
print(f"Sent {result['message_id']}")
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
-
The send/reply/draft-send response carries two additive, nullable keys that explain a held send inline (no second `messages.get` call). `result["scan"]` is the vendor-neutral scanner verdict (`ScanSummary`); `result["hold_context"]` (`{"trigger_source", "summary_reasons"}` or `None`) is the policy/
|
|
190
|
+
The send/reply/draft-send response carries two additive, nullable keys that explain a held send inline (no second `messages.get` call). `result["scan"]` is the vendor-neutral scanner verdict (`ScanSummary`); `result["hold_context"]` (`{"trigger_source", "summary_reasons"}` or `None`) is the policy/human-review reason, non-null only when the delivery `status` diverges from `scan["verdict"]` because of a policy/human-review hold — a clean scan held for review by your mailbox policy, or a scanner review-flag held as quarantine on a plan without the review queue (`trigger_source`: `mailbox_policy` | `scanner` | `both`).
|
|
191
191
|
|
|
192
192
|
By default `drafts.send()`, `messages.send()`, and `messages.reply()` return only once the scanner verdict is known, with `scan` and `hold_context` inline. Pass `async_dispatch=True` to `drafts.send()` to send the `Prefer: respond-async` hint. **The hint is advisory — the server decides.** When async dispatch is available the server returns `202` with `status="queued_for_dispatch"` (`AsyncSendAck`); otherwise it ignores the hint and returns a normal `200` `SendMessageResponse`. **Always branch on the result**: `result["status"] == "queued_for_dispatch"` ⇒ `AsyncSendAck`, otherwise `SendMessageResponse`. Poll `messages.get(message_id)` (or handle the lifecycle webhook) until `state` is terminal. Attachment-bearing drafts fail closed on the async path (`400 ATTACHMENTS_REQUIRE_SYNC_SEND`). (`messages.wait()` is a mailbox long-poll for new *inbound* mail, not a way to observe a specific message by ID.)
|
|
193
193
|
|
|
194
194
|
The send endpoint raises `ReplyLayerError` with distinct `.code` values on 409:
|
|
195
|
-
- `DRAFT_REJECTED_BY_RESCAN` — send-time scan flipped the verdict to `block`/`quarantine`. The draft stays in `draft` state; edit the body and retry. `err.details` carries `scan`, `releasable` (`True` for a `quarantine` hold the customer can release via `POST /v1/drafts/:id/release-and-send`, `False` for a terminal `block`), and, when a policy/
|
|
195
|
+
- `DRAFT_REJECTED_BY_RESCAN` — send-time scan flipped the verdict to `block`/`quarantine`. The draft stays in `draft` state; edit the body and retry. `err.details` carries `scan`, `releasable` (`True` for a `quarantine` hold the customer can release via `POST /v1/drafts/:id/release-and-send`, `False` for a terminal `block`), and, when a policy/human-review decision drove the hold, `hold_context`.
|
|
196
196
|
- `DRAFT_ALREADY_SENT` — the draft was already sent (race or retry after success).
|
|
197
197
|
|
|
198
198
|
```python
|
|
@@ -387,11 +387,11 @@ Images are a separately confirmed raw-download family. When `allowed_file_famili
|
|
|
387
387
|
|
|
388
388
|
Human dashboard sessions and admin/pre-scoping keys can download clean stored `metadata_only` attachments, including attachments held back from agent raw-download policy. Agent-role keys remain bound to the mailbox policy gate plus hard safety checks; all callers remain blocked by infected AV verdicts, non-terminal message states, missing stored bytes, and hard attachment blocks.
|
|
389
389
|
|
|
390
|
-
See
|
|
390
|
+
See the Mailboxes API reference at https://replylayer.ai/docs/api/mailboxes for the full contract and known limitations.
|
|
391
391
|
|
|
392
392
|
### Recipient allowlist (mailbox containment)
|
|
393
393
|
|
|
394
|
-
A mailbox is in `blocklist` mode by default — the pre-send gate rejects `suppressed_addresses` hits and allows everyone else. Switching to `allowlist` mode
|
|
394
|
+
A mailbox is in `blocklist` mode by default — the pre-send gate rejects `suppressed_addresses` hits and allows everyone else. Switching to `allowlist` mode contains **agent-origin** outbound to a pre-approved list (plus thread participants): a prompt-injected or compromised **agent key** cannot email outside the list. It is a containment boundary against a hijacked agent, not an all-origin lock — a human send (your dashboard session or an admin API key) is not restricted by the allowlist; only your do-not-contact (suppression) list binds a human send.
|
|
395
395
|
|
|
396
396
|
```python
|
|
397
397
|
# Populate the allowlist first. Admin-only — agent keys get 403 INSUFFICIENT_SCOPE.
|
|
@@ -413,7 +413,7 @@ rl.mailboxes.allowlist.delete(mailbox["id"], "partner@corp.com", force_empty=Tru
|
|
|
413
413
|
|
|
414
414
|
A send/reply/draft-send to a recipient on your do-not-contact (suppression) list raises `ReplyLayerError` with `.code == "RECIPIENT_SUPPRESSED"` (HTTP 403, `details["reason"] == "suppressed"`). This is terminal — escalate, don't retry; remove the suppression or send to a different recipient.
|
|
415
415
|
|
|
416
|
-
Allowlist mutations are admin-only — granting
|
|
416
|
+
Allowlist mutations are admin-only — granting mutation to an LLM defeats the agent-containment boundary. Agents *can* `list` (so they can see what they're allowed to email) but not `add`/`add_bulk`/`delete`. Three new webhook events: `recipient_allowlist.added`, `recipient_allowlist.removed`, `mailbox.recipient_policy_changed`.
|
|
417
417
|
|
|
418
418
|
### Domain entries
|
|
419
419
|
|
|
@@ -500,7 +500,7 @@ if ctx and ctx.get("instruction_trust"):
|
|
|
500
500
|
# content-safety judgment.
|
|
501
501
|
print(ctx["guidance"])
|
|
502
502
|
print(ctx["instruction_trust"])
|
|
503
|
-
# {"version": "v1", "match": "address", "verified_domain": ..., "verdict": "verified_aligned", "provenance": "
|
|
503
|
+
# {"version": "v1", "match": "address", "verified_domain": ..., "verdict": "verified_aligned", "provenance": "managed"}
|
|
504
504
|
```
|
|
505
505
|
|
|
506
506
|
A human account owner enables the mailbox mode and the key's capability, and designates the trusted sender, from the dashboard (each a loosening change requiring session re-auth). Both the mailbox mode and the per-key capability default to off, so existing integrations are unaffected until a customer opts in. This is a read-path signal only — a copied or hijacked API key cannot self-grant the capability, and there is nothing for a client to set to request it.
|
|
@@ -549,7 +549,7 @@ Error classes: `ReplyLayerError` (base), `AuthenticationError` (401), `Forbidden
|
|
|
549
549
|
|
|
550
550
|
## Webhook signature verification
|
|
551
551
|
|
|
552
|
-
> For a full integration guide (event catalog, retry behavior, idempotency, security, troubleshooting), see
|
|
552
|
+
> For a full integration guide (event catalog, retry behavior, idempotency, security, troubleshooting), see https://replylayer.ai/docs/webhooks.
|
|
553
553
|
|
|
554
554
|
```python
|
|
555
555
|
from replylayer import verify_webhook_signature
|
|
@@ -167,12 +167,12 @@ if draft["worst_decision"] == "allow":
|
|
|
167
167
|
print(f"Sent {result['message_id']}")
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
-
The send/reply/draft-send response carries two additive, nullable keys that explain a held send inline (no second `messages.get` call). `result["scan"]` is the vendor-neutral scanner verdict (`ScanSummary`); `result["hold_context"]` (`{"trigger_source", "summary_reasons"}` or `None`) is the policy/
|
|
170
|
+
The send/reply/draft-send response carries two additive, nullable keys that explain a held send inline (no second `messages.get` call). `result["scan"]` is the vendor-neutral scanner verdict (`ScanSummary`); `result["hold_context"]` (`{"trigger_source", "summary_reasons"}` or `None`) is the policy/human-review reason, non-null only when the delivery `status` diverges from `scan["verdict"]` because of a policy/human-review hold — a clean scan held for review by your mailbox policy, or a scanner review-flag held as quarantine on a plan without the review queue (`trigger_source`: `mailbox_policy` | `scanner` | `both`).
|
|
171
171
|
|
|
172
172
|
By default `drafts.send()`, `messages.send()`, and `messages.reply()` return only once the scanner verdict is known, with `scan` and `hold_context` inline. Pass `async_dispatch=True` to `drafts.send()` to send the `Prefer: respond-async` hint. **The hint is advisory — the server decides.** When async dispatch is available the server returns `202` with `status="queued_for_dispatch"` (`AsyncSendAck`); otherwise it ignores the hint and returns a normal `200` `SendMessageResponse`. **Always branch on the result**: `result["status"] == "queued_for_dispatch"` ⇒ `AsyncSendAck`, otherwise `SendMessageResponse`. Poll `messages.get(message_id)` (or handle the lifecycle webhook) until `state` is terminal. Attachment-bearing drafts fail closed on the async path (`400 ATTACHMENTS_REQUIRE_SYNC_SEND`). (`messages.wait()` is a mailbox long-poll for new *inbound* mail, not a way to observe a specific message by ID.)
|
|
173
173
|
|
|
174
174
|
The send endpoint raises `ReplyLayerError` with distinct `.code` values on 409:
|
|
175
|
-
- `DRAFT_REJECTED_BY_RESCAN` — send-time scan flipped the verdict to `block`/`quarantine`. The draft stays in `draft` state; edit the body and retry. `err.details` carries `scan`, `releasable` (`True` for a `quarantine` hold the customer can release via `POST /v1/drafts/:id/release-and-send`, `False` for a terminal `block`), and, when a policy/
|
|
175
|
+
- `DRAFT_REJECTED_BY_RESCAN` — send-time scan flipped the verdict to `block`/`quarantine`. The draft stays in `draft` state; edit the body and retry. `err.details` carries `scan`, `releasable` (`True` for a `quarantine` hold the customer can release via `POST /v1/drafts/:id/release-and-send`, `False` for a terminal `block`), and, when a policy/human-review decision drove the hold, `hold_context`.
|
|
176
176
|
- `DRAFT_ALREADY_SENT` — the draft was already sent (race or retry after success).
|
|
177
177
|
|
|
178
178
|
```python
|
|
@@ -367,11 +367,11 @@ Images are a separately confirmed raw-download family. When `allowed_file_famili
|
|
|
367
367
|
|
|
368
368
|
Human dashboard sessions and admin/pre-scoping keys can download clean stored `metadata_only` attachments, including attachments held back from agent raw-download policy. Agent-role keys remain bound to the mailbox policy gate plus hard safety checks; all callers remain blocked by infected AV verdicts, non-terminal message states, missing stored bytes, and hard attachment blocks.
|
|
369
369
|
|
|
370
|
-
See
|
|
370
|
+
See the Mailboxes API reference at https://replylayer.ai/docs/api/mailboxes for the full contract and known limitations.
|
|
371
371
|
|
|
372
372
|
### Recipient allowlist (mailbox containment)
|
|
373
373
|
|
|
374
|
-
A mailbox is in `blocklist` mode by default — the pre-send gate rejects `suppressed_addresses` hits and allows everyone else. Switching to `allowlist` mode
|
|
374
|
+
A mailbox is in `blocklist` mode by default — the pre-send gate rejects `suppressed_addresses` hits and allows everyone else. Switching to `allowlist` mode contains **agent-origin** outbound to a pre-approved list (plus thread participants): a prompt-injected or compromised **agent key** cannot email outside the list. It is a containment boundary against a hijacked agent, not an all-origin lock — a human send (your dashboard session or an admin API key) is not restricted by the allowlist; only your do-not-contact (suppression) list binds a human send.
|
|
375
375
|
|
|
376
376
|
```python
|
|
377
377
|
# Populate the allowlist first. Admin-only — agent keys get 403 INSUFFICIENT_SCOPE.
|
|
@@ -393,7 +393,7 @@ rl.mailboxes.allowlist.delete(mailbox["id"], "partner@corp.com", force_empty=Tru
|
|
|
393
393
|
|
|
394
394
|
A send/reply/draft-send to a recipient on your do-not-contact (suppression) list raises `ReplyLayerError` with `.code == "RECIPIENT_SUPPRESSED"` (HTTP 403, `details["reason"] == "suppressed"`). This is terminal — escalate, don't retry; remove the suppression or send to a different recipient.
|
|
395
395
|
|
|
396
|
-
Allowlist mutations are admin-only — granting
|
|
396
|
+
Allowlist mutations are admin-only — granting mutation to an LLM defeats the agent-containment boundary. Agents *can* `list` (so they can see what they're allowed to email) but not `add`/`add_bulk`/`delete`. Three new webhook events: `recipient_allowlist.added`, `recipient_allowlist.removed`, `mailbox.recipient_policy_changed`.
|
|
397
397
|
|
|
398
398
|
### Domain entries
|
|
399
399
|
|
|
@@ -480,7 +480,7 @@ if ctx and ctx.get("instruction_trust"):
|
|
|
480
480
|
# content-safety judgment.
|
|
481
481
|
print(ctx["guidance"])
|
|
482
482
|
print(ctx["instruction_trust"])
|
|
483
|
-
# {"version": "v1", "match": "address", "verified_domain": ..., "verdict": "verified_aligned", "provenance": "
|
|
483
|
+
# {"version": "v1", "match": "address", "verified_domain": ..., "verdict": "verified_aligned", "provenance": "managed"}
|
|
484
484
|
```
|
|
485
485
|
|
|
486
486
|
A human account owner enables the mailbox mode and the key's capability, and designates the trusted sender, from the dashboard (each a loosening change requiring session re-auth). Both the mailbox mode and the per-key capability default to off, so existing integrations are unaffected until a customer opts in. This is a read-path signal only — a copied or hijacked API key cannot self-grant the capability, and there is nothing for a client to set to request it.
|
|
@@ -529,7 +529,7 @@ Error classes: `ReplyLayerError` (base), `AuthenticationError` (401), `Forbidden
|
|
|
529
529
|
|
|
530
530
|
## Webhook signature verification
|
|
531
531
|
|
|
532
|
-
> For a full integration guide (event catalog, retry behavior, idempotency, security, troubleshooting), see
|
|
532
|
+
> For a full integration guide (event catalog, retry behavior, idempotency, security, troubleshooting), see https://replylayer.ai/docs/webhooks.
|
|
533
533
|
|
|
534
534
|
```python
|
|
535
535
|
from replylayer import verify_webhook_signature
|
|
@@ -464,8 +464,9 @@ class SyncMailboxes:
|
|
|
464
464
|
# the field". Mutually exclusive with raw recipient_policy_mode/
|
|
465
465
|
# agent_send_containment in the same call (the server returns 400).
|
|
466
466
|
agent_send_policy: AgentSendPolicy | None = None,
|
|
467
|
-
#
|
|
468
|
-
#
|
|
467
|
+
# Deprecated + ignored by the server: opening the agent on an allowlist
|
|
468
|
+
# mailbox no longer requires consent (the allowlist only bound the
|
|
469
|
+
# agent). Kept for back-compat. None/False omits it.
|
|
469
470
|
confirm_open_human_sends: bool | None = None,
|
|
470
471
|
# PR 8.1 — per-detector redaction visibility. Pass {} to reset to
|
|
471
472
|
# platform default; pass a partial map (e.g.
|
|
@@ -555,11 +556,12 @@ class SyncMailboxes:
|
|
|
555
556
|
stored field). ``"open"`` lets the agent send to any new recipient;
|
|
556
557
|
``"restricted"`` gates it. Session/admin only server-side.
|
|
557
558
|
|
|
558
|
-
Opening on an allowlist mailbox
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
559
|
+
Opening on an allowlist mailbox now succeeds directly — the server
|
|
560
|
+
atomically flips the mailbox to blocklist and clears agent containment.
|
|
561
|
+
No consent is required (the allowlist only ever bound the agent, so
|
|
562
|
+
opening it does not affect human sends). ``confirm_open_human_sends`` is
|
|
563
|
+
**deprecated and ignored** by the server; it is still accepted here for
|
|
564
|
+
back-compat.
|
|
563
565
|
"""
|
|
564
566
|
return self.update(
|
|
565
567
|
id,
|
|
@@ -707,8 +709,9 @@ class AsyncMailboxes:
|
|
|
707
709
|
# Mutually exclusive with raw recipient_policy_mode/agent_send_containment
|
|
708
710
|
# in the same call (the server returns 400).
|
|
709
711
|
agent_send_policy: AgentSendPolicy | None = None,
|
|
710
|
-
#
|
|
711
|
-
#
|
|
712
|
+
# Deprecated + ignored by the server: opening the agent on an allowlist
|
|
713
|
+
# mailbox no longer requires consent (the allowlist only bound the
|
|
714
|
+
# agent). Kept for back-compat. None/False omits it.
|
|
712
715
|
confirm_open_human_sends: bool | None = None,
|
|
713
716
|
# PR 8.1 — per-detector redaction visibility. Pass {} to reset to
|
|
714
717
|
# platform default; pass a partial map to disable redaction on
|
|
@@ -796,10 +799,11 @@ class AsyncMailboxes:
|
|
|
796
799
|
|
|
797
800
|
``"open"`` lets the agent send to any new recipient; ``"restricted"``
|
|
798
801
|
gates it. Session/admin only server-side. Opening on an allowlist
|
|
799
|
-
mailbox
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
802
|
+
mailbox now succeeds directly — the server atomically flips the mailbox
|
|
803
|
+
to blocklist and clears agent containment, with no consent required
|
|
804
|
+
(human sends are never allowlist-restricted). ``confirm_open_human_sends``
|
|
805
|
+
is **deprecated and ignored** by the server; still accepted for
|
|
806
|
+
back-compat.
|
|
803
807
|
"""
|
|
804
808
|
return await self.update(
|
|
805
809
|
id,
|
|
@@ -175,8 +175,8 @@ RecipientPolicyMode = Literal["blocklist", "allowlist"]
|
|
|
175
175
|
AgentSendPolicy = Literal["restricted", "open"]
|
|
176
176
|
|
|
177
177
|
# When agent_send_policy == "restricted", WHY:
|
|
178
|
-
# - "mailbox_allowlist": the
|
|
179
|
-
# the
|
|
178
|
+
# - "mailbox_allowlist": the mailbox is in allowlist mode — the AGENT is
|
|
179
|
+
# restricted to the allowlist; human sends are unaffected (agent-only).
|
|
180
180
|
# - "agent_containment": blocklist mailbox + the R3 containment overlay.
|
|
181
181
|
# - None: agent_send_policy == "open".
|
|
182
182
|
AgentSendRestrictedBy = Literal["mailbox_allowlist", "agent_containment"] | None
|
|
@@ -204,7 +204,7 @@ class FirewallBlock(TypedDict):
|
|
|
204
204
|
matched_field: Literal["envelope", "from"] | None
|
|
205
205
|
matched_pattern: str | None
|
|
206
206
|
reason_code: Literal["SENDER_BLOCKED", "SENDER_NOT_ON_ALLOWLIST"]
|
|
207
|
-
|
|
207
|
+
matched_list: Literal["account_blocklist", "mailbox_allowlist"] | None
|
|
208
208
|
mode: SenderPolicyMode
|
|
209
209
|
|
|
210
210
|
# Sprint 039 — allowlist + suppression entries may be either an exact email
|
|
@@ -413,30 +413,32 @@ class Mailbox(TypedDict):
|
|
|
413
413
|
status: MailboxStatus
|
|
414
414
|
scanner_policy: ScannerPolicy | None
|
|
415
415
|
pii_mode: PiiMode
|
|
416
|
-
#
|
|
417
|
-
#
|
|
418
|
-
#
|
|
419
|
-
# the
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
416
|
+
# Attachment / outbound-attachment CONSENT bookkeeping. SESSION-ONLY: the
|
|
417
|
+
# API returns these only to a dashboard (session-cookie) caller and strips
|
|
418
|
+
# them from every Bearer / API-key response — which includes this SDK. They
|
|
419
|
+
# drive the human re-acceptance banner and an agent cannot act on them (the
|
|
420
|
+
# acceptance routes are session + re-auth only), so they are NotRequired and,
|
|
421
|
+
# for SDK callers, always absent. Read `attachment_exposure_mode` instead.
|
|
422
|
+
attachment_access_enabled: NotRequired[bool]
|
|
423
|
+
attachment_access_accepted_at: NotRequired[str | None]
|
|
424
|
+
attachment_access_accepted_version: NotRequired[str | None]
|
|
423
425
|
attachment_exposure_mode: AttachmentExposureMode
|
|
424
426
|
attachment_allowed_file_families: list[AttachmentAllowedFileFamily]
|
|
425
|
-
attachment_reauth_at: str | None
|
|
426
|
-
attachment_policy_version: str | None
|
|
427
|
-
image_raw_download_confirmed: bool
|
|
428
|
-
current_image_risk_version: str
|
|
429
|
-
attachment_image_access_accepted_at: str | None
|
|
430
|
-
attachment_image_access_accepted_version: str | None
|
|
431
|
-
#
|
|
432
|
-
#
|
|
433
|
-
current_disclaimer_version: str
|
|
427
|
+
attachment_reauth_at: NotRequired[str | None]
|
|
428
|
+
attachment_policy_version: NotRequired[str | None]
|
|
429
|
+
image_raw_download_confirmed: NotRequired[bool]
|
|
430
|
+
current_image_risk_version: NotRequired[str]
|
|
431
|
+
attachment_image_access_accepted_at: NotRequired[str | None]
|
|
432
|
+
attachment_image_access_accepted_version: NotRequired[str | None]
|
|
433
|
+
# The current disclaimer version, for the dashboard re-acceptance banner.
|
|
434
|
+
# Session-only (see the consent note above) — absent on SDK/API-key reads.
|
|
435
|
+
current_disclaimer_version: NotRequired[str]
|
|
434
436
|
# True when the mailbox is on the legacy compat path
|
|
435
437
|
# (attachment_exposure_mode is null but attachment_access_enabled is true).
|
|
436
438
|
# Drives the dashboard re-acceptance banner; flips to false after the
|
|
437
|
-
# customer accepts an explicit mode.
|
|
438
|
-
# docs/runbooks/legacy-attachment-access-migration.md.
|
|
439
|
-
legacy_wildcard_active: bool
|
|
439
|
+
# customer accepts an explicit mode. Session-only — absent on SDK/API-key
|
|
440
|
+
# reads. See docs/runbooks/legacy-attachment-access-migration.md.
|
|
441
|
+
legacy_wildcard_active: NotRequired[bool]
|
|
440
442
|
# Migration 035 — default outbound sub-addressing rewrite mode.
|
|
441
443
|
default_subaddress_mode: SubaddressMode
|
|
442
444
|
# Migration 036 — recipient policy mode.
|
|
@@ -1034,7 +1036,7 @@ class InstructionTrustBasis(TypedDict):
|
|
|
1034
1036
|
# Verified sender's domain; None under pii_mode=redacted.
|
|
1035
1037
|
verified_domain: str | None
|
|
1036
1038
|
verdict: Literal["verified_aligned"]
|
|
1037
|
-
provenance: Literal["
|
|
1039
|
+
provenance: Literal["managed"]
|
|
1038
1040
|
|
|
1039
1041
|
|
|
1040
1042
|
class AgentSafetyContext(TypedDict):
|
|
@@ -1073,7 +1075,7 @@ class SenderAuthentication(TypedDict):
|
|
|
1073
1075
|
verdict: SenderAuthVerdict
|
|
1074
1076
|
from_domain: str | None
|
|
1075
1077
|
signing_domain: str | None
|
|
1076
|
-
provenance: Literal["
|
|
1078
|
+
provenance: Literal["managed", "self_hosted_imap"]
|
|
1077
1079
|
|
|
1078
1080
|
|
|
1079
1081
|
class SenderAuthenticationCompact(TypedDict):
|
|
@@ -45,7 +45,7 @@ def test_review_queued_payload_constructs_with_required_fields() -> None:
|
|
|
45
45
|
"direction": "outbound",
|
|
46
46
|
"subject": "Quarterly review",
|
|
47
47
|
"recipient": "ceo@corp.example",
|
|
48
|
-
"summary_reasons": [
|
|
48
|
+
"summary_reasons": ['This mailbox requires human approval for all outbound mail (review setting: "All outbound").'],
|
|
49
49
|
"origin": "fresh_send",
|
|
50
50
|
}
|
|
51
51
|
assert payload["origin"] == "fresh_send"
|
|
@@ -19,7 +19,7 @@ def test_instruction_trust_basis_type_is_exported():
|
|
|
19
19
|
"match": "address",
|
|
20
20
|
"verified_domain": "partner.com",
|
|
21
21
|
"verdict": "verified_aligned",
|
|
22
|
-
"provenance": "
|
|
22
|
+
"provenance": "managed",
|
|
23
23
|
}
|
|
24
24
|
assert basis["verified_domain"] == "partner.com"
|
|
25
25
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|