Part 6 of 7 · Expense approver series ~3 min read

What the expense approver costs

The approver is one of the cheaper systems in this whole series. The expensive-sounding part — reading a photo of a receipt — is a few tenths of a cent per claim. The policy check that decides each claim’s fate is plain Python and costs almost nothing. Bedrock fires once per claim to sort the category and once a month for a board summary. At typical SMB volume, the bill is a couple of dollars a month, fixed cost essentially zero.

Key takeaways

  • Around $2.40/month at typical SMB volume (around 200 claims a month).
  • Fixed AWS cost is essentially zero. No always-on compute, no NAT Gateway, no API Gateway.
  • The policy check costs pennies — no model calls on the decision.
  • Textract reading each receipt is the dominant cost; the category sort is a small Bedrock sliver.
  • At 1,000 claims a month the bill is around $11. At 1,800 it’s around $19.

Cost at three volumes

Monthly cost at three claim volumes, broken out by component A stacked-bar chart showing monthly cost in US dollars at three claim volumes. The leftmost bar represents 200 claims a month and shows a total around $2.40, dominated by the Textract slice (reading each receipt) with a smaller Bedrock slice for the category sort, a tiny fixed slice, and an everything-else slice for Lambda, DynamoDB, S3, EventBridge, SES, and CloudWatch. The middle bar represents 1,000 claims a month and shows a total around $11, with the same shape — Textract and Bedrock grow linearly with claim count because each receipt is read once. The rightmost bar represents 1,800 claims a month and shows a total around $19, with Textract still the largest slice; the fixed slice for AWS Budgets and Secrets Manager stays flat because it does not depend on volume. Below the chart is a legend explaining the four sections of each bar: Textract (reads every receipt), Bedrock (category sort and monthly summary), AWS Budgets and Secrets Manager (small fixed amounts), and an everything-else bucket for Lambda runtime, DynamoDB on-demand, S3, EventBridge, SES, and CloudWatch. A note at the bottom: Textract is the dominant cost, and even that is fractions of a cent per receipt read. $0 $5 $10 $15 $20 200 claims ~$2.40 1,000 claims ~$11 1,800 claims ~$19 Textract (reads every receipt) Bedrock (category sort + monthly summary) AWS Budgets + Secrets Manager (fixed) Everything else (Lambda, DDB, S3, EventBridge, SES, CloudWatch) Textract is the dominant cost — and even that is fractions of a cent per receipt read.
Fig 6. Monthly cost at three claim volumes. Textract reading each receipt is the largest slice; the category sort is a small Bedrock sliver. The policy check itself is plain Python and barely registers.

Where the dollars actually go

Textract (the bulk). Each claim has one receipt, and Textract reads it once. Receipt reading runs a few tenths of a cent per page, and most receipts are one page. At 200 claims that’s around a dollar; at 1,800 it’s a handful of dollars. This is the single largest line, and it’s still small — reading a receipt by machine is cheap, and it only happens once per claim.

Bedrock (the category sort). One small Haiku 4.5 call per claim reads the receipt text and picks a category. A few hundred input tokens and a handful of output tokens, so a fraction of a cent each. The monthly summary is one larger call: a board-ready paragraph on the month’s spend by category. Bedrock lands at cents to a couple of dollars across these volumes.

Lambda runtime. The intake Lambda, the checker, the routing Lambda, and the decision handler each run for a fraction of a second per claim. At any of these volumes the Lambda total is well under a dollar.

DynamoDB on-demand. Two small tables: ea-claims and ea-audit. A few reads and writes per claim. Pennies a month at any of these volumes.

S3 + storage. The receipt images plus the raw MIME from any forwarded receipts. A few megabytes a month at SMB volume. Effectively free, and lifecycle rules push old receipts to cheaper storage.

SES. Inbound for the forwarding lane and outbound for email-fallback approvals and claimant notices: $0.10 per thousand messages either way. A few cents a month at this scale.

EventBridge. Routing claim events plus the deferred one-offs from the quiet-hours gate. A few events per claim. Pennies.

What doesn’t cost money

  • API Gateway. Replaced by Lambda Function URLs for the submit form and the approve buttons.
  • NAT Gateway. Nothing is in a VPC. No NAT, no $32/month minimum.
  • Always-on compute. No EC2, no Fargate. Every Lambda sleeps until a claim or a decision wakes it.
  • A Knowledge Base. The policy is short structured rules, not free text — deterministic lookup beats vector search here. No embeddings, no Knowledge Base, no S3 Vectors needed.
  • A model on the policy check. The decision is plain Python comparing an amount to a limit. Bedrock fires only on the category sort and the monthly summary.

How the cost scales

Textract and Bedrock grow linearly with claim count, because each claim has one receipt to read and one category to sort. Lambda and DynamoDB grow linearly too but stay tiny. So the bill at 5,000 claims a month is around $50; at 10,000 it’s around $100. Past those volumes you’re a bigger business than this design targets, and you’d look at batching Textract or caching common-vendor categories — but those are optimizations, not redesigns.

Set an AWS Budgets alarm at $25/month so anything unusual pages you before the bill matters. The approver’s normal-volume bill stays well under that ceiling.

Last post in the series: the engineering reference. Same system, drawn for engineers — service names, Lambda inventory, IAM scopes, DynamoDB schemas, the Textract flow, and EventBridge config.

All posts