generated from coulomb/repo-seed
1521 lines
55 KiB
Markdown
1521 lines
55 KiB
Markdown
# EmailAdapterSpecification.md
|
||
|
||
## 1. Document Status
|
||
|
||
**Document:** EmailAdapterSpecification.md
|
||
**Project:** email-connect
|
||
**Target Integration:** coordination-engine
|
||
**Adapter Contract:** AdapterInterfaceSpecification.md v1.0
|
||
**Status:** Draft v1.0
|
||
**Primary Scope:** Email protocol and provider integration as a coordination-engine adapter
|
||
|
||
## 2. Purpose
|
||
|
||
This document specifies how `email-connect` models email as a communication protocol and how it integrates with `coordination-engine`.
|
||
|
||
`email-connect` is the reference adapter for the `coordination-engine` adapter layer. It provides email-specific sending, event ingestion, event normalization, evidence classification, message timelines, endpoint state, and provider abstraction.
|
||
|
||
The central design objective is to make email useful as a notification and communication channel without overclaiming what email can prove.
|
||
|
||
Email can often provide evidence that:
|
||
|
||
* a message was accepted by an outbound provider
|
||
* a message was accepted by a recipient mail server
|
||
* a message was rejected, bounced, deferred, delayed, dropped, or suppressed
|
||
* a user agent, proxy, scanner, or recipient-like actor interacted with tracking mechanisms
|
||
* a recipient or mailbox system replied, complained, unsubscribed, or emitted an out-of-office response
|
||
|
||
Email usually cannot prove by itself that:
|
||
|
||
* the message reached the recipient’s inbox
|
||
* the intended human recipient saw the message
|
||
* the recipient understood the message
|
||
* the intended recipient clicked a link
|
||
* the recipient accessed the primary payload
|
||
* the coordination case result was achieved
|
||
|
||
Therefore, `email-connect` reports email evidence. `coordination-engine` decides what that evidence means for a coordination case.
|
||
|
||
## 3. Core Principle
|
||
|
||
Email is a weak-to-medium evidence channel for awareness. It is not a reliable proof channel for payload delivery or intended-recipient action.
|
||
|
||
The adapter MUST distinguish:
|
||
|
||
```text
|
||
technical email transport evidence
|
||
mailbox or endpoint inference
|
||
engagement evidence
|
||
identity-bound interaction evidence from other systems
|
||
coordination result evidence
|
||
```
|
||
|
||
In the coordination model, most email events map to `undef` or weak evidence unless paired with stronger evidence from another adapter such as `portal-connect`, `identity-connect`, `signature-connect`, or `payment-connect`.
|
||
|
||
## 4. Architectural Role
|
||
|
||
### 4.1 Standalone Role
|
||
|
||
As a standalone component, `email-connect` provides:
|
||
|
||
* provider-neutral email sending
|
||
* transactional email dispatch
|
||
* provider event ingestion
|
||
* bounce classification
|
||
* complaint and unsubscribe handling
|
||
* suppression list management
|
||
* open and click tracking interpretation
|
||
* recipient endpoint diagnostics
|
||
* email message timelines
|
||
* deliverability diagnostics
|
||
* normalized email event stream
|
||
* evidence-oriented message assessment
|
||
|
||
### 4.2 coordination-engine Adapter Role
|
||
|
||
As a `coordination-engine` adapter, `email-connect` provides:
|
||
|
||
#### Actions
|
||
|
||
* `notification.send`
|
||
* `notification.send_reminder`
|
||
* `notification.register_tracking_context`
|
||
* `recipient.suppress`
|
||
* `recipient.unsuppress`
|
||
* optionally `notification.cancel` where supported by queue state
|
||
* optionally `notification.schedule` where supported
|
||
|
||
#### Signals
|
||
|
||
* `notification.attempt.accepted_by_adapter`
|
||
* `notification.attempt.rejected_by_adapter`
|
||
* `notification.attempt.accepted_by_provider`
|
||
* `notification.attempt.rejected_by_provider`
|
||
* `notification.attempt.queued`
|
||
* `notification.attempt.delayed`
|
||
* `notification.endpoint.accepted`
|
||
* `notification.endpoint.deferred`
|
||
* `notification.endpoint.rejected_temporary`
|
||
* `notification.endpoint.rejected_permanent`
|
||
* `notification.endpoint.unreachable`
|
||
* `notification.channel.complaint_received`
|
||
* `notification.channel.unsubscribe_received`
|
||
* `notification.channel.suppression_added`
|
||
* `notification.channel.reputation_warning`
|
||
* `interaction.notification.opened`
|
||
* `interaction.proxy_or_privacy_interaction`
|
||
* `interaction.link.clicked`
|
||
* `interaction.scanner_or_bot_interaction`
|
||
* `interaction.unverified_actor_interaction`
|
||
* `interaction.reply_received`
|
||
* `interaction.out_of_office_received`
|
||
|
||
## 5. Relationship to coordination-engine
|
||
|
||
`email-connect` does not own:
|
||
|
||
* `CoordinationCase`
|
||
* intended result evaluation
|
||
* participant-level success
|
||
* case-level success
|
||
* legal delivery interpretation
|
||
* portal access
|
||
* payload retrieval
|
||
* contract acceptance
|
||
* payment settlement
|
||
* signature completion
|
||
* multi-channel escalation policy
|
||
|
||
`email-connect` owns:
|
||
|
||
* email send requests
|
||
* email provider abstraction
|
||
* email provider identifiers
|
||
* email event ingestion
|
||
* email event classification
|
||
* email-native status timeline
|
||
* email endpoint diagnostics
|
||
* email evidence mapping
|
||
* suppression and complaint handling
|
||
* email-specific uncertainty classification
|
||
|
||
The boundary rule is:
|
||
|
||
> email-connect reports what happened in the email channel and what that may indicate. coordination-engine decides what that means for the coordination case.
|
||
|
||
## 6. Email as Notification, Not Delivery
|
||
|
||
Within `coordination-engine`, email is primarily a **notification channel**.
|
||
|
||
Email may contain small inline payloads, but in the primary architecture it usually points participants toward an action surface such as:
|
||
|
||
* a portal page
|
||
* a secure document download
|
||
* a payment page
|
||
* a contract signing flow
|
||
* an approval screen
|
||
* a form submission page
|
||
|
||
The actual payload delivery or result-relevant interaction should usually be observed by another adapter.
|
||
|
||
Example:
|
||
|
||
```text
|
||
email-connect:
|
||
notification.endpoint.accepted
|
||
interaction.link.clicked
|
||
|
||
portal-connect:
|
||
identity.actor_authenticated
|
||
delivery.payload.downloaded
|
||
|
||
coordination-engine:
|
||
participant result satisfied
|
||
```
|
||
|
||
Email acceptance by the recipient mail server is not payload delivery. It is technical notification evidence.
|
||
|
||
## 7. Email Message Lifecycle Model
|
||
|
||
`email-connect` models an email notification as a lifecycle with multiple observable phases.
|
||
|
||
```text
|
||
message.created
|
||
message.rendered
|
||
message.render_failed
|
||
message.suppression_checked
|
||
message.send_requested
|
||
message.accepted_by_adapter
|
||
message.rejected_by_adapter
|
||
message.accepted_by_provider
|
||
message.rejected_by_provider
|
||
message.queued
|
||
message.provider_dropped
|
||
message.provider_suppressed
|
||
message.deferred
|
||
message.endpoint_accepted
|
||
message.soft_bounced
|
||
message.hard_bounced
|
||
message.async_bounced
|
||
message.delayed
|
||
message.expired_without_delivery
|
||
message.opened
|
||
message.opened_proxy_like
|
||
message.clicked
|
||
message.clicked_scanner_like
|
||
message.clicked_unverified
|
||
message.replied
|
||
message.out_of_office_received
|
||
message.complaint_received
|
||
message.unsubscribe_received
|
||
message.suppression_added
|
||
```
|
||
|
||
The lifecycle is not strictly linear. Events may arrive late, out of order, duplicated, or in conflict.
|
||
|
||
Example:
|
||
|
||
```text
|
||
provider accepted
|
||
endpoint accepted
|
||
later async bounce received
|
||
```
|
||
|
||
The adapter MUST preserve all meaningful events and avoid collapsing the message into a single final status too early.
|
||
|
||
## 8. Email Attempt vs Email Message vs Recipient
|
||
|
||
The adapter MUST distinguish at least three layers.
|
||
|
||
### 8.1 EmailMessage
|
||
|
||
The logical message created by a client or coordination case.
|
||
|
||
```yaml
|
||
EmailMessage:
|
||
email_message_id: string
|
||
coordination_case_id: string?
|
||
participant_id: string?
|
||
purpose: string?
|
||
subject: string?
|
||
template_ref: string?
|
||
content_ref: string?
|
||
tracking_context: TrackingContext
|
||
created_at: timestamp
|
||
```
|
||
|
||
### 8.2 EmailAttempt
|
||
|
||
One send attempt for one message through one provider/configuration.
|
||
|
||
```yaml
|
||
EmailAttempt:
|
||
email_attempt_id: string
|
||
email_message_id: string
|
||
provider_name: string
|
||
provider_account_ref: string?
|
||
from_endpoint: EmailEndpoint
|
||
to_endpoint: EmailEndpoint
|
||
cc_endpoints:
|
||
- EmailEndpoint
|
||
bcc_endpoints:
|
||
- EmailEndpoint
|
||
provider_message_id: string?
|
||
adapter_operation_id: string?
|
||
state: EmailAttemptState
|
||
created_at: timestamp
|
||
updated_at: timestamp
|
||
```
|
||
|
||
### 8.3 EmailEndpoint
|
||
|
||
The target email endpoint.
|
||
|
||
```yaml
|
||
EmailEndpoint:
|
||
endpoint_id: string?
|
||
email_address: string
|
||
display_name: string?
|
||
endpoint_role: from | to | cc | bcc | reply_to | return_path
|
||
verification_state: unknown | syntax_validated | verified | suspected_invalid | invalid
|
||
suppression_state: active | suppressed | complaint_suppressed | bounce_suppressed | unsubscribed | unknown
|
||
metadata: object?
|
||
```
|
||
|
||
Per-recipient modeling is required because one email message may have multiple recipients and different outcomes per recipient.
|
||
|
||
For coordination-engine integration, the preferred mode is one logical notification per participant endpoint.
|
||
|
||
## 9. Email Attempt States
|
||
|
||
The adapter SHOULD support these attempt states:
|
||
|
||
```text
|
||
created
|
||
rendered
|
||
render_failed
|
||
suppressed
|
||
send_requested
|
||
accepted_by_adapter
|
||
rejected_by_adapter
|
||
accepted_by_provider
|
||
rejected_by_provider
|
||
queued
|
||
scheduled
|
||
provider_dropped
|
||
provider_suppressed
|
||
deferred
|
||
endpoint_accepted
|
||
soft_bounced
|
||
hard_bounced
|
||
async_bounced
|
||
delayed
|
||
expired_without_delivery
|
||
complaint_received
|
||
unsubscribe_received
|
||
unknown
|
||
```
|
||
|
||
These states are email-native. They are not coordination result states.
|
||
|
||
## 10. Email Evidence Assessment
|
||
|
||
`email-connect` should provide an email-native assessment separate from coordination-engine state.
|
||
|
||
```yaml
|
||
EmailEvidenceAssessment:
|
||
email_message_id: string
|
||
participant_id: string?
|
||
category: success | fail | undef
|
||
subclass: string
|
||
confidence: low | medium | high
|
||
strongest_signal: string?
|
||
evidence_summary:
|
||
- string
|
||
recommended_coordination_interpretation: string?
|
||
```
|
||
|
||
The assessment categories are adapter-level hints.
|
||
|
||
### 10.1 Email Adapter Success
|
||
|
||
Email-level `success` means only that the email channel produced strong email-channel evidence.
|
||
|
||
Possible subclasses:
|
||
|
||
```text
|
||
success.endpoint_accepted
|
||
success.reply_received
|
||
success.human_like_open
|
||
success.human_like_click
|
||
```
|
||
|
||
Important: These are not automatically coordination success.
|
||
|
||
`success.endpoint_accepted` is technical success for the email transport, but usually maps to coordination `undef.technical_acceptance_only`.
|
||
|
||
### 10.2 Email Adapter Fail
|
||
|
||
Email-level `fail` indicates strong evidence that the email channel failed or should not be used.
|
||
|
||
Subclasses:
|
||
|
||
```text
|
||
fail.invalid_address
|
||
fail.missing_address
|
||
fail.render_failed
|
||
fail.template_invalid
|
||
fail.provider_rejected
|
||
fail.provider_dropped
|
||
fail.provider_suppressed
|
||
fail.hard_bounce
|
||
fail.domain_not_found
|
||
fail.mailbox_not_found
|
||
fail.authentication_rejected
|
||
fail.policy_rejected
|
||
fail.content_rejected
|
||
fail.message_too_large
|
||
fail.complaint_received
|
||
fail.unsubscribed
|
||
fail.suppressed
|
||
fail.expired_without_delivery
|
||
```
|
||
|
||
### 10.3 Email Adapter Undef
|
||
|
||
Email-level `undef` is the normal state when the adapter cannot infer reliable human awareness.
|
||
|
||
Subclasses:
|
||
|
||
```text
|
||
undef.pending
|
||
undef.provider_accepted_only
|
||
undef.queued
|
||
undef.deferred
|
||
undef.delayed
|
||
undef.endpoint_accepted_only
|
||
undef.no_signal
|
||
undef.weak_positive
|
||
undef.proxy_open
|
||
undef.scanner_click
|
||
undef.unverified_click
|
||
undef.identity_uncertain
|
||
undef.mailbox_possibly_reached
|
||
undef.possibly_spam_or_quarantine
|
||
undef.possibly_silent_drop
|
||
undef.forwarding_unknown
|
||
undef.catch_all_domain
|
||
undef.conflicting_evidence
|
||
undef.channel_degraded
|
||
undef.recipient_reported_missing
|
||
undef.out_of_office
|
||
```
|
||
|
||
The `undef` category must not be empty because email does not provide guaranteed delivery or awareness evidence.
|
||
|
||
## 11. Detailed Email Scenario Classification
|
||
|
||
### 11.1 Pre-Send Scenarios
|
||
|
||
| Scenario | Email assessment | Normalized event | Notes |
|
||
| -------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------- | -------------------------------------------- |
|
||
| Missing address | `fail.missing_address` | `notification.attempt.rejected_by_adapter` | No send possible |
|
||
| Invalid address syntax | `fail.invalid_address` | `notification.attempt.rejected_by_adapter` | Strong local failure |
|
||
| Template rendering failure | `fail.render_failed` | `notification.attempt.rejected_by_adapter` or `payload.validation_failed` | Notification unusable |
|
||
| Invalid generated link | `fail.template_invalid` | `notification.attempt.rejected_by_adapter` | Especially relevant for portal links |
|
||
| Suppression hit | `fail.suppressed` | `notification.channel.suppression_added` or action rejected | May be due to bounce, complaint, unsubscribe |
|
||
| Adapter config missing | `fail.provider_rejected` | `system.adapter.health_changed` + action error | Operational failure |
|
||
| Provider unavailable before acceptance | `undef.pending` or `fail.provider_rejected` | action result error | Depends on retryability |
|
||
| Submission timeout | `undef.pending` | action result unknown | Could have been accepted despite timeout |
|
||
|
||
### 11.2 Provider-Side Scenarios
|
||
|
||
| Scenario | Email assessment | Normalized event | Notes |
|
||
| ----------------------------- | ------------------------------------------- | ------------------------------------------------------------------------ | -------------------------------- |
|
||
| Adapter accepted request | `undef.pending` | `notification.attempt.accepted_by_adapter` | Adapter accepted work |
|
||
| Provider accepted message | `undef.provider_accepted_only` | `notification.attempt.accepted_by_provider` | Sending began |
|
||
| Provider rejected message | `fail.provider_rejected` | `notification.attempt.rejected_by_provider` | Strong failure |
|
||
| Provider queued message | `undef.queued` | `notification.attempt.queued` | Pending |
|
||
| Provider delayed message | `undef.delayed` | `notification.attempt.delayed` | Pending, may degrade |
|
||
| Provider dropped message | `fail.provider_dropped` | `notification.attempt.rejected_by_provider` or dropped-specific metadata | Message may never leave provider |
|
||
| Provider suppressed recipient | `fail.provider_suppressed` | `notification.channel.suppression_added` | Channel blocked |
|
||
| Provider rate-limited | `undef.pending` | `notification.attempt.delayed` | Retry or wait |
|
||
| Provider quota exceeded | `fail.provider_rejected` or `undef.pending` | action error | Policy-dependent |
|
||
|
||
### 11.3 Recipient Mail Server Scenarios
|
||
|
||
| Scenario | Email assessment | Normalized event | Notes |
|
||
| ----------------------------- | --------------------------------------------------- | ------------------------------------------------------- | -------------------------------------- |
|
||
| MX accepted | `undef.endpoint_accepted_only` | `notification.endpoint.accepted` | Does not prove inbox placement |
|
||
| Temporary SMTP deferral | `undef.deferred` | `notification.endpoint.deferred` | Retry window active |
|
||
| Soft bounce | `undef.deferred` or `fail.expired_without_delivery` | `notification.endpoint.rejected_temporary` | Becomes failure after retry policy |
|
||
| Hard bounce unknown user | `fail.mailbox_not_found` | `notification.endpoint.rejected_permanent` | Strong endpoint failure |
|
||
| Domain not found | `fail.domain_not_found` | `notification.endpoint.unreachable` | Strong failure |
|
||
| SPF/DKIM/DMARC rejection | `fail.authentication_rejected` | `notification.endpoint.rejected_permanent` | Strong sender config/channel failure |
|
||
| Content policy rejection | `fail.content_rejected` | `notification.endpoint.rejected_permanent` | Strong failure |
|
||
| Recipient mailbox full | `undef.deferred` | `notification.endpoint.rejected_temporary` | May later succeed |
|
||
| Greylisting | `undef.deferred` | `notification.endpoint.deferred` | Usually retryable |
|
||
| Async bounce after acceptance | `fail.async_bounced` | `notification.endpoint.rejected_permanent` or temporary | Important late failure |
|
||
| Catch-all domain acceptance | `undef.catch_all_domain` | `notification.endpoint.accepted` + metadata | Acceptance is especially weak |
|
||
| Forwarding unknown | `undef.forwarding_unknown` | `notification.endpoint.accepted` + metadata if known | Intended recipient awareness uncertain |
|
||
|
||
### 11.4 Mailbox and Inbox Inference Scenarios
|
||
|
||
| Scenario | Email assessment | Notes |
|
||
| --------------------------------- | -------------------------------------------------------- | ----- |
|
||
| Delivered to inbox | Usually not directly observable | |
|
||
| Delivered to spam/junk | Usually not directly observable | |
|
||
| Delivered to promotions/other tab | Usually not directly observable | |
|
||
| Delivered then moved by user rule | Usually not directly observable | |
|
||
| Delivered then silently dropped | Usually not directly observable | |
|
||
| Quarantined by enterprise gateway | Usually not directly observable unless feedback received | |
|
||
| Accepted by shared mailbox | Awareness of intended person uncertain | |
|
||
| Accepted by inactive mailbox | Endpoint works, awareness uncertain | |
|
||
| Out-of-office reply | Mailbox likely reached, but action uncertain | |
|
||
| User reports missing email | Conflicting evidence; treat as suspicious | |
|
||
| User reports spam-folder location | Useful diagnostic; still not initial success | |
|
||
|
||
These cases should usually map to `undef.*` because email lacks reliable inbox placement proof.
|
||
|
||
### 11.5 Open Tracking Scenarios
|
||
|
||
| Scenario | Email assessment | Normalized event | Notes |
|
||
| ----------------------------- | -------------------------------------------------- | -------------------------------------------- | --------------------------------- |
|
||
| No open | `undef.no_signal` | none | Absence of open proves nothing |
|
||
| Pixel loaded normally | `undef.weak_positive` or `success.human_like_open` | `interaction.notification.opened` | Weak-to-medium |
|
||
| Apple/privacy proxy-like open | `undef.proxy_open` | `interaction.proxy_or_privacy_interaction` | Not human proof |
|
||
| Gmail/image proxy open | `undef.proxy_open` or weak positive | `interaction.proxy_or_privacy_interaction` | Ambiguous |
|
||
| Security scanner image load | `undef.proxy_open` | `interaction.proxy_or_privacy_interaction` | Automated |
|
||
| Multiple human-like opens | `success.human_like_open` | `interaction.notification.opened` | Still not payload delivery |
|
||
| Open from unusual geography | `undef.identity_uncertain` | `interaction.notification.opened` + metadata | May be proxy, forwarding, or risk |
|
||
|
||
Open tracking MUST NOT be treated as strong coordination success.
|
||
|
||
### 11.6 Click Tracking Scenarios
|
||
|
||
| Scenario | Email assessment | Normalized event | Notes |
|
||
| -------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------ | -------------------------------- |
|
||
| No click | `undef.no_signal` | none | No evidence of interaction |
|
||
| Scanner-like click | `undef.scanner_click` | `interaction.scanner_or_bot_interaction` | Do not count as recipient action |
|
||
| Link prefetch | `undef.scanner_click` | `interaction.scanner_or_bot_interaction` | Common in enterprise security |
|
||
| Unverified click | `undef.unverified_click` | `interaction.unverified_actor_interaction` | Identity uncertain |
|
||
| Human-like click | `success.human_like_click` | `interaction.link.clicked` | Medium, still not identity-bound |
|
||
| Click followed by authenticated portal login | email event remains weak; portal event is strong | Portal adapter provides identity evidence | |
|
||
| Click followed by payload download | email contributed path evidence; delivery evidence is decisive | Delivery/portal event closes case | |
|
||
| Token invalid after click | `fail.template_invalid` or `undef.identity_uncertain` | Depends on cause | |
|
||
| Expired link clicked | `undef.weak_positive` | Awareness possible, action path failed | |
|
||
| Forwarded link clicked | `undef.identity_uncertain` | Intended recipient unknown | |
|
||
|
||
The adapter SHOULD classify likely scanner clicks using timing, user-agent, IP ranges, known security vendors, HEAD/GET patterns, link fan-out, and immediate multi-link access where available.
|
||
|
||
### 11.7 Reply and Auto-Reply Scenarios
|
||
|
||
| Scenario | Email assessment | Normalized event | Notes |
|
||
| ----------------------------------- | ---------------------------------------------- | --------------------------------------- | -------------------------------------------------------------- |
|
||
| Human reply received | `success.reply_received` | `interaction.reply_received` | Strong awareness signal, identity still may require validation |
|
||
| Out-of-office reply | `undef.out_of_office` | `interaction.out_of_office_received` | Mailbox reached, action uncertain |
|
||
| Auto-reply from ticket system | `undef.identity_uncertain` | `interaction.reply_received` + metadata | System interaction |
|
||
| Bounce-like auto-response | classify as bounce if parseable | notification endpoint event | Must parse carefully |
|
||
| Reply from delegated/shared mailbox | `undef.identity_uncertain` or medium awareness | depends on participant model | |
|
||
|
||
### 11.8 Complaint, Unsubscribe, and Suppression
|
||
|
||
| Scenario | Email assessment | Normalized event | Notes |
|
||
| --------------------------- | ------------------------- | ------------------------------------------- | ---------------------------------- |
|
||
| Spam complaint | `fail.complaint_received` | `notification.channel.complaint_received` | Negative channel evidence |
|
||
| Unsubscribe | `fail.unsubscribed` | `notification.channel.unsubscribe_received` | Future email use constrained |
|
||
| Bounce suppression added | `fail.suppressed` | `notification.channel.suppression_added` | Channel blocked |
|
||
| Complaint suppression added | `fail.suppressed` | `notification.channel.suppression_added` | Channel blocked |
|
||
| Manual suppression | `fail.suppressed` | `notification.channel.suppression_added` | Operator action |
|
||
| Suppression removed | active/unknown | `notification.channel.suppression_removed` | Channel may become available again |
|
||
|
||
For legally or operationally required notifications, unsubscribe semantics must be scenario-specific. `email-connect` records the event; `coordination-engine` decides whether email remains permissible.
|
||
|
||
## 12. Adapter-to-Coordination Mapping
|
||
|
||
### 12.1 Core Mapping Table
|
||
|
||
| Email-native event | Email assessment | coordination-engine event | Coordination interpretation |
|
||
| ------------------ | ------------------------------------------- | ------------------------------------------------------- | --------------------------------- |
|
||
| message created | `undef.pending` | `notification.attempt.created` | Attempt exists |
|
||
| render failed | `fail.render_failed` | `notification.attempt.rejected_by_adapter` | Notification failed before send |
|
||
| adapter accepted | `undef.pending` | `notification.attempt.accepted_by_adapter` | Work accepted |
|
||
| adapter rejected | `fail.provider_rejected` or validation fail | `notification.attempt.rejected_by_adapter` | Attempt failed |
|
||
| provider accepted | `undef.provider_accepted_only` | `notification.attempt.accepted_by_provider` | Weak send evidence |
|
||
| provider rejected | `fail.provider_rejected` | `notification.attempt.rejected_by_provider` | Strong attempt failure |
|
||
| provider queued | `undef.queued` | `notification.attempt.queued` | Pending |
|
||
| provider delayed | `undef.delayed` | `notification.attempt.delayed` | Pending/degraded |
|
||
| provider dropped | `fail.provider_dropped` | `notification.attempt.rejected_by_provider` | Strong failure |
|
||
| MX accepted | `undef.endpoint_accepted_only` | `notification.endpoint.accepted` | Technical delivery only |
|
||
| temporary deferral | `undef.deferred` | `notification.endpoint.deferred` | Pending |
|
||
| soft bounce | `undef.deferred` | `notification.endpoint.rejected_temporary` | Retryable/pending |
|
||
| hard bounce | `fail.hard_bounce` | `notification.endpoint.rejected_permanent` | Strong failure |
|
||
| async bounce | `fail.async_bounced` | `notification.endpoint.rejected_permanent` or temporary | Late failure |
|
||
| open human-like | `success.human_like_open` | `interaction.notification.opened` | Weak-to-medium awareness |
|
||
| open proxy-like | `undef.proxy_open` | `interaction.proxy_or_privacy_interaction` | Ambiguous |
|
||
| click scanner-like | `undef.scanner_click` | `interaction.scanner_or_bot_interaction` | Ambiguous/automated |
|
||
| click unverified | `undef.unverified_click` | `interaction.unverified_actor_interaction` | Identity uncertain |
|
||
| reply received | `success.reply_received` | `interaction.reply_received` | Medium-to-strong awareness |
|
||
| OOO received | `undef.out_of_office` | `interaction.out_of_office_received` | Mailbox reached, action uncertain |
|
||
| complaint | `fail.complaint_received` | `notification.channel.complaint_received` | Strong negative channel signal |
|
||
| unsubscribe | `fail.unsubscribed` | `notification.channel.unsubscribe_received` | Future channel constraint |
|
||
| suppression added | `fail.suppressed` | `notification.channel.suppression_added` | Channel blocked |
|
||
|
||
### 12.2 Coordination Undef Subclasses
|
||
|
||
`coordination-engine` may derive these participant uncertainty classes from email evidence:
|
||
|
||
```text
|
||
undef.pending
|
||
undef.technical_acceptance_only
|
||
undef.no_signal
|
||
undef.weak_positive
|
||
undef.identity_uncertain
|
||
undef.channel_suspicious
|
||
undef.conflicting_evidence
|
||
undef.delivery_pending
|
||
undef.escalation_required
|
||
```
|
||
|
||
Email evidence should commonly produce:
|
||
|
||
```text
|
||
undef.pending
|
||
undef.technical_acceptance_only
|
||
undef.weak_positive
|
||
undef.identity_uncertain
|
||
undef.channel_suspicious
|
||
undef.conflicting_evidence
|
||
```
|
||
|
||
## 13. Evidence Grading Rules
|
||
|
||
### 13.1 Provider Acceptance
|
||
|
||
```yaml
|
||
event_type: notification.attempt.accepted_by_provider
|
||
evidence_grade:
|
||
strength: weak
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: low
|
||
interaction_certainty: none
|
||
timing_certainty: medium
|
||
channel_certainty: medium
|
||
non_repudiation_strength: none
|
||
notes:
|
||
- Provider accepted message for processing.
|
||
- Does not prove recipient endpoint acceptance.
|
||
```
|
||
|
||
### 13.2 Recipient MX Acceptance
|
||
|
||
```yaml
|
||
event_type: notification.endpoint.accepted
|
||
evidence_grade:
|
||
strength: weak
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: low
|
||
interaction_certainty: none
|
||
timing_certainty: medium
|
||
channel_certainty: medium
|
||
non_repudiation_strength: none
|
||
notes:
|
||
- Recipient mail server accepted the message.
|
||
- Does not prove inbox placement or human awareness.
|
||
```
|
||
|
||
### 13.3 Hard Bounce
|
||
|
||
```yaml
|
||
event_type: notification.endpoint.rejected_permanent
|
||
evidence_grade:
|
||
strength: negative
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: none
|
||
interaction_certainty: none
|
||
timing_certainty: medium
|
||
channel_certainty: high
|
||
non_repudiation_strength: low
|
||
notes:
|
||
- Strong evidence the email endpoint is not usable for this attempt.
|
||
```
|
||
|
||
### 13.4 Soft Bounce / Deferral
|
||
|
||
```yaml
|
||
event_type: notification.endpoint.rejected_temporary
|
||
evidence_grade:
|
||
strength: ambiguous
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: none
|
||
interaction_certainty: none
|
||
timing_certainty: medium
|
||
channel_certainty: medium
|
||
non_repudiation_strength: none
|
||
notes:
|
||
- Temporary failure or deferral.
|
||
- May later resolve or expire.
|
||
```
|
||
|
||
### 13.5 Proxy Open
|
||
|
||
```yaml
|
||
event_type: interaction.proxy_or_privacy_interaction
|
||
evidence_grade:
|
||
strength: ambiguous
|
||
actor_certainty: low
|
||
authority_certainty: none
|
||
payload_certainty: low
|
||
interaction_certainty: low
|
||
timing_certainty: low
|
||
channel_certainty: medium
|
||
non_repudiation_strength: none
|
||
notes:
|
||
- Open may be caused by privacy proxy, image proxy, or automated prefetch.
|
||
- Should not be treated as human awareness in high-assurance scenarios.
|
||
```
|
||
|
||
### 13.6 Scanner-Like Click
|
||
|
||
```yaml
|
||
event_type: interaction.scanner_or_bot_interaction
|
||
evidence_grade:
|
||
strength: ambiguous
|
||
actor_certainty: low
|
||
authority_certainty: none
|
||
payload_certainty: low
|
||
interaction_certainty: low
|
||
timing_certainty: medium
|
||
channel_certainty: medium
|
||
non_repudiation_strength: none
|
||
notes:
|
||
- Click appears automated.
|
||
- Should not be treated as recipient engagement.
|
||
```
|
||
|
||
### 13.7 Unverified Click
|
||
|
||
```yaml
|
||
event_type: interaction.unverified_actor_interaction
|
||
evidence_grade:
|
||
strength: medium
|
||
actor_certainty: low
|
||
authority_certainty: none
|
||
payload_certainty: medium
|
||
interaction_certainty: medium
|
||
timing_certainty: medium
|
||
channel_certainty: medium
|
||
non_repudiation_strength: none
|
||
notes:
|
||
- Link was clicked, but actor identity is not proven.
|
||
- May indicate awareness but should be confirmed by portal or identity evidence.
|
||
```
|
||
|
||
### 13.8 Human Reply
|
||
|
||
```yaml
|
||
event_type: interaction.reply_received
|
||
evidence_grade:
|
||
strength: strong
|
||
actor_certainty: medium
|
||
authority_certainty: low
|
||
payload_certainty: medium
|
||
interaction_certainty: high
|
||
timing_certainty: high
|
||
channel_certainty: high
|
||
non_repudiation_strength: low
|
||
notes:
|
||
- Reply indicates mailbox-level interaction.
|
||
- Identity and authority may still require validation.
|
||
```
|
||
|
||
### 13.9 Complaint
|
||
|
||
```yaml
|
||
event_type: notification.channel.complaint_received
|
||
evidence_grade:
|
||
strength: negative
|
||
actor_certainty: medium
|
||
authority_certainty: none
|
||
payload_certainty: low
|
||
interaction_certainty: high
|
||
timing_certainty: high
|
||
channel_certainty: high
|
||
non_repudiation_strength: low
|
||
notes:
|
||
- Complaint is strong negative channel evidence.
|
||
- Future use of the channel may be constrained.
|
||
```
|
||
|
||
## 14. Message Timeline API
|
||
|
||
`email-connect` SHOULD expose a message timeline suitable for standalone diagnostics and coordination audit.
|
||
|
||
```yaml
|
||
EmailMessageTimeline:
|
||
email_message_id: string
|
||
email_attempts:
|
||
- email_attempt_id: string
|
||
provider_name: string
|
||
provider_message_id: string?
|
||
events:
|
||
- EmailTimelineEvent
|
||
current_assessment: EmailEvidenceAssessment
|
||
```
|
||
|
||
```yaml
|
||
EmailTimelineEvent:
|
||
event_id: string
|
||
event_type: string
|
||
occurred_at: timestamp
|
||
source: adapter | provider | inbound_mail | tracking | operator
|
||
native_event_type: string?
|
||
normalized_event_type: string?
|
||
summary: string
|
||
evidence_grade: EvidenceGrade
|
||
raw_event_ref: string?
|
||
```
|
||
|
||
## 15. Adapter Descriptor
|
||
|
||
`email-connect` MUST expose an `AdapterDescriptor` compatible with AdapterInterfaceSpecification.md v1.0.
|
||
|
||
```yaml
|
||
adapter_id: email-connect.default
|
||
adapter_name: email-connect
|
||
adapter_version: 1.0.0
|
||
adapter_contract_version: 1.0
|
||
adapter_types:
|
||
- notification
|
||
- communication
|
||
- interaction
|
||
provider_family: email
|
||
provider_name: configurable
|
||
deployment_mode: external
|
||
supported_channels:
|
||
- email
|
||
supported_endpoint_types:
|
||
- email_address
|
||
supported_actions:
|
||
- action_type: notification.send
|
||
mode: async
|
||
idempotency_required: true
|
||
- action_type: notification.send_reminder
|
||
mode: async
|
||
idempotency_required: true
|
||
- action_type: notification.register_tracking_context
|
||
mode: sync
|
||
idempotency_required: true
|
||
- action_type: recipient.suppress
|
||
mode: sync
|
||
idempotency_required: true
|
||
- action_type: recipient.unsuppress
|
||
mode: sync
|
||
idempotency_required: true
|
||
emitted_event_types:
|
||
- notification.attempt.created
|
||
- notification.attempt.accepted_by_adapter
|
||
- notification.attempt.rejected_by_adapter
|
||
- notification.attempt.accepted_by_provider
|
||
- notification.attempt.rejected_by_provider
|
||
- notification.attempt.queued
|
||
- notification.attempt.delayed
|
||
- notification.endpoint.accepted
|
||
- notification.endpoint.deferred
|
||
- notification.endpoint.rejected_temporary
|
||
- notification.endpoint.rejected_permanent
|
||
- notification.endpoint.unreachable
|
||
- notification.channel.complaint_received
|
||
- notification.channel.unsubscribe_received
|
||
- notification.channel.suppression_added
|
||
- notification.channel.suppression_removed
|
||
- notification.channel.reputation_warning
|
||
- interaction.notification.opened
|
||
- interaction.proxy_or_privacy_interaction
|
||
- interaction.link.clicked
|
||
- interaction.scanner_or_bot_interaction
|
||
- interaction.unverified_actor_interaction
|
||
- interaction.reply_received
|
||
- interaction.out_of_office_received
|
||
evidence_profile:
|
||
strongest_evidence_level: weak_to_medium
|
||
can_prove_human_awareness: false
|
||
can_prove_payload_delivery: false
|
||
can_prove_identity: false
|
||
identity_profile:
|
||
identity_strength: none
|
||
authority_strength: none
|
||
limitations:
|
||
- Recipient mail server acceptance does not prove inbox delivery.
|
||
- Absence of bounce does not prove delivery.
|
||
- Open tracking is ambiguous.
|
||
- Click tracking can be caused by security scanners.
|
||
- Email events alone usually cannot prove intended-recipient awareness.
|
||
- Email events do not prove payload access unless the payload is embedded and policy accepts that as sufficient.
|
||
```
|
||
|
||
## 16. Action Request Handling
|
||
|
||
### 16.1 `notification.send`
|
||
|
||
`notification.send` sends a new email notification.
|
||
|
||
Required fields:
|
||
|
||
```text
|
||
request_id
|
||
action_type
|
||
coordination_case_id
|
||
participant_id
|
||
target_endpoint
|
||
content or template_ref
|
||
tracking_context
|
||
idempotency_key
|
||
requested_at
|
||
```
|
||
|
||
Example:
|
||
|
||
```yaml
|
||
request_id: req_001
|
||
action_type: notification.send
|
||
coordination_case_id: case_123
|
||
participant_id: participant_456
|
||
channel: email
|
||
target_endpoint:
|
||
endpoint_type: email_address
|
||
value: recipient@example.com
|
||
template_ref: secure-document-available
|
||
variables:
|
||
recipient_name: Example Recipient
|
||
portal_link: https://portal.example.com/access/abc
|
||
tracking_context:
|
||
correlation_id: corr_789
|
||
coordination_case_id: case_123
|
||
participant_id: participant_456
|
||
notification_id: notif_001
|
||
payload_id: payload_777
|
||
idempotency_key: case_123:participant_456:notif_001
|
||
requested_at: 2026-01-01T12:00:00Z
|
||
```
|
||
|
||
### 16.2 Action Result
|
||
|
||
The adapter returns:
|
||
|
||
```yaml
|
||
request_id: req_001
|
||
adapter_id: email-connect.default
|
||
accepted: true
|
||
action_state: accepted
|
||
adapter_operation_id: emailop_001
|
||
provider_operation_id: providerop_001
|
||
initial_events:
|
||
- event_type: notification.attempt.accepted_by_adapter
|
||
received_at: 2026-01-01T12:00:01Z
|
||
```
|
||
|
||
The result does not prove the email was delivered or seen.
|
||
|
||
## 17. Provider Abstraction
|
||
|
||
`email-connect` SHOULD support a provider abstraction layer.
|
||
|
||
Provider integration responsibilities:
|
||
|
||
* send messages
|
||
* store provider message IDs
|
||
* verify webhooks
|
||
* ingest provider events
|
||
* normalize provider event names
|
||
* parse bounce reasons
|
||
* parse complaints and unsubscribes
|
||
* handle provider-specific suppression events
|
||
* expose provider health
|
||
|
||
Provider model:
|
||
|
||
```yaml
|
||
EmailProvider:
|
||
provider_name: string
|
||
provider_account_ref: string
|
||
supported_features:
|
||
- sending
|
||
- webhooks
|
||
- bounce_events
|
||
- open_tracking
|
||
- click_tracking
|
||
- suppression_api
|
||
- template_rendering
|
||
event_mapping_ref: string
|
||
configuration_ref: string
|
||
```
|
||
|
||
The first implementation MAY use a simulated provider. Real providers SHOULD be added behind this abstraction.
|
||
|
||
## 18. Native Provider Event Mapping
|
||
|
||
The adapter MUST support provider-specific mapping files or code modules.
|
||
|
||
Examples of provider-native event groups:
|
||
|
||
```text
|
||
accepted
|
||
processed
|
||
queued
|
||
delivered
|
||
deferred
|
||
delayed
|
||
bounce
|
||
dropped
|
||
failed
|
||
rejected
|
||
open
|
||
click
|
||
spam_report
|
||
unsubscribe
|
||
suppressed
|
||
rendering_failed
|
||
```
|
||
|
||
Important mapping rule:
|
||
|
||
Provider `delivered` events MUST be interpreted carefully.
|
||
|
||
For many providers, `delivered` means the receiving mail server accepted the email. It MUST map to:
|
||
|
||
```text
|
||
notification.endpoint.accepted
|
||
```
|
||
|
||
not to coordination success.
|
||
|
||
## 19. Bounce Classification
|
||
|
||
`email-connect` SHOULD classify bounces into structured reasons.
|
||
|
||
```yaml
|
||
EmailBounce:
|
||
bounce_type: hard | soft | transient | unknown
|
||
reason_code: string
|
||
enhanced_status_code: string?
|
||
smtp_status_code: string?
|
||
diagnostic_text: string?
|
||
retryable: boolean
|
||
classification:
|
||
- mailbox_not_found
|
||
- domain_not_found
|
||
- mailbox_full
|
||
- message_too_large
|
||
- authentication_failed
|
||
- policy_rejected
|
||
- content_rejected
|
||
- reputation_rejected
|
||
- rate_limited
|
||
- greylisted
|
||
- temporary_server_failure
|
||
- suppressed
|
||
- unknown
|
||
```
|
||
|
||
Suggested mappings:
|
||
|
||
| Bounce class | Email assessment | Normalized event |
|
||
| ------------------------ | ------------------------------ | -------------------------------------------------------- |
|
||
| mailbox_not_found | `fail.mailbox_not_found` | `notification.endpoint.rejected_permanent` |
|
||
| domain_not_found | `fail.domain_not_found` | `notification.endpoint.unreachable` |
|
||
| mailbox_full | `undef.deferred` | `notification.endpoint.rejected_temporary` |
|
||
| authentication_failed | `fail.authentication_rejected` | `notification.endpoint.rejected_permanent` |
|
||
| policy_rejected | `fail.policy_rejected` | `notification.endpoint.rejected_permanent` |
|
||
| content_rejected | `fail.content_rejected` | `notification.endpoint.rejected_permanent` |
|
||
| reputation_rejected | `fail.policy_rejected` | `notification.channel.reputation_warning` plus rejection |
|
||
| greylisted | `undef.deferred` | `notification.endpoint.deferred` |
|
||
| temporary_server_failure | `undef.deferred` | `notification.endpoint.rejected_temporary` |
|
||
|
||
## 20. Suppression Model
|
||
|
||
`email-connect` SHOULD maintain or integrate with a suppression model.
|
||
|
||
```yaml
|
||
EmailSuppression:
|
||
endpoint_ref: EndpointRef
|
||
suppression_type: hard_bounce | complaint | unsubscribe | manual | provider_policy | unknown
|
||
scope: global | tenant | sender_identity | campaign | coordination_case
|
||
reason: string?
|
||
source: provider | adapter | operator | participant
|
||
created_at: timestamp
|
||
expires_at: timestamp?
|
||
```
|
||
|
||
Suppression should produce evidence:
|
||
|
||
```text
|
||
notification.channel.suppression_added
|
||
notification.channel.suppression_removed
|
||
```
|
||
|
||
coordination-engine decides whether suppression means participant failure, channel failure, alternate channel selection, or manual review.
|
||
|
||
## 21. Open and Click Tracking Model
|
||
|
||
`email-connect` MAY support open and click tracking, but MUST classify such evidence conservatively.
|
||
|
||
### 21.1 Open Tracking
|
||
|
||
```yaml
|
||
EmailOpenEvent:
|
||
email_message_id: string
|
||
occurred_at: timestamp
|
||
ip_address: string?
|
||
user_agent: string?
|
||
proxy_classification: none | suspected | likely | confirmed
|
||
confidence: low | medium | high
|
||
```
|
||
|
||
Possible classifications:
|
||
|
||
```text
|
||
human_like_open
|
||
proxy_open
|
||
privacy_proxy_open
|
||
image_proxy_open
|
||
security_scanner_open
|
||
unknown_open
|
||
```
|
||
|
||
### 21.2 Click Tracking
|
||
|
||
```yaml
|
||
EmailClickEvent:
|
||
email_message_id: string
|
||
link_id: string
|
||
occurred_at: timestamp
|
||
url: string
|
||
ip_address: string?
|
||
user_agent: string?
|
||
scanner_classification: none | suspected | likely | confirmed
|
||
confidence: low | medium | high
|
||
```
|
||
|
||
Possible classifications:
|
||
|
||
```text
|
||
human_like_click
|
||
scanner_click
|
||
link_prefetch
|
||
multi_link_scan
|
||
unverified_click
|
||
unknown_click
|
||
```
|
||
|
||
### 21.3 Scanner Detection Hints
|
||
|
||
The adapter SHOULD consider:
|
||
|
||
* click occurs immediately after delivery
|
||
* multiple links clicked within seconds
|
||
* HEAD requests or unusual HTTP methods
|
||
* known security user agents
|
||
* known security vendor IP ranges
|
||
* no browser-like behavior
|
||
* link fetched but no subsequent portal session
|
||
* repeated link checks from data center IPs
|
||
* clicks before recipient likely had time to read
|
||
|
||
Scanner detection is heuristic and MUST be represented as confidence, not certainty, unless the adapter has strong provider-specific evidence.
|
||
|
||
## 22. Address and Endpoint Quality
|
||
|
||
`email-connect` SHOULD maintain email endpoint quality signals.
|
||
|
||
```yaml
|
||
EmailEndpointQuality:
|
||
endpoint_ref: EndpointRef
|
||
syntax_valid: boolean?
|
||
domain_exists: boolean?
|
||
mx_exists: boolean?
|
||
verified: boolean?
|
||
catch_all_suspected: boolean?
|
||
role_address_suspected: boolean?
|
||
disposable_suspected: boolean?
|
||
suppression_state: string?
|
||
last_successful_endpoint_acceptance_at: timestamp?
|
||
last_hard_bounce_at: timestamp?
|
||
last_complaint_at: timestamp?
|
||
last_engagement_at: timestamp?
|
||
```
|
||
|
||
Endpoint quality may be emitted as diagnostics but should not by itself create coordination success.
|
||
|
||
## 23. Channel Health
|
||
|
||
`email-connect` SHOULD expose channel health.
|
||
|
||
```yaml
|
||
EmailChannelHealth:
|
||
sender_identity: string
|
||
domain: string?
|
||
provider_account_ref: string?
|
||
status: healthy | degraded | failing | unknown
|
||
authentication_status:
|
||
spf: pass | fail | unknown
|
||
dkim: pass | fail | unknown
|
||
dmarc: pass | fail | unknown
|
||
reputation_status: good | warning | poor | unknown
|
||
bounce_rate: number?
|
||
complaint_rate: number?
|
||
deferral_rate: number?
|
||
provider_degradation:
|
||
- string
|
||
```
|
||
|
||
Health-related normalized events:
|
||
|
||
```text
|
||
notification.channel.reputation_warning
|
||
system.provider.degraded
|
||
system.provider.unavailable
|
||
system.adapter.health_changed
|
||
```
|
||
|
||
## 24. Security Requirements
|
||
|
||
`email-connect` MUST:
|
||
|
||
* protect provider credentials
|
||
* verify provider webhook signatures where available
|
||
* validate inbound webhook source authenticity where possible
|
||
* avoid logging sensitive message content by default
|
||
* preserve idempotency
|
||
* prevent duplicate sends for repeated idempotency keys
|
||
* support tenant or sender separation where applicable
|
||
* avoid leaking tracking tokens
|
||
* protect suppression lists
|
||
* sanitize inbound replies and auto-replies
|
||
|
||
## 25. Privacy Requirements
|
||
|
||
`email-connect` SHOULD:
|
||
|
||
* store message content only when necessary
|
||
* support metadata-only mode
|
||
* support raw event redaction
|
||
* support configurable retention of raw provider events
|
||
* store endpoint references instead of endpoint values where possible
|
||
* avoid storing unnecessary open/click details if not needed
|
||
* allow tracking to be disabled
|
||
* separate operational diagnostics from coordination evidence
|
||
* support deletion or anonymization workflows where applicable
|
||
|
||
## 26. Reliability Requirements
|
||
|
||
`email-connect` MUST support:
|
||
|
||
* idempotent send requests
|
||
* duplicate webhook event detection
|
||
* out-of-order event handling
|
||
* late bounce handling
|
||
* retryable provider failures
|
||
* non-retryable provider failures
|
||
* provider timeout handling
|
||
* correlation preservation
|
||
* dead-letter handling for unprocessable events
|
||
|
||
Late events MUST be preserved.
|
||
|
||
Example:
|
||
|
||
```text
|
||
MX accepted at 10:00
|
||
participant unresolved at 14:00
|
||
hard bounce arrives at 18:00
|
||
```
|
||
|
||
The hard bounce must still be recorded and emitted as evidence.
|
||
|
||
## 27. Raw Event Preservation
|
||
|
||
`email-connect` SHOULD preserve raw provider events or references to them.
|
||
|
||
```yaml
|
||
RawEmailEventRef:
|
||
raw_event_id: string
|
||
provider_name: string
|
||
storage_ref: string?
|
||
received_at: timestamp
|
||
redacted: boolean
|
||
```
|
||
|
||
Normalized events should reference raw event data where available:
|
||
|
||
```yaml
|
||
raw_event_ref: raw_email_event_123
|
||
```
|
||
|
||
## 28. Minimal API Surface
|
||
|
||
`email-connect` SHOULD expose a headless API.
|
||
|
||
### 28.1 Adapter Contract API
|
||
|
||
Required conceptual operations:
|
||
|
||
```text
|
||
GET /adapter/descriptor
|
||
GET /adapter/health
|
||
POST /adapter/actions
|
||
POST /adapter/events/provider
|
||
GET /adapter/events
|
||
GET /adapter/messages/{id}/timeline
|
||
GET /adapter/messages/{id}/assessment
|
||
```
|
||
|
||
The actual transport may differ, but these conceptual operations should exist.
|
||
|
||
### 28.2 Standalone API
|
||
|
||
Useful standalone operations:
|
||
|
||
```text
|
||
POST /email/send
|
||
GET /email/messages/{id}
|
||
GET /email/messages/{id}/timeline
|
||
GET /email/messages/{id}/assessment
|
||
GET /email/endpoints/{id}/quality
|
||
POST /email/suppressions
|
||
DELETE /email/suppressions/{id}
|
||
GET /email/channel-health
|
||
```
|
||
|
||
## 29. Example End-to-End Flow
|
||
|
||
### 29.1 Secure Document Notification
|
||
|
||
1. `coordination-engine` creates a coordination case.
|
||
2. `portal-connect` creates an authenticated document access link.
|
||
3. `coordination-engine` sends `notification.send` to `email-connect`.
|
||
4. `email-connect` renders and dispatches email.
|
||
5. Provider accepts the message.
|
||
6. `email-connect` emits `notification.attempt.accepted_by_provider`.
|
||
7. Recipient MX accepts message.
|
||
8. `email-connect` emits `notification.endpoint.accepted`.
|
||
9. User clicks the link.
|
||
10. `email-connect` emits `interaction.unverified_actor_interaction`.
|
||
11. User logs into portal.
|
||
12. `portal-connect` emits `identity.actor_authenticated`.
|
||
13. User downloads document.
|
||
14. `portal-connect` emits `delivery.payload.downloaded`.
|
||
15. `coordination-engine` marks participant complete.
|
||
|
||
Email contributed useful path evidence but did not by itself prove the result.
|
||
|
||
### 29.2 Hard Bounce and Fallback
|
||
|
||
1. `email-connect` sends email.
|
||
2. Provider accepts the message.
|
||
3. Recipient server returns hard bounce.
|
||
4. `email-connect` emits `notification.endpoint.rejected_permanent`.
|
||
5. `coordination-engine` marks email channel failed for that participant.
|
||
6. Policy engine selects SMS fallback or manual review.
|
||
|
||
### 29.3 Scanner Click
|
||
|
||
1. Email is accepted by MX.
|
||
2. Link is clicked immediately by a known scanner pattern.
|
||
3. `email-connect` emits `interaction.scanner_or_bot_interaction`.
|
||
4. `coordination-engine` keeps participant in `undef.identity_uncertain`.
|
||
5. No reminder is suppressed merely because of the scanner click.
|
||
6. If no portal evidence appears after threshold, reminder or alternate channel is triggered.
|
||
|
||
## 30. Provider Implementation Guidance
|
||
|
||
The first real provider integration SHOULD be selected based on:
|
||
|
||
* webhook support
|
||
* bounce event quality
|
||
* provider metadata/correlation support
|
||
* suppression API
|
||
* event reliability
|
||
* local availability and cost
|
||
* compatibility with transactional email
|
||
* ability to separate message streams
|
||
|
||
The implementation SHOULD avoid hardcoding provider semantics into the core email model.
|
||
|
||
Provider-specific modules should map to the email-native model first, then to normalized coordination events.
|
||
|
||
```text
|
||
Provider event
|
||
→ email-native event
|
||
→ EmailEvidenceAssessment
|
||
→ EvidenceEvent for coordination-engine
|
||
```
|
||
|
||
## 31. Message Steparation
|
||
|
||
`email-connect` SHOULD support sender identities or message streams.
|
||
|
||
Recommended streams:
|
||
|
||
```text
|
||
transactional
|
||
notification
|
||
legal_or_high_assurance_notice
|
||
marketing
|
||
system_alert
|
||
test
|
||
```
|
||
|
||
High-assurance or legally relevant notifications SHOULD NOT share reputation-critical infrastructure with marketing traffic unless explicitly accepted by policy.
|
||
|
||
Stream separation may affect:
|
||
|
||
* sender domain
|
||
* return path
|
||
* DKIM identity
|
||
* provider account
|
||
* suppression scope
|
||
* complaint handling
|
||
* deliverability diagnostics
|
||
|
||
## 32. Legal and Compliance Disclaimer
|
||
|
||
`email-connect` does not by itself provide legal proof of delivery, legal notice, acceptance, signature, or contract closure.
|
||
|
||
It provides evidence from the email channel.
|
||
|
||
Scenario-specific applications and `coordination-engine` policies may combine email evidence with stronger evidence from portal, identity, signature, payment, archive, or manual processes.
|
||
|
||
The adapter MUST avoid naming technical email events in ways that imply legal success.
|
||
|
||
For example:
|
||
|
||
Use:
|
||
|
||
```text
|
||
notification.endpoint.accepted
|
||
```
|
||
|
||
Avoid:
|
||
|
||
```text
|
||
recipient_notified
|
||
legal_delivery_completed
|
||
```
|
||
|
||
## 33. MVP Scope
|
||
|
||
The first useful version of `email-connect` should implement:
|
||
|
||
1. Adapter descriptor.
|
||
2. Adapter health endpoint.
|
||
3. `notification.send`.
|
||
4. Idempotent send request handling.
|
||
5. Simulated provider or one real provider.
|
||
6. Email message and attempt records.
|
||
7. Provider event ingestion.
|
||
8. Basic bounce classification.
|
||
9. Basic open/click classification.
|
||
10. Evidence event generation.
|
||
11. Message timeline.
|
||
12. Message assessment.
|
||
13. Suppression support.
|
||
14. Mapping to AdapterInterfaceSpecification.md v1.0.
|
||
|
||
### MVP Required Email Events
|
||
|
||
```text
|
||
notification.attempt.accepted_by_adapter
|
||
notification.attempt.rejected_by_adapter
|
||
notification.attempt.accepted_by_provider
|
||
notification.attempt.rejected_by_provider
|
||
notification.endpoint.accepted
|
||
notification.endpoint.deferred
|
||
notification.endpoint.rejected_temporary
|
||
notification.endpoint.rejected_permanent
|
||
interaction.proxy_or_privacy_interaction
|
||
interaction.scanner_or_bot_interaction
|
||
interaction.unverified_actor_interaction
|
||
notification.channel.complaint_received
|
||
notification.channel.suppression_added
|
||
```
|
||
|
||
### MVP Acceptance Criteria
|
||
|
||
The MVP is acceptable when it can:
|
||
|
||
1. Accept a coordination-compatible send request.
|
||
2. Dispatch or simulate an email.
|
||
3. Preserve correlation and idempotency.
|
||
4. Ingest or simulate provider events.
|
||
5. Produce normalized evidence events.
|
||
6. Classify MX acceptance as weak technical evidence.
|
||
7. Classify hard bounce as strong failure evidence.
|
||
8. Classify proxy opens and scanner clicks as ambiguous.
|
||
9. Provide a message timeline.
|
||
10. Provide an email evidence assessment.
|
||
11. Integrate with coordination-engine without overclaiming success.
|
||
|
||
## 34. Future Extensions
|
||
|
||
Potential future capabilities:
|
||
|
||
* multi-provider routing
|
||
* provider failover
|
||
* domain reputation monitoring
|
||
* inbox placement seed testing
|
||
* DMARC aggregate report ingestion
|
||
* BIMI diagnostics
|
||
* advanced bounce parsing
|
||
* advanced scanner detection
|
||
* inbound reply classification
|
||
* natural-language reply intent extraction
|
||
* tenant-specific suppression scopes
|
||
* message stream isolation
|
||
* deliverability analytics
|
||
* adaptive sending policies
|
||
* integration with archive systems
|
||
* S/MIME or PGP support
|
||
* signed email support
|
||
* email authentication diagnostics
|
||
* AI-assisted deliverability analysis
|
||
|
||
## 35. Non-Goals
|
||
|
||
`email-connect` is not:
|
||
|
||
* a marketing automation platform
|
||
* a newsletter campaign manager
|
||
* a CRM
|
||
* a full workflow engine
|
||
* a legal delivery service by itself
|
||
* a document portal
|
||
* a payment system
|
||
* a signature system
|
||
* the owner of coordination case success
|
||
|
||
It may integrate with such systems or be used by them.
|
||
|
||
## 36. Summary
|
||
|
||
`email-connect` models email as a useful but uncertain communication and notification channel.
|
||
|
||
Its job is to:
|
||
|
||
* send emails
|
||
* ingest provider events
|
||
* classify email outcomes
|
||
* normalize email evidence
|
||
* preserve message timelines
|
||
* expose endpoint and channel diagnostics
|
||
* integrate cleanly with `coordination-engine`
|
||
|
||
The key rule is:
|
||
|
||
> Email events are evidence, not result satisfaction. email-connect reports email-channel facts and uncertainty. coordination-engine evaluates intended results.
|
||
|