Files
coordination-engine/spec/EmailAdapterSpecification.md

1521 lines
55 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 recipients 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.