# 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.