diff --git a/CloudZen.csproj b/CloudZen.csproj
index a710a5d..1a38685 100644
--- a/CloudZen.csproj
+++ b/CloudZen.csproj
@@ -21,6 +21,7 @@
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 45e0454..6973a5b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -22,6 +22,7 @@
+
diff --git a/Program.cs b/Program.cs
index b8f6f24..39ab45c 100644
--- a/Program.cs
+++ b/Program.cs
@@ -74,11 +74,12 @@
// HTTP CLIENT REGISTRATION
// =============================================================================
-// Register HttpClient with base address for API calls
-builder.Services.AddScoped(sp => new HttpClient
-{
- BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
-});
+// NOTE: Each service below gets its OWN HttpClient via AddHttpClient (IHttpClientFactory).
+// Do NOT register a single shared HttpClient instance here - ApiEmailService, ChatbotService,
+// and AppointmentService each set httpClient.Timeout in their constructor, and HttpClient throws
+// InvalidOperationException ("net_http_operation_started") if Timeout is set after any request
+// has been sent on that instance. A shared instance crashes as soon as a second service is
+// constructed after the first has made a request.
// =============================================================================
// SECURITY NOTES FOR BLAZOR WEBASSEMBLY
@@ -101,23 +102,27 @@
builder.Services.AddScoped();
// Register AppointmentService for n8n webhook appointment booking
-builder.Services.AddScoped();
+builder.Services.AddHttpClient(client =>
+ client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
// Register TicketService as the implementation for ITicketService
builder.Services.AddScoped();
// Register ResumeService (uses IOptions for configuration)
-builder.Services.AddScoped();
+builder.Services.AddHttpClient(client =>
+ client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
// Register ApiEmailService as the implementation for IEmailService
// Uses IOptions for configuration
// This sends emails through the Azure Functions API backend (secure for WebAssembly)
-builder.Services.AddScoped();
+builder.Services.AddHttpClient(client =>
+ client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
// Register ChatbotService as the implementation for IChatbotService
// Uses IOptions for configuration
// This sends chat messages through the Azure Functions API backend (API key stays server-side)
-builder.Services.AddScoped();
+builder.Services.AddHttpClient(client =>
+ client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
// Register ProjectService for managing portfolio projects
builder.Services.AddScoped();
diff --git a/docs/02-deployment/AZURE_FUNCTION_DEPLOYMENT.md b/docs/02-deployment/AZURE_FUNCTION_DEPLOYMENT.md
index a605438..f4b86b8 100644
--- a/docs/02-deployment/AZURE_FUNCTION_DEPLOYMENT.md
+++ b/docs/02-deployment/AZURE_FUNCTION_DEPLOYMENT.md
@@ -184,7 +184,7 @@ Add these application settings:
| `ANTHROPIC_API_KEY` | `your-anthropic-api-key` | Anthropic Claude API key (for AI chatbot) |
| `BREVO_SMTP_KEY` | `your-brevo-smtp-key` | Brevo SMTP relay key |
| `BREVO_SMTP_LOGIN` | `your-smtp-login@smtp-brevo.com` | Brevo SMTP login |
-| `N8N_WEBHOOK_URL` | `https://cloudzen-n8n.pikapod.net/webhook/appointments` | n8n appointment booking webhook — Production URL only, workflow must be activated (required — 502 if unset). Booking DB (Neon Postgres) is configured inside n8n itself, not an Azure resource. |
+| `N8N_WEBHOOK_URL` | `https://cloudzen-n8n.pikapod.net/webhook/appointments` | n8n appointment booking webhook — Production URL only, workflow must be activated (required — 502 if unset). Booking DB (Neon Postgres) is configured inside n8n itself, not an Azure resource. See [../08-N8N/appointment-system-v3.md](../08-N8N/appointment-system-v3.md) for workflow setup. |
| `KEY_VAULT_ENDPOINT` | `https://cloudzenvault.vault.azure.net/` | *(Optional)* Azure Key Vault URI for secrets management |
| `EmailSettings:FromEmail` | `cloudzen.inc@gmail.com` | Sender email address |
| `EmailSettings:CcEmail` | `softevolutionsl@gmail.com` | CC email address |
@@ -604,4 +604,4 @@ CloudZen/
---
-*Last updated: June 2025 -- Updated Function App name, URLs, CORS configuration (multi-level), Key Vault integration, Polly rate limiting, environment-specific Blazor config, security headers, file structure, and local development setup.*
+*Last updated: 2026-07-25 -- Added cross-reference to n8n workflow docs (08-N8N/appointment-system-v3.md) for N8N_WEBHOOK_URL.*
diff --git a/docs/02-deployment/DEPLOYMENT_CHECKLIST.md b/docs/02-deployment/DEPLOYMENT_CHECKLIST.md
index ac7e2a6..82fca04 100644
--- a/docs/02-deployment/DEPLOYMENT_CHECKLIST.md
+++ b/docs/02-deployment/DEPLOYMENT_CHECKLIST.md
@@ -1,5 +1,9 @@
# Azure Deployment Quick Reference
+> **See also:** [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md) · [AZURE_FUNCTION_DEPLOYMENT.md](AZURE_FUNCTION_DEPLOYMENT.md)
+>
+> **Last synced: 2026-07-25**
+
## ⚡ Quick Start Checklist
### 🚨 Before You Begin - CRITICAL
@@ -29,21 +33,10 @@
- [ ] Configure CORS (allow your Static Web App domain)
### 3️⃣ Azure Functions Backend (REQUIRED for secure operations)
-```bash
-# Create the backend project:
-dotnet new func -n CloudZen.Api
-cd CloudZen.Api
-dotnet add package Azure.Identity
-dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
-dotnet add package MailKit
-dotnet add package Polly
-```
-- [ ] Create `SendEmailFunction.cs` (see `DEPLOYMENT_GUIDE.md` section 5)
-- [ ] Create `ChatFunction.cs` (AI chatbot proxy to Anthropic Claude)
-- [ ] Deploy to Azure Function App (Consumption plan)
-- [ ] Enable Managed Identity
-- [ ] Link to Static Web App (in Azure Portal: Static Web App > APIs)
-- [ ] Set `ANTHROPIC_API_KEY` in Azure Function App settings
+- [x] Backend created and deployed (`CloudZen.Api`, Consumption plan) — see [AZURE_FUNCTION_DEPLOYMENT.md](AZURE_FUNCTION_DEPLOYMENT.md) for setup
+- [x] `SendEmailFunction.cs` and `ChatFunction.cs` deployed
+- [x] Managed Identity enabled, linked to Static Web App
+- [ ] Verify all env vars set in Function App settings — see [AZURE_FUNCTION_DEPLOYMENT.md](AZURE_FUNCTION_DEPLOYMENT.md#azure-portal-configuration)
### 4️⃣ Azure Key Vault Setup
```bash
diff --git a/docs/02-deployment/DEPLOYMENT_GUIDE.md b/docs/02-deployment/DEPLOYMENT_GUIDE.md
index 6b5da18..8bac82d 100644
--- a/docs/02-deployment/DEPLOYMENT_GUIDE.md
+++ b/docs/02-deployment/DEPLOYMENT_GUIDE.md
@@ -68,27 +68,11 @@ Azure Static Web Apps requires a configuration file for routing, security header
}
```
-### Configure Static Web App Application Settings:
-
-1. Go to your Static Web App in the Azure Portal.
-2. Select **Configuration** under **Settings**.
-3. Add these application settings (these will be available as environment variables):
-
-| Name | Value | Purpose |
-|------|-------|---------|
-| `BREVO_SMTP_KEY` | Your Brevo SMTP relay key | Email service (for Azure Function backend) |
-| `BREVO_SMTP_LOGIN` | Your Brevo SMTP login | Email service (for Azure Function backend) |
-| `BLOB_STORAGE_CONNECTION_STRING` | Your storage connection string | Blob operations (for Azure Function backend) |
-| `ANTHROPIC_API_KEY` | Your Anthropic Claude API key | AI chatbot (ChatFunction) |
-| `N8N_WEBHOOK_URL` | Your n8n production webhook URL | Appointment booking webhook (required — 502 if unset) |
-| `KEY_VAULT_ENDPOINT` | Your Key Vault URI | *(Optional)* Loads secrets from Key Vault via Managed Identity |
-| `ProductionOrigin` | Your Static Web App URL | CORS allowed origin |
-| `AllowedOrigins:0` / `AllowedOrigins:1` | Explicit origin URLs | *(Optional)* Overrides default CORS origin list |
-| `RateLimiting:PermitLimit` | e.g. `10` | Max requests per window |
-| `RateLimiting:WindowSeconds` | e.g. `60` | Rate limit window in seconds |
-| `RateLimiting:QueueLimit` | e.g. `0` | Queue limit for excess requests |
-| `RateLimiting:InactivityTimeoutMinutes` | e.g. `5` | Timeout for inactive limiters |
-| `RateLimiting:EnableCircuitBreaker` | `true`/`false` | Enable Polly circuit breaker pattern |
+### Function App Environment Variables
+
+Runtime config/secrets (`BREVO_SMTP_KEY`, `N8N_WEBHOOK_URL`, `ANTHROPIC_API_KEY`, rate limiting, CORS origins, etc.) are set on the **Azure Function App** — not the Static Web App. These are two separate Azure resources with separate Configuration blades.
+
+**Authoritative table:** see [AZURE_FUNCTION_DEPLOYMENT.md](AZURE_FUNCTION_DEPLOYMENT.md#azure-portal-configuration) for the current, complete list (kept in one place to avoid drift).
**Note:** These environment variables are **only accessible to Azure Functions**, not to your Blazor WebAssembly app directly.