By Trevor Daniel

Erin Claim System

By Trevor Daniel · Created Apr 1, 2026 · Updated 3 hours ago public

Erin (erin.yodel.co.uk) is a unified REST API portal used by both Yodel and InPost for automated claim submission and monitoring. A single Temporal workflow — ErinClaimWorkflow — handles both couriers, routing each claim through the correct path based on claim type and client configuration.

Couriers Yodel, InPost
Portal erin.yodel.co.uk (REST API)
Workflow ErinClaimWorkflow (Temporal)
Clients Any client with Yodel or InPost credentials
Trigger Manual CLI backfill — no automatic schedule today
Supported claim reasons Lost, Delivery Dispute, Damaged, Late

Claim Types

The workflow handles two claim types, determined from ClaimReason.Name:

Same endpoint and payload shape for both types:

POST /api/gateway/claims/parcels/{altPclNo}/{instanceId}/claims
{ "type": "LOSS" | "DAMAGE", "costPrice": "28.00", "retailPrice": "35.00",
  "itemDescription": "...", "description": "Parcel reported as lost/damaged.", "evidence": ["uuid1"] }

Settlement amounts: Erin returns a claimsLevel field alongside settlementAmount. Settlement is min(retailPrice, claimsLevel) — i.e. the claim is paid in full up to a per-category cap set in the portal contract. For example, M&S hamper claims have a £25 cap regardless of retail price (a £50 hamper settles at £25; a £20 hamper settles at £20). The cap is a Yodel contract configuration, not something we control.


How We Decide: Query First vs Direct Claim

This routing applies to LOSS claims only. DAMAGE claims always go direct.

We know this from evidence, not assumption. M&S is the strictest client we have on Erin — they have allowClaimsWithoutQueries=false, meaning every LOSS claim requires a query to be raised first before a formal claim can be submitted. Despite this, when we queried the full M&S DAMAGE claim history on Erin, we found 148 DAMAGE claims submitted between 2020 and 2026, none of which had a prior query. Every one went straight to claim submission and was accepted. If the query requirement applied to DAMAGE claims, at least some of those would have been rejected — they weren't.

This tells us the allowClaimsWithoutQueries flag is only evaluated by Erin for LOSS claims. DAMAGE claim submissions hit the same endpoint but Erin does not apply the query gate to them.

Before submitting any LOSS claim, the workflow calls the Erin validate endpoint:

GET /api/gateway/claims/{altPclNo}/{instanceId}/validate

Response fields used:

Outcome routing table:

claimAllowed messageList contains Meaning Action
true Client may submit directly → Submit LOSS claim (Path A)
false "A query must be raised on this parcel" Client's contract requires query-first → Raise query (Path B)
false "A query was not raised within 14 days" Claim window has passed → Reject — Outside Window (status 9, reason 19)
false Other Already submitted or not eligible → Log and end workflow

Example — Path A client (e.g. Frasers Group / InPost):

Clients configured with allowClaimsWithoutQueries: true in the Erin portal get claimAllowed: true from the validate endpoint — Erin accepts the LOSS claim immediately with no query step required. Frasers Group is one such client (6 InPost contracts, all inpostClient: true).

Example — Path B client (e.g. M&S / Yodel):

Clients with allowClaimsWithoutQueries: false get claimAllowed: false with the message "A query must be raised on this parcel". Our code detects this specific message string and routes to Path B automatically. M&S is one such client. Any future Yodel or InPost client with the same contract configuration will follow the same path.

Important distinction — validate vs query eligibility:

The validate endpoint checks claim submission eligibility only. There is no time window restriction on raising queries. The "14 days" message only blocks formal LOSS claim submission — queries can still be raised at any time after that. Raising a query does not reopen the claim submission window.


How Claims Enter the System

Claims are created in Claimit when a client uploads them via the portal. They start in status New. A CLI command is run manually to find New claims and start Temporal workflows for each one.

backfill yodel      ← picks up New + Investigating Yodel claims
backfill inpost     ← picks up New + Investigating InPost claims

Known gap: There is no automated schedule. New claims sit as New until an operator runs the backfill command manually.

What the backfill does per claim:

Condition Behaviour
Claim status = New Sets IsBackfill=false → submission runs
Claim status = Investigating Sets IsBackfill=true → skips submission, enters query polling only
DryRun config Yodel: { DryRun: false }, InPost: { DryRun: false }

Three Paths: Direct, Query-First, and Damage


Phase 1: Evidence Generation (All Paths)

Before any submission to Erin, two PDF documents must exist for the claim. These activities run in parallel and are fully idempotent — they skip silently if a matching file already exists.

Step 1a — Proof of Purchase (POP)

Activity: ErinGenerateProofOfPurchaseActivity

  1. Fetches customer name, delivery address, email, and phone from Erin API (GetParcelDetailsAsync)
  2. Renders a PDF from an HTML template via Playwright
  3. Uploads to Azure Blob Storage (production container)

Guards:

Retry policy: 3 attempts · 30s → 60s → 120s backoff · 10 min timeout


Step 1b — Proof of Cost (POC)

