Dave Ebbelaar’s Full-Stack AI Engineering Course, released October 30, 2026, builds a customer-support application across 3 hours, 10 minutes. The focus is integration: Gmail, durable records, typed model outputs, policy, evals, a dashboard, and deployment.

The official handbook supplies the code alongside this full-course outline. Hostinger sponsors the course; that hosting choice is not an architectural requirement. A working classroom deployment does not prove production reliability.

The system in one view

Gmail -> separate poller -> event claim + guards + stored conversation
      -> Haiku classification -> Sonnet draft + knowledge tools
      -> Python policy checks -> reply OR escalation acknowledgement
      -> Gmail labels + ticket/message/event updates

React dashboard <-> FastAPI <-> PostgreSQL on Supabase
Docker Compose: separate API and poller services on an Ubuntu VPS
Remote dashboard access: encrypted SSH tunnel

The distinction to retain: the model proposes; application code controls the next step. That reduces risk without making the model’s judgments infallible.

01. 0:00 — A bounded support problem

  • Harbor and Pine is a fictional Amazon seller of paddleboards and shelving. Product questions, missing parts, and returns create bounded support scenarios.
  • Automate what the knowledge base covers and escalate what it cannot. This is not a general assistant with unrestricted authority.
  • Gmail makes the project accessible; a dummy account limits experimentation risk. A different ticketing connector still needs its own permissions and thread semantics.

02. 0:06:57 — Establish the application before adding AI

  • uv manages Python 3.13 and dependencies. app/ holds reusable behavior; scripts/ holds setup/check utilities. Development dependencies stay separate.
  • .env.example is a template, .env holds secrets, and Git exclusions precede credentials. Pydantic Settings centralizes typed configuration in app/config.py.
  • A FastAPI /health route is checked in a browser. This proves startup, not external integrations. Build small checkpoints before adding model complexity.

03. 0:20:37 — Gmail authorization is real engineering work

  • Google Cloud setup enables Gmail, configures consent/test users, and creates a desktop OAuth client. OAuth delegates access without storing the Gmail password.
  • The course uses gmail.modify. Private credentials.json feeds scripts/get_refresh_token.py; browser consent produces client/token settings outside Git.
  • The testing grant is short-lived. Plan for expiry and revocation before unattended use. Disposable on-screen secrets are demonstration context, not a practice to copy.

04. 0:36:23 — Wrap Gmail, then inspect its actual behavior

  • app/gmail.py wraps messages, threads, labels, and replies. A checking script sends fixed text; the instructor verifies actual Gmail receipt before adding models.
  • Interactive exploration reveals that “list new messages” can return already-seen inbox mail. Archiving changes the result. A helper’s name is not its contract.
  • Inbox membership, unread status, thread identity, and processing state differ. This is not established as unread-only intake; no persisted history cursor is demonstrated.

05. 0:45:27 — Polling is the intake mechanism

  • app/poller.py repeatedly lists candidates and invokes app/pipeline.py. Initial handling only logs and labels; AI-processed does not yet mean a model ran.
  • A 30-second environment value overrides a 60-second source default. API and poller are separate processes, not one loop attached to web-worker startup.
  • Polling avoids a public receiving endpoint. Webhooks—provider-initiated event delivery—and Gmail Pub/Sub are discussed, not built. Repeated intake needs durable identity.

06. 0:53:48 — Model events, tickets, and messages separately

  • Supabase hosts PostgreSQL. Checked-in migrations define events, tickets, messages, and knowledge. app/db.py uses Psycopg and explicit SQL.
  • Events track processing; tickets group Gmail threads; messages store contributions. The database handbook confirms an atomic claim: insert new IDs, retry failed records only.
  • Existing processing records have no reclaim lease in the supplied helper. Unique rows reduce duplicate work but cannot make Gmail send and database completion atomic.

