Generate Consistent Beautiful Receipts with LangChain and AgentGen
Receipts seem trivial until you need to generate thousands of them consistently. Different line items, amounts, currencies, tax rates — they all need to look identical and professional every time. In this tutorial we'll build a LangChain agent that produces beautiful, brand-consistent PDF receipts using AgentGen, all from a natural-language request or a structured API call.
What we're building
A generate_receipt LangChain tool that accepts order data and returns a CDN download URL for a polished PDF. The receipt includes:
- A branded header with your company name and accent color
- Order metadata — order ID, date, payment method
- Itemized line items with quantity, unit price, and row total
- Subtotal, tax, and grand total with proper currency formatting
- Customer info and a thank-you footer
The receipt HTML template
The foundation is a Python dataclass + template function. Using inline styles guarantees Chromium renders it identically every time, regardless of system fonts or environment:
from dataclasses import dataclass, field
from typing import List
@dataclass
class LineItem:
description: str
quantity: int
unit_price: float
@dataclass
class OrderData:
order_id: str
date: str
customer_name: str
customer_email: str
line_items: List[LineItem]
tax_rate: float = 0.08
currency_symbol: str = "$"
payment_method: str = "Credit card"
brand_name: str = "My Store"
brand_color: str = "#4f46e5"
def build_receipt_html(order: OrderData) -> str:
subtotal = sum(item.quantity * item.unit_price for item in order.line_items)
tax = subtotal * order.tax_rate
total = subtotal + tax
sym = order.currency_symbol
rows = "".join(
f"""<tr>
<td style="padding:10px 0;color:#374151;border-bottom:1px solid #f3f4f6">{item.description}</td>
<td style="padding:10px 0;text-align:center;color:#6b7280;border-bottom:1px solid #f3f4f6">{item.quantity}</td>
<td style="padding:10px 0;text-align:right;color:#374151;border-bottom:1px solid #f3f4f6">{sym}{item.unit_price:.2f}</td>
<td style="padding:10px 0;text-align:right;font-weight:600;border-bottom:1px solid #f3f4f6">{sym}{item.quantity * item.unit_price:.2f}</td>
</tr>"""
for item in order.line_items
)
return f"""<!DOCTYPE html>
<html><head><meta charset="UTF-8"></head>
<body style="margin:0;padding:0;background:#f9fafb;font-family:'Helvetica Neue',Arial,sans-serif">
<div style="max-width:600px;margin:40px auto;background:white;border-radius:12px;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,0.08)">
<!-- Brand header -->
<div style="background:{order.brand_color};padding:28px 36px;color:white">
<h1 style="margin:0 0 4px;font-size:22px;font-weight:800">{order.brand_name}</h1>
<p style="margin:0;opacity:0.8;font-size:13px">Order Receipt</p>
</div>
<!-- Order meta -->
<div style="padding:20px 36px;background:#f8faff;border-bottom:1px solid #e5e7eb;display:flex;gap:32px">
<div>
<p style="margin:0 0 3px;font-size:10px;text-transform:uppercase;color:#9ca3af;letter-spacing:.06em">Order ID</p>
<p style="margin:0;font-weight:700;color:#111">#{order.order_id}</p>
</div>
<div>
<p style="margin:0 0 3px;font-size:10px;text-transform:uppercase;color:#9ca3af;letter-spacing:.06em">Date</p>
<p style="margin:0;font-weight:600;color:#374151">{order.date}</p>
</div>
<div>
<p style="margin:0 0 3px;font-size:10px;text-transform:uppercase;color:#9ca3af;letter-spacing:.06em">Payment</p>
<p style="margin:0;font-weight:600;color:#374151">{order.payment_method}</p>
</div>
</div>
<!-- Customer -->
<div style="padding:16px 36px;border-bottom:1px solid #e5e7eb">
<p style="margin:0 0 2px;font-size:12px;color:#9ca3af">Billed to</p>
<p style="margin:0;font-weight:600;color:#111">{order.customer_name}</p>
<p style="margin:0;color:#6b7280;font-size:13px">{order.customer_email}</p>
</div>
<!-- Line items -->
<div style="padding:0 36px">
<table style="width:100%;border-collapse:collapse;margin:20px 0">
<thead>
<tr>
<th style="text-align:left;font-size:10px;text-transform:uppercase;color:#9ca3af;padding-bottom:8px;border-bottom:2px solid #e5e7eb">Item</th>
<th style="text-align:center;font-size:10px;text-transform:uppercase;color:#9ca3af;padding-bottom:8px;border-bottom:2px solid #e5e7eb">Qty</th>
<th style="text-align:right;font-size:10px;text-transform:uppercase;color:#9ca3af;padding-bottom:8px;border-bottom:2px solid #e5e7eb">Price</th>
<th style="text-align:right;font-size:10px;text-transform:uppercase;color:#9ca3af;padding-bottom:8px;border-bottom:2px solid #e5e7eb">Total</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
</div>
<!-- Totals -->
<div style="padding:0 36px 28px">
<div style="margin-left:auto;max-width:240px">
<div style="display:flex;justify-content:space-between;padding:5px 0;color:#6b7280;font-size:14px">
<span>Subtotal</span><span>{sym}{subtotal:.2f}</span>
</div>
<div style="display:flex;justify-content:space-between;padding:5px 0;color:#6b7280;font-size:14px">
<span>Tax ({int(order.tax_rate * 100)}%)</span><span>{sym}{tax:.2f}</span>
</div>
<div style="display:flex;justify-content:space-between;padding:12px 0 0;font-size:18px;font-weight:800;color:{order.brand_color};border-top:2px solid #e5e7eb;margin-top:6px">
<span>Total</span><span>{sym}{total:.2f}</span>
</div>
</div>
</div>
<!-- Footer -->
<div style="background:#f9fafb;padding:18px 36px;text-align:center;border-top:1px solid #e5e7eb">
<p style="margin:0 0 3px;font-weight:600;color:#111">Thank you for your order!</p>
<p style="margin:0;color:#9ca3af;font-size:12px">Questions? Reply to your order confirmation email.</p>
</div>
</div>
</body></html>"""
The LangChain tool
Wrap the template and API call in a StructuredTool with a Pydantic schema the LLM can fill in from conversation context:
import os
import requests
from pydantic import BaseModel, Field
from langchain_core.tools import StructuredTool
from typing import List, Optional
class LineItemInput(BaseModel):
description: str
quantity: int
unit_price: float
class GenerateReceiptInput(BaseModel):
order_id: str = Field(description="Unique order identifier, e.g. ORD-2847")
date: str = Field(description="Human-readable order date, e.g. March 2, 2026")
customer_name: str
customer_email: str
line_items: List[LineItemInput]
tax_rate: float = Field(default=0.08, description="Tax rate as a decimal, e.g. 0.08 for 8%")
payment_method: Optional[str] = "Credit card"
brand_name: Optional[str] = "My Store"
brand_color: Optional[str] = "#4f46e5"
def _generate_receipt(
order_id: str, date: str, customer_name: str, customer_email: str,
line_items: list, tax_rate: float = 0.08,
payment_method: str = "Credit card",
brand_name: str = "My Store", brand_color: str = "#4f46e5",
) -> dict:
order = OrderData(
order_id=order_id, date=date,
customer_name=customer_name, customer_email=customer_email,
line_items=[
LineItem(**item) if isinstance(item, dict) else item
for item in line_items
],
tax_rate=tax_rate, payment_method=payment_method,
brand_name=brand_name, brand_color=brand_color,
)
html = build_receipt_html(order)
response = requests.post(
"https://www.agent-gen.com/api/v1/generate/pdf",
headers={"X-API-Key": os.environ["AGENTGEN_API_KEY"], "Content-Type": "application/json"},
json={"html": html, "format": "A4"},
timeout=30,
)
response.raise_for_status()
data = response.json()
return {"receipt_url": data["url"], "tokens_used": data.get("tokens_used", 2)}
generate_receipt_tool = StructuredTool.from_function(
func=_generate_receipt,
name="generate_receipt",
description=(
"Generate a beautiful branded PDF receipt for an order. "
"Returns a permanent CDN download URL. Cost: 2 tokens."
),
args_schema=GenerateReceiptInput,
)
Wiring into an agent
Attach the tool to a Claude agent and let it handle the extraction and generation end-to-end:
from langchain_anthropic import ChatAnthropic
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
llm = ChatAnthropic(model="claude-opus-4-6", temperature=0)
prompt = ChatPromptTemplate.from_messages([
(
"system",
"You are an order management assistant. When given order details, "
"use the generate_receipt tool to create a PDF receipt and share the download link. "
"Confirm the total before generating.",
),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(llm, [generate_receipt_tool], prompt)
executor = AgentExecutor(agent=agent, tools=[generate_receipt_tool], verbose=True)
result = executor.invoke({
"input": (
"Generate a receipt for order #ORD-2847. "
"Customer: Sarah Chen ([email protected]). "
"Items: 2x Wireless Headphones at $89.99, 1x USB-C Cable at $12.99. "
"Payment: Visa ending 4242. Date: March 2, 2026."
)
})
print(result["output"])
# → "Here's the receipt for order #ORD-2847: https://cdn.agent-gen.com/output/..."
Batch generation — all of today's orders
For end-of-day batch processing, skip the agent and call the function directly with asyncio to generate hundreds of receipts concurrently:
import asyncio
import aiohttp
async def generate_receipt_async(session: aiohttp.ClientSession, order: OrderData) -> tuple[str, str]:
html = build_receipt_html(order)
async with session.post(
"https://www.agent-gen.com/api/v1/generate/pdf",
headers={"X-API-Key": os.environ["AGENTGEN_API_KEY"]},
json={"html": html, "format": "A4"},
) as resp:
data = await resp.json()
return order.order_id, data["url"]
async def generate_all_receipts(orders: list[OrderData]) -> dict[str, str]:
async with aiohttp.ClientSession() as session:
results = await asyncio.gather(
*(generate_receipt_async(session, o) for o in orders)
)
return dict(results) # {order_id: pdf_url}
# Generate 50 receipts in parallel
receipt_urls = asyncio.run(generate_all_receipts(todays_orders))
# → {"ORD-2847": "https://cdn...", "ORD-2848": "https://cdn...", ...}
Sending the receipt
The PDF URL is a permanent CDN link valid for 30 days — paste it directly into your transactional email, Slack message, or SMS:
import smtplib
from email.mime.text import MIMEText
def email_receipt(to: str, name: str, order_id: str, pdf_url: str):
msg = MIMEText(
f"Hi {name},
Your receipt for order #{order_id} is ready:
{pdf_url}
Thank you!",
"plain",
)
msg["Subject"] = f"Your receipt for order #{order_id}"
msg["From"] = "[email protected]"
msg["To"] = to
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp:
smtp.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
smtp.send_message(msg)
Cost
Each receipt is one A4 page = 2 tokens. At the Growth tier ($39 for 2,500 tokens) that's 1,250 receipts for $39 — about 3 cents each. Tokens never expire, so unused balance carries over indefinitely.
Get your free API key → and generate your first receipt in minutes.
Ready to start generating?
Create a free account and generate your first PDF or image in minutes.
Get started free