Activity: ErinGenerateProofOfCostActivity

  1. Net cost = ValueWholesaleCost; VAT derived at standard UK 20% rate; total shown on document
  2. Renders PDF via Playwright, uploads to Azure Blob Storage

Guards:

Retry policy: 3 attempts · 30s → 60s → 120s backoff · 10 min timeout


Phase 2: Submit to Erin

Activity: SubmitClaimToErinActivity

This activity performs a five-step authenticated API sequence:

Retry policy: 3 attempts · 30s → 60s → 120s backoff · 5 min timeout

Submission Outcomes

Outcome What it means What happens next
Submitted Erin accepted the claim Erin claimId stored as CourierClaimReference → enter Poll Claim Status
Pending Parcel not found in Erin portal (e.g. not yet scanned at locker) Workflow ends; claim stays as-is; retry by re-running backfill later
Rejected Claim refused (e.g. return parcel / LRT service → HTTP 403) Workflow ends; claim stays failed
QueryRequired LOSS claim: client has allowClaimsWithoutQueries=false Route to Query Path (M&S)

DAMAGE claims never produce a QueryRequired outcome — they are accepted directly by Erin regardless of the client's allowClaimsWithoutQueries setting.


Path A: Direct Claim (LOSS and DAMAGE)

After a Submitted outcome, the workflow enters continuous polling to watch for Erin's decision.


Path B: Query Path (LOSS claims, M&S-style clients only)

When the submission outcome is QueryRequired, the workflow pivots to raising a formal query with Yodel before a claim can be submitted.

Step B1 — Raise Query

Activity: RaiseErinQueryActivity

Retry policy: 3 attempts · 30s → 60s → 120s backoff · 5 min timeout


Step B2 — Poll Query Responses

Activity: PollErinQueryResponsesActivity

Poll interval: every 12 hours · Max duration: 30 days

Each poll makes two API calls:

  1. GET /api/gateway/queries/{queryId}/responses — fetch all responses from Yodel
  2. GET /api/parcel-search/parcel-search + GET /api/gateway/parcels/{altPclNo}/{instanceId}/queries — check if the query has been closed

Retry policy: 3 attempts · 30s → 60s → 120s backoff · 5 min timeout

Decision Tree (evaluated in strict order)


AI Agent (Query Analysis)

Activity: RunErinQueryAgentActivity

Triggered when the "raise a claim" phrase is found. The agent analyses all Yodel responses to determine the appropriate claim outcome.


Complete End-to-End Flow


Claim Status Transitions

Trigger From Status To Status
Backfill starts workflow New (unchanged — workflow tracks internally)
LOSS or DAMAGE claim submitted to Erin New Submitted (3)
Query raised (M&S LOSS path) New Investigating (21)
Formal LOSS claim submitted after query trigger Investigating Submitted (3)
Erin status: ACCEPTED_PENDING_PAYMENT Submitted Accepted (5)settlementAmount stored as credit amount
Erin status: ACCEPTED_PAID Submitted or Accepted Credited (6)settlementAmount + creditNoteRef logged
Erin status: RESPONSE_REQUIRED Submitted Manual review (stays Submitted)
Erin status: REOPEN_BY_CLIENT Submitted Manual review (stays Submitted)
Query CLOSED + Yodel says "delivered" Investigating Found (12)
Validate: out-of-time-window Investigating Rejected (9) — reason: Outside Window
Parcel not at locker yet New (unchanged — workflow ends, retry later)
90-day poll timeout Submitted Manual review (stays Submitted)
30-day query timeout Investigating Manual review (stays Investigating)

Activity Retry Policies

Activity Attempts Backoff Timeout Heartbeat
Generate POP 3 30s → 60s → 120s 10 min
Generate POC 3 30s → 60s → 120s 10 min
Submit claim 3 30s → 60s → 120s 5 min
Raise query 3 30s → 60s → 120s 5 min
Poll query responses 3 30s → 60s → 120s 5 min
Check claim status 3 30s → 60s → 120s 5 min
AI agent 2 10s → 20s 10 min 30s

Error Scenarios

Error Behaviour
No ValueRetail and no POP file Non-retryable failure. Workflow cannot proceed.
No ValueWholesaleCost and no POC file Non-retryable failure. Workflow cannot proceed.
Parcel not found in Erin (Pending) Workflow ends cleanly. Retry by re-running backfill.
Return parcel / LRT service (403) Claim rejected. Workflow ends.
Validate: query not raised within 14 days Transition to Rejected (Outside Window). Workflow ends.
REOPEN_BY_CLIENT status Manual review required. Workflow ends.
Query CLOSED, no "delivered", no trigger Manual review required. Workflow ends safely without guessing outcome.
AI agent failure Non-blocking warning logged. Polling continues.
90-day claim poll timeout Manual review required.
30-day query poll timeout Manual review required.

Technology

This system runs on the Temporal workflow engine, which provides durable execution, automatic retries, and long-running timer support without infrastructure risk. If the workflow process crashes, Temporal replays history and resumes exactly where it left off — including mid-poll waits of days or weeks.

Evidence PDFs are rendered server-side using Playwright (headless browser) and stored in Azure Blob Storage (production container). All Erin API access is authenticated per-client using credentials stored in ClientCourierLogins.