07. 1:10:36 — Stop reply loops before generating answers

  • Guards skip sent/self-originated messages and headers associated with auto-replies, bulk mail, or lists. Deterministic transport checks prevent reply loops.
  • A fixed acknowledgement comes before model-generated answers. Self-mail is now supposed to be skipped, so testing needs another sender account.
  • The instructor verifies both outcomes: external acknowledgement and skipped self-mail, with ledger reasons. Visibility does not imply permission to answer.

08. 1:18:14 — Keep the knowledge base small and inspectable

  • Markdown documents cover brand/tone, escalation, paddleboards, shelving, returns/warranty, and shipping. A seed utility creates section/title/content database records.
  • The agent fetches relevant sections instead of every document. This teaches a useful boundary even though one prompt might work for this tiny collection.
  • This is retrieval-augmented generation, or RAG: retrieved knowledge assists generation. Vector search is not demonstrated. Later live edits require a clear source of truth.

09. 1:23:21 — Use structured outputs as contracts

  • app/ai/schemas.py uses Pydantic for validated types. Pydantic AI applies these contracts at the model boundary.
  • The handbook names Classification, Issue, AgentReply, and Decision: routing, extracted facts, proposed answer, and final result.
  • Reply fields include information sufficiency, action, reasoning, and unsupported promises. They enable branching and tests; valid types do not prove truthful values.

10. 1:28:07 — Let a smaller model triage first

  • Haiku triages the thread into category, language, needs-human, reason, and acknowledgement. It does not send email.
  • Refunds, anger, requests for a person, and specific order status can escalate early. The handbook also covers safety/legal concerns and instruction overrides.
  • Early escalation avoids unnecessary drafting. Python obeys the flag deterministically; deciding that flag remains fallible model judgment. Specialized product agents are only suggested.

11. 1:31:17 — Give the drafter knowledge tools, not fulfillment authority

  • Sonnet uses list_sections() and get_section(section). Live logs show brand/tone, escalation, and paddleboard retrieval before a structured draft.
  • The handbook adds ModelRetry for nonexistent sections and a validator for empty replies or certain internal-system mentions. These are narrow checks, not general safety proofs.
  • These tools retrieve knowledge. They do not issue refunds, ship products, or query a live order system. Knowing a policy is not executing its resolution.

12. 1:35:15 — Encode business decisions outside the prompt

  • app/ai/rules.py maps product/problem facts to proposed steps: accessory shipment, hardware kit, exchange, valve check, or replacement.
  • The handbook’s customer steps are valve-check and ask-details. Missing order information can turn a shipment proposal into a request for details.
  • Fulfillment remains human work. Code makes policy reviewable, but wrong issue extraction can select the wrong branch. Test extracted facts separately from rule mappings.

13. 1:37:21 — Put the final decision boundary in Python

  • decide classifies, escalates attachments/needs-human, then drafts if permitted. It checks action, information sufficiency, issue rules, and unsupported promises.
  • promises_an_action flags text promising refunds, shipments, dates, or callbacks without execution capability. It is model-reported, so false negatives remain possible.
  • The outer pipeline sends text. Escalation sends an acknowledgement and labels needs-human. The handbook skips escalated threads until a colleague answers in Gmail and removes that label.

14. 1:46:06 — Verify the full loop, then shorten the development loop

  • A 90-kilogram rider’s question goes through classification, retrieval, drafting, delivery, and persistence. The instructor checks Gmail and stored messages.
  • One success is an integration check, not broad accuracy evidence. The uncertain spoken board dimension is not necessary to explain the architecture.
  • At 1:49:46, a playground prints decisions without sending mail. It still calls models and Supabase; it complements, not replaces, transport tests.

15. 1:55:31 — Evals expose what a good demo can hide

  • The evals handbook supplies 18 golden emails with expected outputs. Pydantic Evals calls decide, with four concurrent cases shown; Gmail is bypassed, models/database are not.
  • Only SectionMatches and ActionMatches are scored. Actual answer correctness is not scored. A wrong-finish case escalates correctly but selects the wrong category.
  • Connection errors prompt eval-runner retries; classification prompts are clarified. Perfect scores fluctuate across runs. Avoid memorizing cases: small-set 100% is not production accuracy.

