Architecture of the procurement mailer integration
How the pieces fit together — the runtime call path an email prompt takes, the component makeup of the caller / bridge / data-source, and the procurement data domain the agent reasons over. The companion setup runbook covers how to build each stage.
← Back to the setup runbookTwo planes
The solution splits cleanly into two planes. The call plane is the request/response path — a user prompt travels through Joule, into this MCP server, out to Microsoft Graph, and back as a rendered answer. The data plane is the procurement domain the agent operates against: products, stock levels, purchase orders, allocations. This server is the bridge between the two — it turns an agent's intent into a concrete mailbox action.
Runtime call flow
One email prompt, nine steps. Invocation runs left-to-right; data flows back right-to-left as dashed returns. The MCP server holds no session — every call rebuilds a fresh server + transport pair.
Steps 4–5 are the OAuth2 client-credentials handshake the SAP Cloud SDK performs transparently; steps 6–7 are the actual Graph call. The server never stores a token — the graph-mail destination handles it per request.
Component architecture
The same three stages, decomposed into the parts you configure. The caller and data source are configuration on either side; the bridge in the middle is this repository.
caller
bridge · this repo
data source
← return path 3 · mail data / result · 4 · tool result → render
Procurement data model
The domain the Joule agent reasons over — the CAP backend behind the procurement service. PK marks a primary key, FK a foreign key. Product and Location are the hubs almost everything else references.
| request_idPK | int |
| requested_qty | int |
| status | varchar |
| raised_by | varchar |
| created_at | datetime |
| product_idFK | int |
| location_idFK | int |
| product_idPK | int |
| name | varchar |
| category | varchar |
| safety_stock_level | double |
| current_stock_level | double |
| stock_level_idPK | int |
| quantityOnHand | int |
| reorderPoint | int |
| safetyStock | int |
| location_idFK | int |
| product_idFK | int |
| location_idPK | int |
| name | varchar |
| type | enum |
| city | varchar |
| po_idPK | int |
| po_number | varchar |
| status | varchar |
| expected_delivery_date | datetime |
| received_date | datetime |
| disruption_reason | enum |
| product_quantity | int |
| po_total | double |
| allocation_idFK | int |
| product_idFK | int |
| shipToLocation_idFK | int |
| issue_idPK | int |
| status | varchar |
| description | text |
| created_at | datetime |
| resolved_by | varchar |
| notes | text |
| purchase_order_idFK | int |
| allocation_idPK | int |
| allocated_quantity | int |
| total_effective_cost | double |
| rationale_summary | text |
| created_at | datetime |
| suggested_by_agent | boolean |
| product_idFK | int |
| location_idFK | int |
Product1 — ∗StockLevel·ProcurementRequest·PurchaseOrder·AllocationResult(viaproduct_id)Location1 — ∗StockLevel·ProcurementRequest·AllocationResult, andPurchaseOrder(viashipToLocation_id)AllocationResult1 — ∗PurchaseOrder(a costed allocation drives one or more purchase orders)PurchaseOrder1 — ∗ProcurementIssue(disruptions logged against an order)
The mailer MCP server doesn't read these tables directly — this is the business context the agent works in. When the agent sends or reads mail about a purchase order or a stock shortfall, these are the entities it's reasoning about.