An e-invoice has at least five statuses, and they are five different questions. Did the bytes arrive at the next hop? Did the document pass validation? Does the invoice legally exist? Does the counterparty agree with it? Can you prove any of this in ten years?
Most implementations collapse these into one field called status, and the collapse is where the defects live. A rejection arrives over a perfectly healthy connection. A delivery failure can be a terminal success. And the single most expensive state — “we don’t know” — usually isn’t modelled at all.
The five layers
Sorted by the question each one answers, not by the order they happen in.
- Transport status — did the message reach the next hop? An HTTP code, an AS4 acknowledgement. Purely technical, and it says nothing whatsoever about the invoice.
- Validation result — did it pass schema and business rules? Peppol’s MLR, Italy’s notifica di scarto, a JSON syntax error.
- Legal status — does the invoice exist for VAT purposes? Greece’s MARK, India’s IRN, Italy’s ricevuta di consegna.
- Counterparty acceptance — does the buyer agree? Peppol’s Invoice Response. This is a commercial axis, not a tax one.
- Audit evidence — can you reconstruct the above years later? Signatures, authentication codes, retained receipts.
These are not stages of a pipeline. They’re orthogonal. You can be transport-successful and legally non-existent at the same time — that’s the normal case for a rejection.
A rejection is a successful transport
Italy’s Sistema di Interscambio is the clearest vocabulary anyone has built for this, because it names each state separately and attaches different legal consequences to each.
Submit a FatturaPA file and the SdI runs its checks, then returns one of several notifications. A notifica di scarto (NS) is a rejection with a numeric error code — and a rejected invoice is legally treated as never issued. The sender has five days to correct and resubmit, reusing the same invoice number and date to avoid penalties. A ricevuta di consegna (RC) means the invoice is both issued and delivered. And a mancata consegna (MC) means the recipient’s channel was unreachable: the SdI parks the invoice in the buyer’s reserved area on the Agenzia delle Entrate portal, and the invoice is still legally issued — delivery failure doesn’t invalidate it for VAT — but the sender must retain the MC notification as proof and tell the customer.
Read those three again as state transitions:
- NS — transport succeeded, invoice does not exist.
- RC — transport succeeded, invoice exists, buyer has it.
- MC — transport succeeded, invoice exists, buyer does not have it, and this is a terminal success with an obligation attached rather than an error to retry.
All three arrive over the same healthy connection. A system whose status field is set from the HTTP response cannot distinguish any of them. And MC is the one that breaks naive designs: it looks like a failure, it is not a failure, and retrying it is wrong.
Peppol splits the same problem across three message types. The AS4 transport acknowledgement confirms gateway-to-gateway delivery and nothing more. The Message Level Response (MLR) reports the validation outcome, with codes like AP for accepted and RE for rejected. The Invoice Response — the business-level response — carries the buyer’s commercial decision: accepted, rejected, or under query. Three layers, three documents, and the industry considered the gap between them serious enough that Peppol finalised Message Level Status (MLS) in May 2025 specifically because MLR adoption was low and end-to-end visibility after the gateway was largely opaque.
The state nobody models: unknown
The best artefact in this entire subject is buried in SAP Hellas’s August 2024 implementation guide for Greek e-invoicing, and it’s worth reading closely.
The guide introduces a table, zgrein_edogrinv, that carries both a STATUS field and an HTTP_CODE field. Two status columns, side by side, in one table — because they answer different questions. It then defines the print logic in three branches:
- Success —
http_code = 201. The invoice reached the tax authority and a MARK was assigned. Print the MARK, the UID, the authentication code and the QR code. - Transmission failure between issuer and service provider, or a business error, or a JSON syntax error —
http_codeis neither 408 nor 201. Print an offline QR code, generated on the fly during the legal printing process. - Transmission failure between service provider and the tax authority — same offline QR treatment, different printed message.
Three things are happening here, and each is a lesson.
408 is carved out deliberately. A request timeout is the one code that means we don’t know — the submission may have landed. It gets neither the success path nor the failure path. That’s the unknown state, explicitly handled, and it’s the state most homegrown integrations skip straight past into a retry loop that duplicates invoices.
Transport failure and JSON syntax error share a branch. A network outage (not your fault, the invoice is fine) and a malformed payload (your fault, the invoice is wrong) both fall into the same condition and produce the same printed outcome. That’s the conflation this article is about, in production, in a vendor’s official guide. The branch is correct for the immediate purpose — print something legal — and wrong for everything downstream, because one of those needs a retry and the other needs a code fix.
When nobody is reachable, you mint your own evidence. The offline QR exists because the invoice has to be printed regardless, for business reasons, when communication fails through network outage or application errors. The API endpoint that issues it is literally named TransmissionFailure. That is a fifth state — issued, unreported, evidence self-generated — and it has no equivalent in any status enum that runs from pending to sent to error.
The same guide contains the whole thesis in one configuration note: the status field can drive specific messages on the invoice printout if it is not SUBMITTED. Submitted is the happy path. Everything else has to say so, on paper, to a human.
Validation is not a property of the document
This one surprises people. SAP’s own documentation on Peppol message types notes that the sending and receiving access points may be checking against different versions of the Schematron rules — so a document can pass validation at the sender’s access point and fail at the receiver’s.
Validity, then, is a property of a document and a validator and a moment in time. Italy makes the same point on a schedule: FatturaPA v1.9.1 takes effect 15 May 2026 with new error codes, and a file that validated two years ago can fail today if your generator hasn’t tracked the specification. If you cache a boolean is_valid against an invoice, you have stored the answer to a question that has since changed.
Worse, absence of rejection is not acceptance. Peppol’s MLR is an optional document — not every sender supports receiving one, and the receiver has to query the SMP to find out whether the sender does. If your model treats “no negative response” as success, it will report success for every counterparty that never agreed to tell you.
Acceptance is a different axis entirely
Tax acceptance and commercial acceptance are unrelated, and conflating them produces the worst class of bug: a legally valid invoice marked as failed because the buyer disputed it, or an accepted invoice that never legally existed.
Peppol keeps them apart by design — an MLR rejection is a technical failure you fix with better validation, while an Invoice Response rejection is a business dispute that requires a conversation. Italy keeps them apart too, and asymmetrically: public administrations can refuse an invoice received through the SdI and the system carries a separate outcome notification plus a deadline notification for when the acceptance window expires. B2B buyers get no such right — once an invoice clears for VAT, the buyer cannot reject it, and the remedy is a credit note.
So “rejected” means at least three unrelated things depending on who said it. Model the speaker, not just the verdict.
Your state machine’s transitions are legislated
Here’s the constraint that catches product teams: the transitions are not yours to design.
SAP’s Greek guide describes a framework offering submission, cancellation, resubmission and deletion of e-documents, with deletion only available in the created status. Then it records that, following a Greek ministerial decision, the cancel function is no longer available at all — companies must issue credit notes as a substitute. A transition was removed from the state machine by law, after the software was built.
The same guide ships a configuration table, EDOMETASTATUSV, whose entire purpose is assigning eDocument process status to status. Roughly twenty process states — matched, match error, cancelled, created, deviation rejected, added to summary — collapse onto three status codes. The mapping table exists precisely because the process status and the status are not the same thing, and someone had to write down the relationship. Note also that the guide’s V3 revision renamed the old status field to http_status. Somebody hit this ambiguity and fixed it in the field name.
What to build
Separate fields, not one enum. At minimum: transport outcome, validation outcome, legal identifier (present or absent), counterparty response, and evidence reference. Greece ships two of these in one table already. Derive a display status from them; never store only the derived value.
Model unknown as a first-class state. Timeouts, not just successes and failures. An unknown submission needs a reconciliation query against the authority, not a retry — retrying an invoice that landed is how you create duplicates in a system where the sequential number is legally significant.
Make failure states carry their obligation. Italy’s MC requires retaining the notification and notifying the customer. Greece’s transmission failure requires printing an offline QR. Neither is “retry later”. If your error state has no field for the obligation it triggers, someone will do it manually or not at all.
Store the evidence, not a log line. Under ViDA, Article 138(1a) removes the exemption for an intra-Community supply where the supplier hasn’t met the transmission obligation or the data is wrong — unless the failure can be duly justified to the competent authorities. “Duly justified” is a data requirement. It means the artefact that proves you tried, retained for as long as the invoice itself: Italy requires ten years of electronic preservation, and audits check the receipts, rejections and resubmission logs alongside the invoices.
Frequently asked questions
What’s the difference between an MLR and an Invoice Response in Peppol?
An MLR is a technical response about validation — it tells you whether the document was structurally acceptable and processed, using codes such as AP and RE. An Invoice Response is a business-level document carrying the buyer’s commercial decision: accepted, rejected, or under query. An MLR rejection is fixed with better validation. An Invoice Response rejection is a dispute.
If I get an HTTP 200, is my invoice filed?
No. The transport succeeded; that’s all you know. In Italy the same successful connection can carry a notifica di scarto, which means the invoice is legally treated as never issued. In Greece, only http_code = 201 with a returned MARK means the tax authority holds it.
What should happen on a timeout?
Not a blind retry. A timeout is the unknown state — the submission may have been processed. Query the authority for the document’s status before resubmitting, because re-sending a landed invoice creates a duplicate against a sequential number that carries legal weight. Greece’s guide treats HTTP 408 as its own branch for exactly this reason.
Can a delivery failure be a success?
Yes. Italy’s mancata consegna means the invoice is legally issued but the buyer’s channel was unreachable, so it sits in their portal area. It doesn’t invalidate the invoice for VAT. It’s a terminal state with two obligations — retain the notification, inform the customer — not an error to retry.
How long do I need to keep the receipts?
As long as the invoices, and treat them as part of the same record. Italy requires ten years of compliant electronic preservation and audits verify the delivery receipts, rejection notifications and resubmission logs. Under ViDA, the ability to duly justify a transmission failure is what stands between you and losing an exemption — which is only possible if the evidence survived.
