
How to turn a shipping document into a clean confirmation
Extract the details from the document you received, reconcile against your records with Enrich, and render a standardized confirmation. Shown on a real public bill of lading.

Operations teams live in this loop. A document arrives, an invoice, a purchase order, a bill of lading, in whatever layout the sender used. Someone reads it, matches the details to internal records, and produces a clean, standardized document back: an order confirmation, a remittance, a shipment confirmation. The inputs are a mess and the output has to be exact.
This is a bem workflow: Extract, optionally Enrich, then Render. To keep it concrete, we ran it on a real, public bill of lading, a NileDutch liner B/L for a container moving from China to the Republic of the Congo.

The real input: a public NileDutch liner bill of lading. Dense, multi-block, half in French. This is what "the document you received" actually looks like.
The pattern
1inbound document (PDF/scan)2 -> Extract pull the header + line details, verbatim3 -> Enrich match against your own records (customer, SKU, terms)4 -> Render produce the standardized confirmation .docx
1. Extract what the confirmation needs
Write a schema for the fields your confirmation carries, and tell the model to copy values verbatim. Real documents are messy: this B/L lists weight as 28.000.00 kgs and mixes English and French. You want what is there, not a cleaned-up guess.
1{2 "type": "object",3 "description": "Extract a bill of lading. Copy values verbatim.",4 "properties": {5 "blNumber": { "type": "string" },6 "carrier": { "type": "string" },7 "shipper": { "type": "string" },8 "consignee": { "type": "string" },9 "portOfLoading": { "type": "string" },10 "portOfDischarge": { "type": "string" },11 "placeOfDelivery": { "type": "string" },12 "containerNumber": { "type": "string" },13 "packages": { "type": "string" },14 "grossWeight": { "type": "string" }15 },16 "required": ["blNumber", "shipper", "consignee"]17}
Run on the real B/L, bem returned the values cleanly: blNumber: 079243, carrier: NileDutch, the full shipper and consignee blocks, containerNumber: NIDU 180101-5, packages: 1x 40' dry cargo SAID TO CONTAIN 664 CTNS MARCHANDISES DIVERSES.
2. Reconcile with Enrich (optional)
For invoices and POs, the sender's SKU and price are what they claim, not necessarily what you agreed. An Enrich step matches each line against a Collection of your catalog or customer master and attaches your canonical record. Point the source field at the value that is always present, the item description is usually more reliable than a SKU that is often missing.
1{2 "type": "enrich",3 "functionName": "catalog-enrich",4 "config": { "steps": [{5 "sourceField": "lineItems",6 "collectionName": "product_catalog",7 "targetField": "catalogMatch",8 "topK": 1,9 "searchMode": "hybrid"10 }]}11}
3. Author the confirmation template
A normal .docx with inline header fields and block tables:
1SHIPMENT CONFIRMATION2B/L Number: {{ bl_number }} | Carrier: {{ carrier }}3Shipper: {{ shipper }}4Consignee: {{ consignee }}56{{p routing }}7{{p cargo }}8{{p flags }}
{{ }} is inline text, {{p }} is a block. Upload it and bem derives the placeholder and style contract from the file.
4. Render, and here is the actual output
Compose the routing and cargo tables from the extracted values and send the data through the workflow. bem returned this finished .docx:

Generated from the real B/L. Routing (Port Victoria to Pointe-Noire), the container and seal, 664 cartons, 28,000 kg, and a review section that resolved cleanly because every field was present.
Why the review section matters
The temptation with document automation is to make the output look finished no matter what. That is how a wrong weight or a missing consignee slips into a confirmation nobody re-reads. bem does the opposite: Render will not silently fill a field it cannot resolve, and anything ambiguous surfaces in a review block instead of getting written as fact. On this B/L everything resolved, so the review section says so. On the next one, if the place of delivery is missing, it will tell you.
The same three functions handle an invoice into an order confirmation, a purchase order into an acknowledgment, or a bill of lading into a shipment confirmation. One document in, one clean document out.
The build above runs on the public bem API. [Start here.](https://app.bem.ai/auth/sign-up)

Written by
Antonio Bustamante
Jul 23, 2026


Ready to see it in action?
Talk to our team to walk through how Bem can work inside your stack.
Talk to the team