generated from coulomb/repo-seed
1166 lines
39 KiB
Markdown
1166 lines
39 KiB
Markdown
# HybridmailPingenSpecification.md
|
||
|
||
## 1. Document Status
|
||
|
||
**Document:** HybridmailPingenSpecification.md
|
||
**Project:** hybridmail-connect
|
||
**Provider Flavor:** Pingen
|
||
**Parent Specification:** HybridmailAdapterSpecification.md
|
||
**Target Integration:** coordination-engine
|
||
**Adapter Contract:** AdapterInterfaceSpecification.md v1.0
|
||
**Status:** Draft v1.0
|
||
**Primary Scope:** Pingen-specific hybrid-mail provider flavor for letter upload, letter creation, validation, auto-send/manual-send control, Track & Trace, webhooks, return mail, delivered/undeliverable status mapping, and idempotent API usage.
|
||
|
||
## 2. Purpose
|
||
|
||
This document specifies the Pingen provider flavor for `hybridmail-connect`.
|
||
|
||
The Pingen flavor implements the generic hybrid-mail adapter model for the Pingen Letter API. It maps Pingen’s API concepts into the provider-neutral hybrid-mail model used by `coordination-engine`.
|
||
|
||
The purpose of this document is to capture:
|
||
|
||
* Pingen-specific provider capabilities.
|
||
* Pingen-specific workflow stages.
|
||
* Pingen letter, file, address, validation, send, status, Track & Trace, and webhook concepts.
|
||
* Pingen return-mail and undeliverable processing.
|
||
* Pingen delivery-confirmation semantics.
|
||
* Pingen idempotency and rate-limit behavior.
|
||
* Pingen evidence event mapping.
|
||
* Pingen-specific limitations and assumptions.
|
||
* Pingen-specific MVP boundaries.
|
||
|
||
The Pingen flavor MUST remain compatible with `HybridmailAdapterSpecification.md` and MUST NOT leak Pingen-specific terminology into the core coordination model except through mapped provider metadata.
|
||
|
||
## 3. Grounding Summary
|
||
|
||
The public Pingen material indicates the following relevant features:
|
||
|
||
* Pingen provides a Letter API for automated postal-letter sending.
|
||
* Pingen offers SDKs, including PHP, Python, Go, .NET, and code examples.
|
||
* API authentication uses OAuth-style client credentials in common tooling/examples.
|
||
* Pingen supports Idempotency-Key headers.
|
||
* Pingen supports rate limiting and configurable send limits.
|
||
* Pingen offers staging/sandbox or full-feature simulation.
|
||
* Pingen supports document validation.
|
||
* Pingen supports configurable address position.
|
||
* Pingen supports auto-send control.
|
||
* Pingen offers Track & Trace for real-time progress transparency.
|
||
* Pingen provides webhooks for status changes.
|
||
* Public webhook categories include letter issues, sent letters, undeliverable letters, and delivered letters.
|
||
* Pingen automates return-mail processing and can provide return details via email and webhook.
|
||
* Pingen distinguishes ordinary postal dispatch from actual delivery-confirmation products. Ordinary letters may not receive final post-office delivery confirmation.
|
||
|
||
These facts define the baseline for this provider flavor.
|
||
|
||
## 4. Provider Flavor Identity
|
||
|
||
```yaml
|
||
HybridmailProviderFlavorDescriptor:
|
||
provider_name: pingen
|
||
provider_label: Pingen Letter API
|
||
provider_family: hybridmail
|
||
adapter_contract_version: "1.0"
|
||
parent_specification: HybridmailAdapterSpecification.md
|
||
deployment_mode: external
|
||
```
|
||
|
||
## 5. Pingen Capability Descriptor
|
||
|
||
The Pingen flavor MUST expose a capability descriptor.
|
||
|
||
```yaml
|
||
HybridmailProviderFlavorDescriptor:
|
||
provider_name: pingen
|
||
provider_version: provider_specific
|
||
supported_file_types:
|
||
- pdf
|
||
supported_attachment_file_types:
|
||
- provider_specific
|
||
supports_single_letter: true
|
||
supports_bulk: true
|
||
supports_serial_letters: true
|
||
supports_attachments: provider_specific
|
||
supports_preview: true
|
||
supports_webhooks: true
|
||
supports_polling: true
|
||
supports_track_and_trace: true
|
||
supports_registered_mail: true
|
||
supports_delivery_confirmation: product_dependent
|
||
supports_return_mail: true
|
||
supports_address_correction: provider_specific
|
||
supports_international_mail: true
|
||
supports_cancellation: provider_specific
|
||
supports_staging: true
|
||
supports_full_feature_simulation: true
|
||
supports_idempotency: true
|
||
supports_rate_limits: true
|
||
supports_configurable_send_limits: true
|
||
supports_auto_send: true
|
||
supports_address_position_configuration: true
|
||
supports_document_validation: true
|
||
supported_print_options:
|
||
color_mode: provider_specific
|
||
simplex_duplex: provider_specific
|
||
envelope_selection: provider_specific
|
||
address_position: true
|
||
supported_postal_products:
|
||
- standard
|
||
- registered
|
||
- delivery_confirmation
|
||
- international
|
||
- provider_specific
|
||
limitations:
|
||
- Ordinary postal letters usually do not provide final post-office delivery confirmation.
|
||
- Delivered-letter webhook semantics must be interpreted according to selected product and Pingen status details.
|
||
- Return-mail information may arrive late.
|
||
- Public documentation confirms webhook categories, but exact event payload fields must be implemented against the live Pingen API documentation.
|
||
- Provider-native status names must be preserved in metadata and mapped conservatively.
|
||
```
|
||
|
||
## 6. Pingen-Specific Conceptual Workflow
|
||
|
||
The Pingen flavor uses a file/letter-centric workflow.
|
||
|
||
```text
|
||
authenticate
|
||
→ upload file / create letter
|
||
→ configure address position and sending options
|
||
→ validate letter
|
||
→ optionally auto-send or manually trigger send
|
||
→ Track & Trace status changes
|
||
→ webhook or polling status ingestion
|
||
→ sent-letter evidence
|
||
→ undeliverable/return-mail evidence where applicable
|
||
→ delivered-letter evidence where product/status supports it
|
||
```
|
||
|
||
The adapter MUST preserve this distinction:
|
||
|
||
```text
|
||
Letter creation is not sending.
|
||
Validation success is not postal dispatch.
|
||
Sent status is not necessarily final delivery.
|
||
Ordinary postal dispatch is not proof of human awareness.
|
||
Return-mail events may arrive late and can change the delivery assessment.
|
||
```
|
||
|
||
## 7. Pingen Object Mapping
|
||
|
||
## 7.1 Generic to Pingen Mapping
|
||
|
||
| Generic hybrid-mail concept | Pingen flavor concept |
|
||
| --------------------------- | --------------------------------------------------------------------------------------------- |
|
||
| `HybridmailLetter` | Pingen letter |
|
||
| `HybridmailDocumentRef` | Uploaded file / letter document |
|
||
| `HybridmailAttempt` | Pingen letter creation/send operation |
|
||
| `HybridmailBatch` | Bulk or serial letter group where used |
|
||
| `PostalRecipient` | Address extracted from document or configured via address position / metadata where supported |
|
||
| `HybridmailOptions` | Pingen send, print, address, and delivery options |
|
||
| `HybridmailPreview` | Pingen preview or validation representation where supported |
|
||
| `HybridmailTimeline` | Pingen Track & Trace and webhook timeline |
|
||
|
||
## 7.2 PingenLetter
|
||
|
||
```yaml
|
||
PingenLetter:
|
||
pingen_letter_id: string
|
||
hybridmail_letter_id: string?
|
||
coordination_case_id: string?
|
||
participant_id: string?
|
||
provider_status: string?
|
||
normalized_status: string
|
||
file_ref: ResourceRef
|
||
page_count: integer?
|
||
sheet_count: integer?
|
||
address_position: PingenAddressPosition?
|
||
options: PingenLetterOptions?
|
||
validation_result: PingenValidationResult?
|
||
track_and_trace_ref: string?
|
||
webhook_event_refs:
|
||
- string
|
||
created_at: timestamp
|
||
updated_at: timestamp?
|
||
metadata: object?
|
||
```
|
||
|
||
## 7.3 PingenFile
|
||
|
||
```yaml
|
||
PingenFile:
|
||
pingen_file_id: string?
|
||
hybridmail_letter_id: string?
|
||
original_file_ref: ResourceRef
|
||
file_type: pdf | unknown
|
||
file_size_bytes: integer?
|
||
integrity_hash: string?
|
||
upload_state: pending | uploaded | rejected | failed | unknown
|
||
created_at: timestamp
|
||
metadata: object?
|
||
```
|
||
|
||
## 7.4 PingenAttempt
|
||
|
||
```yaml
|
||
PingenAttempt:
|
||
pingen_attempt_id: string
|
||
hybridmail_letter_id: string
|
||
pingen_letter_id: string?
|
||
provider_operation_id: string?
|
||
idempotency_key: string
|
||
operation_type: create_letter | validate_letter | send_letter | cancel_letter | status_poll | webhook_ingest
|
||
state: pending | accepted | rejected | failed | completed | duplicate | unknown
|
||
created_at: timestamp
|
||
updated_at: timestamp?
|
||
raw_provider_response_ref: string?
|
||
```
|
||
|
||
## 7.5 PingenBatch
|
||
|
||
```yaml
|
||
PingenBatch:
|
||
pingen_batch_id: string?
|
||
hybridmail_batch_id: string
|
||
coordination_case_id: string?
|
||
batch_type: bulk | serial | campaign | mixed | unknown
|
||
letters:
|
||
- hybridmail_letter_id
|
||
state: pending | partial | completed | failed | unknown
|
||
created_at: timestamp
|
||
updated_at: timestamp?
|
||
```
|
||
|
||
Batch-level events MUST NOT be treated as success for every child letter unless Pingen provides per-letter status or policy explicitly accepts batch-level evidence.
|
||
|
||
## 8. Pingen Address Model
|
||
|
||
Pingen supports configurable address position. The Pingen flavor MUST model address positioning explicitly.
|
||
|
||
```yaml
|
||
PingenAddressPosition:
|
||
source: configured | detected | provider_default | unknown
|
||
position: left | right | provider_specific | unknown
|
||
page_number: integer?
|
||
bounding_box: object?
|
||
confidence: low | medium | high
|
||
```
|
||
|
||
Generic postal recipient mapping:
|
||
|
||
```yaml
|
||
PostalRecipient:
|
||
recipient_id: string?
|
||
name_lines:
|
||
- string
|
||
organization: string?
|
||
street: string?
|
||
house_number: string?
|
||
address_addition: string?
|
||
postal_code: string?
|
||
city: string?
|
||
region: string?
|
||
country_code: string?
|
||
country_name: string?
|
||
address_source: extracted_from_document | provided_metadata | provider_detected | manual | unknown
|
||
address_quality: PostalAddressQuality?
|
||
```
|
||
|
||
If Pingen validation detects address issues, they MUST be mapped into `PingenValidationIssue` and then into generic `HybridmailValidationIssue`.
|
||
|
||
## 9. Pingen Validation Model
|
||
|
||
Pingen validation results MUST be mapped into generic `HybridmailValidationResult`.
|
||
|
||
```yaml
|
||
PingenValidationResult:
|
||
pingen_letter_id: string
|
||
provider_validation_state: string?
|
||
validation_state: pending | passed | action_required | failed | unknown
|
||
issues:
|
||
- PingenValidationIssue
|
||
validated_at: timestamp?
|
||
raw_provider_response_ref: string?
|
||
```
|
||
|
||
```yaml
|
||
PingenValidationIssue:
|
||
provider_issue_code: string?
|
||
issue_code: string
|
||
severity: info | warning | action_required | fatal
|
||
category: format | page_size | pdf | address | address_position | restricted_area | margins | file_size | page_count | postage | country | provider_policy | unknown
|
||
message: string
|
||
page_number: integer?
|
||
bounding_box: object?
|
||
fixable: boolean?
|
||
```
|
||
|
||
## 10. Pingen Validation Issue Mapping
|
||
|
||
| Pingen validation concern | Generic validation category | Normalized issue code |
|
||
| --------------------------------- | --------------------------- | ------------------------------- |
|
||
| Invalid PDF | `pdf` / `format` | `invalid_pdf` |
|
||
| Unsupported file type | `format` | `unsupported_file_type` |
|
||
| Address not found or unreadable | `address` | `address_not_detected` |
|
||
| Address position invalid | `address_position` | `address_position_invalid` |
|
||
| Address format invalid | `address` | `address_format_invalid` |
|
||
| Unsupported destination country | `country` | `unsupported_country` |
|
||
| Page or file constraints violated | `page_count` / `file_size` | `document_constraint_violation` |
|
||
| Postal product invalid | `postage` | `unsupported_product` |
|
||
| Provider policy rejection | `provider_policy` | `provider_policy_rejected` |
|
||
| Unknown validation issue | `unknown` | `unknown_validation_issue` |
|
||
|
||
Validation failure MUST emit:
|
||
|
||
```text
|
||
payload.validation_failed
|
||
```
|
||
|
||
Validation success MUST emit:
|
||
|
||
```text
|
||
payload.validation_passed
|
||
```
|
||
|
||
If Pingen classifies an issue as fixable/action-required, the adapter SHOULD include severity `action_required` rather than immediately treating the coordination case as permanently failed.
|
||
|
||
## 11. Pingen Letter Options
|
||
|
||
The Pingen flavor MUST map Pingen options into the generic `HybridmailOptions`.
|
||
|
||
```yaml
|
||
PingenLetterOptions:
|
||
color_mode: color | grayscale | provider_default | unknown
|
||
print_sides: simplex | duplex | provider_default | unknown
|
||
address_position: left | right | provider_default | unknown
|
||
postage_product: standard | registered | delivery_confirmation | international | provider_specific
|
||
country_scope: domestic | international | unknown
|
||
auto_send: boolean?
|
||
dispatch_mode: manual_review | auto_send | submit_later | scheduled
|
||
desired_dispatch_date: date?
|
||
return_mail_handling: none | provider_processed | scan_return | digital_return_info | physical_return | unknown
|
||
delivery_confirmation_requested: boolean?
|
||
metadata: object?
|
||
```
|
||
|
||
Provider-native option names MUST be preserved in metadata.
|
||
|
||
The adapter MUST NOT assume support for a postal product unless the Pingen capability descriptor confirms it.
|
||
|
||
## 12. Pingen Auto-Send Handling
|
||
|
||
Pingen supports auto-send behavior according to public API materials.
|
||
|
||
The adapter MUST explicitly model auto-send, because it affects the transition from creation/validation to physical dispatch.
|
||
|
||
```yaml
|
||
PingenAutoSendPolicy:
|
||
auto_send: boolean
|
||
send_after_validation: boolean?
|
||
require_adapter_approval: boolean?
|
||
require_coordination_policy_approval: boolean?
|
||
```
|
||
|
||
Recommended safety rule:
|
||
|
||
```text
|
||
For high-assurance or expensive physical-mail scenarios,
|
||
auto_send SHOULD default to false unless explicitly enabled by policy.
|
||
```
|
||
|
||
If `auto_send=true`, the adapter MUST make duplicate-send prevention especially strict.
|
||
|
||
## 13. Pingen Track & Trace Model
|
||
|
||
Pingen provides Track & Trace progress updates. The adapter MUST represent those as a timeline.
|
||
|
||
```yaml
|
||
PingenTrackAndTraceEvent:
|
||
event_id: string
|
||
pingen_letter_id: string
|
||
provider_status: string
|
||
normalized_status: string
|
||
event_category: letter_issue | sent_letter | undeliverable_letter | delivered_letter | return_mail | processing | unknown
|
||
occurred_at: timestamp?
|
||
observed_at: timestamp
|
||
raw_provider_response_ref: string?
|
||
metadata: object?
|
||
```
|
||
|
||
Track & Trace events should map to the strongest safe generic event.
|
||
|
||
If the event only indicates process progress, it MUST NOT be mapped to postal delivery confirmation.
|
||
|
||
## 14. Pingen Webhook Model
|
||
|
||
Pingen public help identifies webhook categories:
|
||
|
||
```text
|
||
Letter issues
|
||
Sent letters
|
||
Undeliverable letters
|
||
Delivered letters
|
||
```
|
||
|
||
The adapter MUST support these categories where available.
|
||
|
||
```yaml
|
||
PingenWebhookEvent:
|
||
pingen_webhook_event_id: string
|
||
webhook_category: letter_issue | sent_letter | undeliverable_letter | delivered_letter | unknown
|
||
pingen_letter_id: string?
|
||
provider_event_id: string?
|
||
occurred_at: timestamp?
|
||
observed_at: timestamp
|
||
raw_payload_ref: string?
|
||
normalized_events:
|
||
- EvidenceEvent
|
||
```
|
||
|
||
## 15. Pingen Webhook Category Mapping
|
||
|
||
| Pingen webhook category | Generic event | Evidence interpretation |
|
||
| ----------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
||
| Letter issues | `payload.validation_failed` or `delivery.payload.rejected` | Strong issue/failure/action-required evidence depending payload |
|
||
| Sent letters | `delivery.postal.handed_over` or `delivery.payload.submitted` | Strong dispatch evidence only if semantics confirm physical send/handover |
|
||
| Undeliverable letters | `delivery.postal.undeliverable` or `delivery.postal.return_received` | Strong negative delivery evidence |
|
||
| Delivered letters | `delivery.postal.delivery_confirmed` | Strong delivery evidence if product/status supports delivery confirmation |
|
||
|
||
Important:
|
||
|
||
```text
|
||
A "sent letters" event should not be interpreted as "recipient received the letter".
|
||
A "delivered letters" event must be interpreted according to the postal product and provider semantics.
|
||
```
|
||
|
||
## 16. Pingen Return Mail Model
|
||
|
||
Pingen supports automated return-mail processing and provides return details via channels including webhooks.
|
||
|
||
```yaml
|
||
PingenReturnEvent:
|
||
return_event_id: string
|
||
pingen_letter_id: string
|
||
provider_return_id: string?
|
||
return_type: undeliverable | address_unknown | recipient_moved | refused | insufficient_address | other | unknown
|
||
return_details: string?
|
||
address_correction: PostalRecipient?
|
||
return_document_ref: ResourceRef?
|
||
occurred_at: timestamp?
|
||
observed_at: timestamp
|
||
raw_event_ref: string?
|
||
```
|
||
|
||
Return events usually create strong negative delivery evidence:
|
||
|
||
```text
|
||
delivery.postal.return_received
|
||
delivery.postal.undeliverable
|
||
```
|
||
|
||
Return events may arrive late and MUST be preserved even if the coordination case has already progressed.
|
||
|
||
## 17. Pingen Delivery Confirmation Model
|
||
|
||
Pingen delivery-confirmation evidence is product-dependent.
|
||
|
||
```yaml
|
||
PingenDeliveryConfirmation:
|
||
confirmation_id: string
|
||
pingen_letter_id: string
|
||
product_type: delivery_confirmation | registered | provider_specific
|
||
provider_status: string?
|
||
normalized_status: delivered | attempted | refused | undeliverable | returned | unknown
|
||
occurred_at: timestamp?
|
||
observed_at: timestamp
|
||
raw_event_ref: string?
|
||
```
|
||
|
||
The adapter MUST only emit:
|
||
|
||
```text
|
||
delivery.postal.delivery_confirmed
|
||
```
|
||
|
||
when the Pingen status and selected postal product semantically support delivery confirmation.
|
||
|
||
Ordinary letter dispatch MUST NOT be mapped to `delivery.postal.delivery_confirmed`.
|
||
|
||
## 18. Pingen Status Model
|
||
|
||
The Pingen flavor MUST preserve raw provider status and map it to normalized status.
|
||
|
||
```yaml
|
||
PingenStatusRecord:
|
||
status_record_id: string
|
||
pingen_letter_id: string
|
||
provider_status: string
|
||
normalized_status: string
|
||
status_scope: letter | validation | sending | track_and_trace | webhook | return_mail | unknown
|
||
observed_at: timestamp
|
||
raw_provider_response_ref: string?
|
||
```
|
||
|
||
## 19. Normalized Pingen Status Categories
|
||
|
||
The Pingen flavor SHOULD map native statuses into these normalized categories:
|
||
|
||
```text
|
||
letter.created
|
||
letter.uploaded
|
||
letter.validation_pending
|
||
letter.validation_passed
|
||
letter.validation_failed
|
||
letter.action_required
|
||
letter.ready_for_sending
|
||
letter.submitted_for_sending
|
||
letter.processing
|
||
letter.handed_to_postal_service
|
||
letter.sent
|
||
letter.delivery_confirmed
|
||
letter.undeliverable
|
||
letter.return_received
|
||
letter.cancelled
|
||
letter.failed
|
||
letter.status_unknown
|
||
```
|
||
|
||
Where Pingen status granularity is insufficient, the adapter MUST use the nearest weaker event and preserve the raw status in metadata.
|
||
|
||
## 20. Pingen-to-Generic Event Mapping
|
||
|
||
| Pingen workflow event | Generic event | Evidence interpretation |
|
||
| -------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------- |
|
||
| File/letter accepted | `delivery.payload.accepted` | Digital submission accepted |
|
||
| Letter rejected | `delivery.payload.rejected` | Attempt failed |
|
||
| Validation passed | `payload.validation_passed` | Provider-processable |
|
||
| Letter issue | `payload.validation_failed` or `delivery.payload.rejected` | Issue/action-required/failure |
|
||
| Ready for sending | `delivery.payload.available` | Operational readiness |
|
||
| Auto-send triggered | `delivery.payload.submitted` | Provider instructed to send |
|
||
| Manual send triggered | `delivery.payload.submitted` | Provider instructed to send |
|
||
| Processing | `delivery.production.started` | Provider processing began |
|
||
| Sent-letter event | `delivery.postal.handed_over` if semantics support; otherwise `delivery.payload.submitted` | Strong dispatch or send-process evidence |
|
||
| Delivered-letter event | `delivery.postal.delivery_confirmed` if product/status supports | Strong delivery evidence |
|
||
| Undeliverable-letter event | `delivery.postal.undeliverable` | Strong negative evidence |
|
||
| Return details received | `delivery.postal.return_received` | Strong negative evidence |
|
||
| Track & Trace update | status-specific mapped event | Preserve raw status |
|
||
| Cancellation | `delivery.payload.failed` or cancellation metadata | Attempt stopped |
|
||
|
||
## 21. Evidence Grading Rules
|
||
|
||
### 21.1 Pingen Letter Accepted
|
||
|
||
```yaml
|
||
event_type: delivery.payload.accepted
|
||
evidence_grade:
|
||
strength: weak
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: medium
|
||
interaction_certainty: none
|
||
timing_certainty: medium
|
||
channel_certainty: medium
|
||
non_repudiation_strength: low
|
||
notes:
|
||
- Pingen accepted the letter or file into the API workflow.
|
||
- This does not prove validation, sending, postal handover, or delivery.
|
||
```
|
||
|
||
### 21.2 Pingen Validation Passed
|
||
|
||
```yaml
|
||
event_type: payload.validation_passed
|
||
evidence_grade:
|
||
strength: medium
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: high
|
||
interaction_certainty: none
|
||
timing_certainty: high
|
||
channel_certainty: high
|
||
non_repudiation_strength: low
|
||
notes:
|
||
- Pingen validation passed.
|
||
- The letter can proceed to sending according to provider rules.
|
||
```
|
||
|
||
### 21.3 Pingen Letter Issue / Validation Failed
|
||
|
||
```yaml
|
||
event_type: payload.validation_failed
|
||
evidence_grade:
|
||
strength: negative
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: high
|
||
interaction_certainty: none
|
||
timing_certainty: high
|
||
channel_certainty: high
|
||
non_repudiation_strength: low
|
||
notes:
|
||
- Pingen reported a letter issue or validation failure.
|
||
- The issue should be categorized and preserved.
|
||
```
|
||
|
||
### 21.4 Pingen Sent-Letter Event
|
||
|
||
```yaml
|
||
event_type: delivery.postal.handed_over
|
||
evidence_grade:
|
||
strength: strong
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: high
|
||
interaction_certainty: none
|
||
timing_certainty: high
|
||
channel_certainty: high
|
||
non_repudiation_strength: medium
|
||
notes:
|
||
- Pingen reports the letter as sent or handed into the postal process.
|
||
- This is strong dispatch evidence.
|
||
- It does not prove that the recipient received or read the letter.
|
||
```
|
||
|
||
If the native status only means “submitted for sending” and not postal handover, the adapter MUST instead map to:
|
||
|
||
```text
|
||
delivery.payload.submitted
|
||
```
|
||
|
||
### 21.5 Pingen Delivered-Letter Event
|
||
|
||
```yaml
|
||
event_type: delivery.postal.delivery_confirmed
|
||
evidence_grade:
|
||
strength: strong
|
||
actor_certainty: low
|
||
authority_certainty: none
|
||
payload_certainty: high
|
||
interaction_certainty: low
|
||
timing_certainty: high
|
||
channel_certainty: high
|
||
non_repudiation_strength: medium
|
||
notes:
|
||
- Pingen reports delivered-letter evidence.
|
||
- This event is product- and status-dependent.
|
||
- It may still not prove that the intended human personally read the payload.
|
||
```
|
||
|
||
### 21.6 Pingen Undeliverable / Return Mail
|
||
|
||
```yaml
|
||
event_type: delivery.postal.return_received
|
||
evidence_grade:
|
||
strength: negative
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: high
|
||
interaction_certainty: none
|
||
timing_certainty: high
|
||
channel_certainty: high
|
||
non_repudiation_strength: medium
|
||
notes:
|
||
- Pingen reports undeliverable or return-mail processing.
|
||
- This is strong negative evidence for the physical delivery attempt.
|
||
```
|
||
|
||
## 22. Pingen Evidence Assessment
|
||
|
||
The Pingen flavor SHOULD provide a Pingen-native evidence assessment.
|
||
|
||
```yaml
|
||
PingenEvidenceAssessment:
|
||
hybridmail_letter_id: string
|
||
pingen_letter_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?
|
||
```
|
||
|
||
### 22.1 Pingen Adapter Success Subclasses
|
||
|
||
```text
|
||
success.letter_created
|
||
success.validation_passed
|
||
success.ready_for_sending
|
||
success.submitted_for_sending
|
||
success.sent_letter
|
||
success.handed_to_postal_service
|
||
success.delivery_confirmed
|
||
success.return_info_processed
|
||
```
|
||
|
||
### 22.2 Pingen Adapter Fail Subclasses
|
||
|
||
```text
|
||
fail.upload_rejected
|
||
fail.validation_failed
|
||
fail.letter_issue
|
||
fail.address_position_invalid
|
||
fail.address_invalid
|
||
fail.unsupported_file_type
|
||
fail.unsupported_country
|
||
fail.product_invalid
|
||
fail.send_limit_exceeded
|
||
fail.provider_rejected
|
||
fail.cancelled
|
||
fail.undeliverable
|
||
fail.return_received
|
||
```
|
||
|
||
### 22.3 Pingen Adapter Undef Subclasses
|
||
|
||
```text
|
||
undef.pending
|
||
undef.uploaded_only
|
||
undef.validation_pending
|
||
undef.action_required
|
||
undef.ready_but_not_sent
|
||
undef.auto_send_pending
|
||
undef.submitted_for_sending
|
||
undef.processing
|
||
undef.sent_but_delivery_unproven
|
||
undef.handed_to_postal_service_but_delivery_unproven
|
||
undef.track_and_trace_pending
|
||
undef.return_mail_pending
|
||
undef.no_final_status_expected
|
||
undef.delivered_status_semantics_unclear
|
||
undef.conflicting_evidence
|
||
undef.channel_degraded
|
||
```
|
||
|
||
## 23. Pingen Workflow States
|
||
|
||
The Pingen flavor SHOULD support these normalized workflow states:
|
||
|
||
```text
|
||
created
|
||
upload_requested
|
||
uploaded
|
||
upload_failed
|
||
validation_pending
|
||
validation_passed
|
||
validation_failed
|
||
action_required
|
||
ready_for_sending
|
||
auto_send_pending
|
||
send_requested
|
||
submitted_for_sending
|
||
processing
|
||
sent
|
||
handed_to_postal_service
|
||
delivery_confirmed
|
||
undeliverable
|
||
return_received
|
||
cancelled
|
||
failed
|
||
unknown
|
||
```
|
||
|
||
These are Pingen-flavor states, not coordination result states.
|
||
|
||
## 24. Pingen Idempotency and Duplicate Send Prevention
|
||
|
||
Pingen publicly supports Idempotency-Key headers. The Pingen flavor MUST use them for externally visible create/send operations where supported.
|
||
|
||
The adapter MUST also maintain local idempotency records to prevent duplicate physical sends.
|
||
|
||
Idempotency scope SHOULD include:
|
||
|
||
```text
|
||
coordination_case_id
|
||
participant_id
|
||
delivery_id
|
||
payload_id
|
||
payload_integrity_hash
|
||
provider_flavor
|
||
postal recipient
|
||
letter options
|
||
auto_send flag
|
||
```
|
||
|
||
A repeated `delivery.submit_letter` request with the same idempotency key MUST NOT create a second physical send.
|
||
|
||
If the repeated request refers to a changed payload, address, auto-send setting, or delivery product, the adapter MUST reject it as an idempotency conflict unless a new idempotency key is provided.
|
||
|
||
## 25. Pingen Rate Limits and Send Limits
|
||
|
||
Pingen publicly advertises rate limiting and configurable send limits.
|
||
|
||
The adapter SHOULD model rate-limit and send-limit events.
|
||
|
||
```yaml
|
||
PingenLimitState:
|
||
provider_account_ref: string?
|
||
rate_limit_status: available | limited | exhausted | unknown
|
||
send_limit_status: available | limited | exhausted | unknown
|
||
reset_at: timestamp?
|
||
metadata: object?
|
||
```
|
||
|
||
Relevant events:
|
||
|
||
```text
|
||
system.provider.degraded
|
||
delivery.payload.rejected
|
||
```
|
||
|
||
If a send limit blocks dispatch, the adapter SHOULD classify the letter as:
|
||
|
||
```text
|
||
undef.pending
|
||
```
|
||
|
||
if retry/continuation is expected, or:
|
||
|
||
```text
|
||
fail.provider_rejected
|
||
```
|
||
|
||
if the provider rejects the request as final.
|
||
|
||
## 26. Pingen Webhook Handling
|
||
|
||
The Pingen flavor SHOULD prefer webhooks for status changes where available and use polling as a fallback.
|
||
|
||
Webhook handling MUST:
|
||
|
||
* verify authenticity where supported.
|
||
* deduplicate events.
|
||
* preserve raw payload references.
|
||
* map categories conservatively.
|
||
* accept late return-mail events.
|
||
* emit per-letter evidence events.
|
||
* avoid treating webhook delivery to the adapter as postal delivery to the recipient.
|
||
|
||
```yaml
|
||
PingenWebhookHandling:
|
||
enabled: boolean
|
||
verification_mode: signature | shared_secret | ip_allowlist | none | unknown
|
||
subscribed_categories:
|
||
- letter_issue
|
||
- sent_letter
|
||
- undeliverable_letter
|
||
- delivered_letter
|
||
fallback_polling_enabled: boolean
|
||
```
|
||
|
||
## 27. Pingen Status Polling
|
||
|
||
The Pingen flavor MUST support polling if the API exposes status retrieval.
|
||
|
||
```yaml
|
||
PingenStatusPollingConfig:
|
||
enabled: boolean
|
||
initial_delay_seconds: integer
|
||
interval_seconds: integer
|
||
max_duration_seconds: integer
|
||
terminal_statuses:
|
||
- delivered
|
||
- undeliverable
|
||
- returned
|
||
- failed
|
||
- cancelled
|
||
no_final_status_expected_after_seconds: integer?
|
||
```
|
||
|
||
Polling MUST preserve status history and MUST NOT overwrite raw evidence.
|
||
|
||
## 28. Pingen Raw Event Preservation
|
||
|
||
The Pingen flavor SHOULD preserve raw provider responses or references to them.
|
||
|
||
```yaml
|
||
RawPingenEventRef:
|
||
raw_event_id: string
|
||
source: api_response | status_poll | webhook | track_and_trace | validation | operator
|
||
endpoint: string?
|
||
storage_ref: string?
|
||
received_at: timestamp
|
||
redacted: boolean
|
||
```
|
||
|
||
Normalized events SHOULD reference raw Pingen event data where available.
|
||
|
||
## 29. Pingen Channel Health
|
||
|
||
```yaml
|
||
PingenChannelHealth:
|
||
provider_name: pingen
|
||
provider_account_ref: string?
|
||
status: healthy | degraded | failing | unknown
|
||
authentication_status: valid | expired | missing | insufficient | unknown
|
||
api_status: healthy | degraded | unavailable | unknown
|
||
upload_status: healthy | degraded | unavailable | unknown
|
||
validation_status: healthy | degraded | unavailable | unknown
|
||
track_and_trace_status: healthy | degraded | unavailable | unknown
|
||
webhook_status: healthy | degraded | unavailable | not_configured | unknown
|
||
rate_limit_status: available | limited | exhausted | unknown
|
||
send_limit_status: available | limited | exhausted | unknown
|
||
staging_status: healthy | degraded | unavailable | not_configured | unknown
|
||
known_degradations:
|
||
- string
|
||
```
|
||
|
||
Health events SHOULD map to:
|
||
|
||
```text
|
||
system.provider.degraded
|
||
system.provider.unavailable
|
||
system.adapter.health_changed
|
||
```
|
||
|
||
## 30. Security Requirements
|
||
|
||
The Pingen flavor MUST:
|
||
|
||
* protect Pingen API credentials.
|
||
* use secure transport.
|
||
* use provider Idempotency-Key support where available.
|
||
* maintain local idempotency records.
|
||
* avoid duplicate physical sends.
|
||
* verify webhooks where supported.
|
||
* avoid logging document contents.
|
||
* protect postal recipient data.
|
||
* protect return-mail details.
|
||
* separate staging/sandbox and production configuration.
|
||
* support tenant/account separation where applicable.
|
||
|
||
## 31. Privacy Requirements
|
||
|
||
The Pingen flavor SHOULD:
|
||
|
||
* store payload references instead of payload content where possible.
|
||
* support metadata-only mode after provider submission.
|
||
* mask postal address data in logs.
|
||
* support configurable retention of raw provider responses.
|
||
* support configurable retention of return-mail details.
|
||
* separate operational diagnostics from coordination evidence.
|
||
* document provider-side retention limitations.
|
||
* support deletion or anonymization workflows where legally possible.
|
||
|
||
## 32. Reliability Requirements
|
||
|
||
The Pingen flavor MUST support:
|
||
|
||
* idempotent create/send requests.
|
||
* duplicate webhook event detection.
|
||
* out-of-order status handling.
|
||
* polling-based status recovery.
|
||
* late return-mail events.
|
||
* late delivered-letter events.
|
||
* retryable upload failures.
|
||
* non-retryable validation failures.
|
||
* provider timeout handling.
|
||
* validation issue preservation.
|
||
* rate-limit handling.
|
||
* send-limit handling.
|
||
* Track & Trace history preservation.
|
||
* correlation preservation.
|
||
* dead-letter handling for unprocessable provider responses.
|
||
|
||
## 33. Minimal API Surface
|
||
|
||
The Pingen flavor SHOULD implement or expose these conceptual operations through `hybridmail-connect`.
|
||
|
||
### 33.1 Adapter Contract Operations
|
||
|
||
```text
|
||
GET /adapter/descriptor
|
||
GET /adapter/health
|
||
POST /adapter/actions
|
||
POST /adapter/events/provider
|
||
GET /adapter/events
|
||
GET /adapter/letters/{id}/timeline
|
||
GET /adapter/letters/{id}/assessment
|
||
```
|
||
|
||
### 33.2 Pingen Flavor Operations
|
||
|
||
```text
|
||
POST /pingen/letters
|
||
POST /pingen/letters/{id}/send
|
||
POST /pingen/letters/{id}/cancel
|
||
GET /pingen/letters/{id}
|
||
GET /pingen/letters/{id}/status
|
||
GET /pingen/letters/{id}/track-and-trace
|
||
GET /pingen/letters/{id}/return-info
|
||
GET /pingen/letters/{id}/delivery-confirmation
|
||
GET /pingen/letters/{id}/timeline
|
||
GET /pingen/letters/{id}/assessment
|
||
POST /pingen/webhooks
|
||
GET /pingen/channel-health
|
||
```
|
||
|
||
The exact provider endpoint names MUST be implemented according to the live Pingen API documentation. The conceptual operations above define the adapter-facing semantic model.
|
||
|
||
## 34. Example End-to-End Flow
|
||
|
||
### 34.1 Single Pingen Letter with Manual Send
|
||
|
||
1. `coordination-engine` creates a coordination case.
|
||
2. A PDF payload is registered.
|
||
3. Policy selects `hybridmail-connect` with provider flavor `pingen`.
|
||
4. `coordination-engine` sends `delivery.submit_letter`.
|
||
5. `hybridmail-connect` creates a Pingen letter with `auto_send=false`.
|
||
6. Pingen accepts the letter.
|
||
7. The adapter emits `delivery.payload.accepted`.
|
||
8. Pingen validates the letter.
|
||
9. If validation passes, the adapter emits `payload.validation_passed`.
|
||
10. Policy approves sending.
|
||
11. The adapter sends or submits the letter.
|
||
12. The adapter emits `delivery.payload.submitted`.
|
||
13. Pingen Track & Trace reports sent-letter progress.
|
||
14. The adapter emits `delivery.postal.handed_over` only if the native event semantics support physical dispatch/handover.
|
||
15. `coordination-engine` evaluates whether dispatch evidence is sufficient for the case policy.
|
||
|
||
### 34.2 Auto-Send Letter
|
||
|
||
1. `coordination-engine` submits a letter with `auto_send=true`.
|
||
2. The adapter validates idempotency and payload identity.
|
||
3. Pingen accepts the request.
|
||
4. Validation and send may proceed without separate manual approval.
|
||
5. The adapter emits distinct events for validation and send stages as they become observable.
|
||
6. If validation fails, the adapter emits `payload.validation_failed`.
|
||
7. If sending proceeds, the adapter emits `delivery.payload.submitted` and later stronger status events.
|
||
|
||
### 34.3 Pingen Letter Issue
|
||
|
||
1. Pingen reports a letter issue through webhook or status polling.
|
||
2. The adapter classifies the issue.
|
||
3. The adapter emits `payload.validation_failed` or `delivery.payload.rejected`.
|
||
4. `coordination-engine` marks the participant attempt as action-required or failed.
|
||
5. Policy requests correction, alternate document generation, or fallback.
|
||
|
||
### 34.4 Undeliverable / Return Mail
|
||
|
||
1. A letter was sent.
|
||
2. Pingen later reports it as undeliverable or return mail is processed.
|
||
3. The adapter emits `delivery.postal.undeliverable` or `delivery.postal.return_received`.
|
||
4. `coordination-engine` updates the participant delivery assessment.
|
||
5. Policy may trigger address correction, alternate channel, or manual review.
|
||
|
||
### 34.5 Delivered-Letter Product
|
||
|
||
1. A letter is sent using a delivery-confirmation-capable product.
|
||
2. Pingen reports delivered-letter evidence.
|
||
3. The adapter confirms the product/status semantics.
|
||
4. The adapter emits `delivery.postal.delivery_confirmed`.
|
||
5. `coordination-engine` evaluates whether this satisfies the intended result.
|
||
|
||
## 35. Pingen MVP Scope
|
||
|
||
The first Pingen flavor implementation should include:
|
||
|
||
1. Pingen provider flavor descriptor.
|
||
2. Adapter descriptor integration.
|
||
3. Pingen credential/config handling.
|
||
4. Letter creation or upload.
|
||
5. Auto-send flag support.
|
||
6. Address-position option mapping.
|
||
7. Validation result mapping.
|
||
8. Sending submission.
|
||
9. Track & Trace status polling.
|
||
10. Webhook ingestion for supported categories.
|
||
11. Return-mail / undeliverable event mapping.
|
||
12. Delivered-letter event mapping, product-dependent.
|
||
13. Evidence event generation.
|
||
14. Letter timeline.
|
||
15. Pingen evidence assessment.
|
||
16. Provider idempotency header usage.
|
||
17. Local idempotency protection.
|
||
|
||
### MVP Required Pingen Events
|
||
|
||
```text
|
||
delivery.payload.accepted
|
||
delivery.payload.rejected
|
||
payload.validation_passed
|
||
payload.validation_failed
|
||
delivery.payload.available
|
||
delivery.payload.submitted
|
||
delivery.production.started
|
||
delivery.postal.handed_over
|
||
delivery.postal.delivery_confirmed
|
||
delivery.postal.undeliverable
|
||
delivery.postal.return_received
|
||
delivery.postal.status_unknown
|
||
system.provider.degraded
|
||
system.provider.unavailable
|
||
```
|
||
|
||
Where a provider status is unavailable or semantically unclear, the adapter MUST emit the weakest safe event and preserve the raw status.
|
||
|
||
## 36. Pingen MVP Acceptance Criteria
|
||
|
||
The Pingen flavor MVP is acceptable when it can:
|
||
|
||
1. Accept a coordination-compatible `delivery.submit_letter` request.
|
||
2. Create or simulate a Pingen letter.
|
||
3. Preserve correlation and idempotency.
|
||
4. Use Pingen Idempotency-Key support where available.
|
||
5. Prevent duplicate physical sends for duplicate idempotency keys.
|
||
6. Support auto-send configuration.
|
||
7. Map validation success to `payload.validation_passed`.
|
||
8. Map letter issues or validation failure to `payload.validation_failed`.
|
||
9. Map send submission to `delivery.payload.submitted`.
|
||
10. Map sent-letter evidence conservatively.
|
||
11. Map delivered-letter evidence only when provider/product semantics support delivery confirmation.
|
||
12. Map undeliverable/return-mail evidence to strong negative delivery evidence.
|
||
13. Provide a Pingen letter timeline.
|
||
14. Provide a Pingen evidence assessment.
|
||
15. Integrate with `coordination-engine` without overclaiming physical delivery or human awareness.
|
||
|
||
## 37. Open Questions
|
||
|
||
1. Which exact Pingen native statuses map to `delivery.production.started`, `delivery.postal.handed_over`, and `delivery.postal.delivery_confirmed`?
|
||
2. Which event payload fields are present for Pingen webhook categories in the target API version?
|
||
3. How does the target Pingen account expose delivery-confirmation products?
|
||
4. Which return-mail details are available through API versus email/webhook?
|
||
5. Does the implementation need organization/workspace selection before letter creation?
|
||
6. Which Pingen country/product combinations are enabled for the target account?
|
||
7. How should configurable send limits be surfaced to coordination-engine policy?
|
||
8. How should staging/full-feature simulation be represented in evidence grades?
|
||
9. Which address-position values are available in the live API?
|
||
10. Should Pingen return details update a shared postal address quality registry?
|
||
|
||
## 38. Non-Goals
|
||
|
||
The Pingen flavor is not:
|
||
|
||
* a document authoring system.
|
||
* a PDF renderer by default.
|
||
* a legal notice system by itself.
|
||
* a postal carrier.
|
||
* a general Pingen UI replacement.
|
||
* the owner of coordination case success.
|
||
* the owner of contract, payment, or signature result semantics.
|
||
|
||
It integrates Pingen hybrid-mail capabilities into the coordination framework.
|
||
|
||
## 39. Summary
|
||
|
||
`HybridmailPingenSpecification.md` defines the Pingen provider flavor for `hybridmail-connect`.
|
||
|
||
The Pingen flavor models Pingen as a letter-centric hybrid-mail provider with:
|
||
|
||
* letter/file creation
|
||
* validation
|
||
* configurable address position
|
||
* auto-send/manual-send control
|
||
* Track & Trace
|
||
* webhook status categories
|
||
* sent-letter evidence
|
||
* delivered-letter evidence where product-supported
|
||
* undeliverable and return-mail evidence
|
||
* idempotency and rate-limit support
|
||
* staging/simulation support
|
||
|
||
The key rule is:
|
||
|
||
> Pingen events are provider, production, postal-chain, Track & Trace, and return-mail evidence. They are not automatic coordination result satisfaction. hybridmail-connect reports Pingen-channel facts and uncertainty. coordination-engine evaluates intended results.
|
||
|