From Inbox to Sales Order: Automating PO Processing with Azure Document Intelligence, Power Automate, and Sage 100 Visual Integrator
Watch: How the Automation Works
Every business that sells to other businesses knows this routine: a customer emails a PDF purchase order, someone opens it, reads the line items, and manually types them into the ERP system to create a sales order. This post walks through a production-tested automation pattern that removes that manual step, connecting email intake, AI-based document extraction, Dynamics 365 CRM validation, and Sage 100 order creation into one continuous pipeline.
The architecture, end to end
1. Email intake. A Power Automate cloud flow watches a shared mailbox for incoming messages with PDF attachments. When a new PO email arrives, the attachment is saved and passed downstream automatically.
2. Extraction with Azure Document Intelligence. The PDF is sent to a custom-trained Document Intelligence model built to recognize purchase order layouts: PO number, customer, ship-to address, line items, quantities, unit prices, requested dates. The service returns structured JSON, including bounding-region coordinates and a confidence value for every field, so downstream logic can decide what to trust and what to send for human review.
3. Matching and validation logic. Rather than pushing extracted data straight into the ERP, a dedicated matching service (an Azure Function) compares the extracted PO lines against the corresponding sales quote already sitting in Dynamics 365 CRM. It checks line items against quoted unit price within a small tolerance band and reconciles product codes, producing a match status per line rather than a single pass or fail flag. Lines that don't match cleanly get routed for review instead of silently proceeding.
4. CRM-side conditional logic. Inside the Dynamics 365 CRM flow, conditions check things like whether a record actually has a contact linked before attempting to look up contact details. This is exactly the kind of check that prevents an automation from failing downstream on empty or null values — a related record can exist without every field populated, and the flow needs to branch cleanly rather than assume otherwise.
5. Sales order creation via Sage 100 Visual Integrator. Instead of driving the Sage 100 desktop UI screen-by-screen, this build uses Sage 100's own Visual Integrator (VI) module — an ERP-native import/export engine that runs predefined "jobs" against CSV or XML data without needing a modern REST API layer. A scheduled process invokes the Sage business framework in unattended mode, pointing it at a specific VI import job that creates the sales order directly from the validated, matched data. The same mechanism can run a corresponding export job afterward to read back the newly created sales order number, or, as a more resilient fallback, the process can parse the VI job's own log file to extract the resulting order number without depending on a second export step succeeding.
Why this combination works
Each component does the job it's actually good at. Document Intelligence handles inconsistent PO layouts far better than fixed-position parsing. The matching service catches pricing or product mismatches before they ever reach the ERP, instead of trusting extraction blindly. Dynamics 365 CRM stays the source of truth for quotes, contacts, and account context, with explicit branching logic rather than assumptions about which fields are populated. And Visual Integrator bridges the gap to Sage 100 without custom ERP development, using the same import mechanism Sage already ships with.
Development steps
- Set up the mailbox trigger. Create a Power Automate cloud flow using the "When a new email arrives" trigger scoped to the shared PO inbox, filtered to messages with attachments, and save the PDF attachment to a staging location.
- Train the Document Intelligence model. Collect a representative sample of real customer PO templates, label the fields you need (PO number, line items, quantities, pricing, ship-to) in Document Intelligence Studio, and train a custom extraction model rather than relying on a generic prebuilt model.
- Call the extraction API. From the flow, send the staged PDF to your trained model's analyze endpoint and poll for the result, which returns structured JSON with a confidence score per field.
- Build the matching service. Stand up an Azure Function that accepts the extracted PO data, looks up the matching CRM quote, and compares line items on price (within a tolerance) and product code, returning a per-line match status rather than a single boolean.
- Add CRM validation branching. In the Dynamics 365 CRM flow, add explicit conditions that check whether required lookup fields are actually populated before using them downstream, rather than assuming a related record always has the field you expect.
- Route low-confidence or mismatched lines for review. Anything below your confidence or match threshold should go to a Teams approval or review queue instead of proceeding automatically.
- Trigger the Sage 100 Visual Integrator job. Once data is validated, write it to the CSV/XML format your VI import job expects, then invoke the Sage business framework executable in unattended mode against that job.
- Capture the resulting sales order number. Either run a corresponding VI export job or parse the import job's log file, then write the order number back onto the CRM record so sales and support teams have visibility without opening Sage 100 directly.
- Log every run. Because unattended VI jobs run silently, write a timestamped log for each execution, this is often the only diagnostic trail when something doesn't behave as expected.
What to plan for if you build this yourself
Log everything: unattended VI jobs run silently, so a timestamped log file per run is often your only diagnostic trail when a job doesn't behave as expected. Don't trust a single condition to mean what its label says — verify what a condition actually evaluates, not just what it's named. Build a confidence threshold into your extraction step and route uncertain matches to a human reviewer rather than assuming AI extraction is always right. And treat credentials and internal paths powering these jobs the same way you'd treat any production secret: outside of source control, outside of documentation, and never hardcoded into a script that might get shared.