feat(api): enhance order creation with optional email metadata fields

- Added `email_sender`, `email_subject`, and `email_body` fields to the order creation input schema for improved traceability of inbound emails.
- Updated logic to include these fields in the order creation process, allowing operators to track the source of orders without needing follow-up updates.
- Enhanced documentation to reflect the new optional fields and their purpose in the order management workflow.

This update improves the audit trail for orders and enhances user experience by linking orders directly to their source emails.
This commit is contained in:
Dave 2026-08-28 13:00:31 +02:00
parent dc0139a62c
commit 5eed862007
3 changed files with 50 additions and 1 deletions

View file

@ -565,7 +565,11 @@ const tools = [
"on the inbound PO PDF) to use it verbatim as `orders.order_number`. Empty / whitespace " +
"values fall back to auto-generated `ORD-YYYY-MM-NNN` — same as omitting the field. On " +
"collision the call surfaces an MCP error envelope with code/message `order_number_collision` " +
"and a `data` block carrying the colliding order id and attempted number.",
"and a `data` block carrying the colliding order id and attempted number.\n\n" +
"Optional `email_sender` / `email_subject` / `email_body`: stamp the inbound email metadata " +
"on the order in the same call so the operator can trace it back to its source email without " +
"a follow-up `orders.update` patch. The n8n intake pipeline fills these in from the parsed " +
"email; counter / agent runs typically leave them blank.",
inputSchema: {
type: "object",
required: ["customer_id", "company_id", "items"],
@ -577,6 +581,9 @@ const tools = [
description: { type: "string", description: "Short description of the order (free-text)." },
rep: { type: "string", description: "Sales rep name (free-text)." },
order_number:{ type: "string", description: "Optional custom order_number (e.g. PO/ref printed on the inbound attachment). Empty/whitespace falls back to auto-generation." },
email_sender: { type: "string", description: "Optional From: address of the inbound email — stamped on the order for audit trail." },
email_subject:{ type: "string", description: "Optional subject line of the inbound email — stamped on the order for audit trail." },
email_body: { type: "string", description: "Optional plain-text body of the inbound email — stamped on the order for audit trail." },
items: {
type: "array",
minItems: 1,
@ -3613,6 +3620,12 @@ export async function POST(req: NextRequest) {
// Empty / whitespace order_number is treated as "not provided"
// so the service layer falls back to auto-generation.
order_number: typeof args.order_number === "string" ? args.order_number : undefined,
// Inbound email metadata — optional audit trail. Stamped
// here so the operator can trace the order back to the
// source email without a follow-up orders.update PATCH.
email_sender: typeof args.email_sender === "string" ? args.email_sender : undefined,
email_subject: typeof args.email_subject === "string" ? args.email_subject : undefined,
email_body: typeof args.email_body === "string" ? args.email_body : undefined,
description: typeof args.description === "string" ? args.description : undefined,
rep: typeof args.rep === "string" ? args.rep : undefined,
items,

View file

@ -23,6 +23,9 @@ import {
* description?: string,
* email_message_id?: string, // dedup key — returns existing order if one already exists
* order_number?: string, // optional custom order number; empty/whitespace falls back to auto-gen
* email_sender?: string, // From: address of the inbound email (audit trail)
* email_subject?: string, // Subject line of the inbound email (audit trail)
* email_body?: string, // Plain-text body of the inbound email (audit trail)
* rep?: string,
* items: [
* {
@ -97,6 +100,13 @@ export async function POST(req: NextRequest) {
// as the workflow sending `order_number: ""` when the AI didn't
// find a PO number on the inbound attachment.
order_number: typeof body?.order_number === "string" ? body.order_number : undefined,
// Inbound email metadata — optional audit-trail fields stamped
// by the n8n intake pipeline so the operator can trace the
// order back to its source email without needing the original
// attachment.
email_sender: typeof body?.email_sender === "string" ? body.email_sender : undefined,
email_subject: typeof body?.email_subject === "string" ? body.email_subject : undefined,
email_body: typeof body?.email_body === "string" ? body.email_body : undefined,
description: typeof body?.description === "string" ? body.description : undefined,
rep: typeof body?.rep === "string" ? body.rep : undefined,
items,

View file

@ -1910,6 +1910,26 @@ export interface CreateOrderInput {
* dedup check entirely.
*/
email_message_id?: string;
/**
* Free-text sender address of the inbound email that originated
* this order (RFC 5322 From: address). Optional — the n8n intake
* pipeline stamps it for traceability so the operator can see who
* emailed the PO even when customer matching falls back to a
* placeholder.
*/
email_sender?: string;
/**
* Free-text subject line of the inbound email. Optional — same
* audit purpose as `email_sender`.
*/
email_subject?: string;
/**
* Plain-text body of the inbound email. Optional. Trimmed by the
* caller; large HTML attachments stay on `quote_attachments` /
* `order_attachements` — this field is the lightweight text-only
* snapshot for the order's audit trail.
*/
email_body?: string;
/**
* Optional customer-supplied order number (e.g. the PO/reference
* printed on the inbound PO PDF that the n8n intake pipeline
@ -2067,6 +2087,12 @@ export async function createOrder(input: CreateOrderInput): Promise<CreateOrderR
description: input.description || "",
contact_name: input.contact_name || "",
email_message_id: input.email_message_id?.trim() || "",
// Inbound email metadata — optional, stamped by the n8n intake
// pipeline so the operator can trace this order back to its
// source PO email. Trimmed; empty / undefined collapses to "".
email_sender: input.email_sender?.trim() || "",
email_subject: input.email_subject?.trim() || "",
email_body: input.email_body?.trim() || "",
})) as Order;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);