Prolo — Ingestion AWS Architecture (Deliverable F1)

Gmail → extracted quote in RDS (stages 1–4). Private posture: the whole pipeline runs inside a VPC with no public ingress — the only internet traffic is outbound to the Gmail API, via NAT. Notifications use a scheduled poll, not a push webhook. Yellow = AI.

flowchart TB
    subgraph GC["☁️ Google Cloud — external"]
        GM["Gmail · N inboxes
history.list + messages.get"] end subgraph VPC["🔒 AWS VPC — private · no public ingress"] direction TB subgraph PUBSN["Public subnet · egress-only"] NAT["NAT Gateway"] end subgraph PRIV["Private subnets · multi-AZ · no public IPs"] N1["λ Lambda · 1 Notification
scheduled poll · list since watermark"] F2["λ Lambda · 2 Fetch
pull body + attachments"] C3["λ Lambda · 3 Classify"] X4["Fargate · 4 Extract
PyMuPDF + Tesseract"] RDS[("RDS Postgres
private · SG-scoped")] end subgraph VPE["AWS services · via VPC Endpoints (PrivateLink) — no internet"] SCH["EventBridge Scheduler"] Q2(["SQS · to-fetch"]) Q3(["SQS · to-classify"]) Q4(["SQS · to-extract"]) HRQ(["SQS · human review"]) DLQ(["SQS · DLQ"]) TRK[("DynamoDB
watermark + status")] S3[("S3
raw email + attachments")] BR["Bedrock (Claude)
classify-fallback + extract"] SEC["Secrets Manager"] CW["CloudWatch
metrics · logs · alarms"] SNS["SNS · alarm topic"] end end subgraph OC["🔔 On-call — external"] PD["PagerDuty"] SLACK["Slack / ticket"] end SCH ==>|"every ~1–2 min"| N1 N1 -.->|"history.list · egress via NAT"| NAT -.-> GM N1 <-->|advance watermark| TRK N1 -->|new messageIds| Q2 --> F2 F2 -.->|"messages.get + attachments · egress via NAT"| NAT -.-> GM F2 <-->|dedupe| TRK F2 -->|store raw| S3 F2 -->|RawMessage refs| Q3 --> C3 C3 -.ambiguous.-> BR C3 -->|mark classified/discarded| TRK C3 -->|QuoteCandidate| Q4 --> X4 S3 -->|attachment bytes| X4 X4 <-->|structured extract| BR X4 -->|ExtractedQuote P1| RDS X4 -.low confidence.-> HRQ Q4 -.poison.-> DLQ SEC -.creds.-> N1 & F2 & X4 N1 & F2 & C3 & X4 -.metrics/logs.-> CW DLQ -.depth alarm.-> CW CW -->|alarms| SNS SNS ==>|"critical → page"| PD SNS -.->|warning| SLACK RDS -.->|handoff → matching| NEXT["RFQ matching (out of scope)"] classDef ai fill:#fde047,stroke:#ca8a04,color:#000 class BR ai %% subgraph fills — distinct colours per zone style GC fill:#f2f0ff,stroke:#8250df,stroke-width:1.5px,color:#1f2328 style VPC fill:#eef4ff,stroke:#2f6fd6,stroke-width:2px,color:#1f2328 style PUBSN fill:#e6fbf0,stroke:#1a7f37,stroke-width:1.5px,color:#1f2328 style PRIV fill:#e2ecff,stroke:#3b7ddd,stroke-width:1.5px,color:#1f2328 style VPE fill:#fff4e6,stroke:#e8992e,stroke-width:2px,color:#1f2328 style OC fill:#fdeef0,stroke:#d1242f,stroke-width:1.5px,color:#1f2328
AWS VPC (private compute) AWS services · VPC Endpoints On-call · PagerDuty / Slack Yellow = AI (Bedrock / Claude) Solid = in-VPC data flow · Dashed = egress / async / observability