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