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.exampleis a template,.envholds secrets, and Git exclusions precede credentials. Pydantic Settings centralizes typed configuration inapp/config.py.- A FastAPI
/healthroute 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. Privatecredentials.jsonfeedsscripts/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.pywraps 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.pyrepeatedly lists candidates and invokesapp/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.pyuses 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.pyuses Pydantic for validated types. Pydantic AI applies these contracts at the model boundary.- The handbook names
Classification,Issue,AgentReply, andDecision: 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()andget_section(section). Live logs show brand/tone, escalation, and paddleboard retrieval before a structured draft. - The handbook adds
ModelRetryfor 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.pymaps product/problem facts to proposed steps: accessory shipment, hardware kit, exchange, valve check, or replacement.- The handbook’s customer steps are
valve-checkandask-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
decideclassifies, escalates attachments/needs-human, then drafts if permitted. It checks action, information sufficiency, issue rules, and unsupported promises.promises_an_actionflags 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
SectionMatchesandActionMatchesare 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/pollcan 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
- 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.
- 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.
- 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.
- 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.