16. 2:08:41 — Prepare a server with a restricted entry path

  • An Ubuntu virtual private server (VPS) runs Docker. SSH—Secure Shell—provides encrypted access with a key; the provider firewall allows port 22 from one IP.
  • A server fingerprint check verifies the first connection. Docker is updated and a deploy user added. Key-plus-IP restriction is defense in depth, not demonstrated two-factor authentication.
  • Root-login disabling is discussed but skipped. Empty key passphrases/password reuse are classroom shortcuts. A Docker-capable deploy user remains powerful.

17. 2:23:41 — Package and verify remote execution

  • Docker packages runtime/dependencies; Compose runs separate API and poller services. A read-only, repository-scoped deploy key limits GitHub access. Secrets arrive separately.
  • docker compose up -d --build, docker ps, logs, and a real Gmail reply verify remote execution. Detached containers continue after closing SSH.
  • At 2:37:01, database/pooler access is IP-restricted, not all Supabase APIs. Updates need pull plus rebuild/restart; reboot recovery and rollback are not established.

18. 2:44:54 — Make behavior visible and editable

  • FastAPI gains operator routes and manual polling. A React/TypeScript dashboard uses Vite, Tailwind, and shadcn/ui. Basic authentication exists.
  • Local API, poller, and frontend are coordinated; remote polling is stopped. A sign-off edit is verified in the database. Docker packaging then includes the frontend.
  • The remote UI uses encrypted SSH forwarding and verifies another Gmail reply. The 3:05:19 wrap-up leaves public domain/HTTPS and stronger identity as follow-up work.

Production boundaries to test

The course includes safeguards. These are narrower questions than saying it has “no security” or “no reliability”:

  • Partial failure: Gmail send, reply storage, labels, and event completion are separate effects. Reconcile ambiguous sends and stranded claims; unique IDs do not guarantee exactly-once customer contact.
  • Concurrency: manual POST /api/poll can overlap scheduled polling. Atomic claims help, but thread ordering and recovery need tests. More API workers do not themselves multiply the separate poller.
  • Access/privacy: basic login and restricted SSH exist. Per-user roles, browser-readable credential protection, customer-data retention, and access auditing need decisions. The supplied database role bypasses row-level security.
  • Knowledge/handoff: live policy edits need history, approval, rollback, and source-of-truth rules. A human must own escalated work, not merely see a label.
  • Evidence: a small routing/action set does not prove grounding or general prompt-injection resistance. Add held-out cases, repeated runs, and production error/stall visibility.

Practice — editorial extensions, not executed here

  1. Fault-inject sends: stop a safe-mailbox worker after claim and after send. Success: recover without duplicate contact or create a visible reconciliation task; never silently strand work.
  2. Evaluate unseen scenarios: test grounding, missing facts, language, and unsupported promises, including hostile quoted history. Success: report failure counts and variation without rewriting labels to pass.
  3. Overlap operations: combine manual polls, timed polling, rapid follow-ups, and knowledge edits. Success: no simultaneous duplicate claim, stale contradictory reply, or untraceable policy change.
  4. Rehearse recovery: revoke OAuth, simulate provider outages, change IPs, reboot, restore, and roll back. Success: alerts reach an owner and recovery does not expose secrets or the admin service.

Selective watch for experienced backend developers: 0:41:31 message semantics, 1:03:30 event claiming, the 1:18:14 knowledge-to-evals sequence, and 2:44:54 operator integration. Add the server chapters if SSH/Docker are unfamiliar.

The reusable pattern is a bounded language workflow inside an inspectable application: models interpret and draft; code governs side effects; durable records and human operators make the result supportable.