# Contact Form → n8n Webhook with Personalized MDX Response Plan

## Overview
Wire the existing Astro contact form to an n8n webhook using `PUBLIC_N8N_WEBHOOK_URL`, enable n8n to return a personalized MDX/Markdown message, render it on the client, and implement automatic fallback to a standard toast notification when n8n is down or fails.

---

## Implementation Steps

### 1. n8n Webhook + Environment Setup

**Verify n8n Webhook Configuration:**
- In your n8n instance, create or verify a Webhook node configured for `POST` requests
- Use a path like `contact-form`
- Note the complete webhook URL (e.g., `https://your-n8n-instance.com/webhook/contact-form`)

**Define Response Contract:**
The n8n workflow should return JSON in one of these formats:
- **Success:** `{ success: true, format: 'mdx', message: '...markdown/mdx string...' }`
- **Handled Error:** `{ success: false, error: 'Human-friendly error message' }`

**Environment Variable:**
- Confirm `PUBLIC_N8N_WEBHOOK_URL` is set in `.env` with the webhook URL
- Ensure the same variable is configured in your Cloudflare Pages environment settings
- Optional: Update `env.d.ts` to type `import.meta.env.PUBLIC_N8N_WEBHOOK_URL` for TypeScript safety

---

### 2. Wire Astro Contact Form to n8n (with Robust Error Detection)

**File to modify:** `src/pages/contact.astro`

**Form Markup Updates:**
- Add `id="contact-form"` to the form element
- Remove `action="#"` and `method="POST"` attributes (JavaScript will handle submission)
- Preserve all existing classes, labels, and the custom subject dropdown

**Client-Side Submit Handler:**
Add a new script block (or extend the existing one) with:

1. **Form submission interception:**
   - Attach a `submit` event listener that calls `preventDefault()`
   - Extract form data using `FormData` API
   - Build JSON payload including:
     - `name`, `email`, `subject`, `message`
     - Metadata: `timestamp` (ISO string), `source: 'portfolio-website'`

2. **Fetch with timeout wrapper:**
   - Use `fetch(import.meta.env.PUBLIC_N8N_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) })`
   - Wrap with `AbortController` or `Promise.race` for 8-10 second timeout

3. **Failure detection conditions** (any of these triggers fallback):
   - Network error or thrown exception
   - Timeout reached
   - Non-2xx HTTP response
   - 2xx response with `success: false` in JSON

4. **Success path:**
   - Extract the `message` field from response
   - Pass to MDX/Markdown rendering logic (see Step 3)
   - Show brief success state on submit button

5. **Failure path:**
   - Display standard toast notification with error message
   - Keep form data intact (don't reset)
   - Re-enable submit button

**Button UX States:**
- **Waiting:** Disable button, change text to "Transmitting..."
- **Success:** Briefly show "Message Sent!" then re-enable
- **Failure:** Show "Transmission Failed" then revert to original text

---

### 3. Render Personalized MDX/Markdown Response

**Add Markdown Renderer:**
- Install a lightweight markdown library via `pnpm add marked` (or `markdown-it`)
- Import it in the client-side script section

**Response Panel UI:**
- Create a dedicated container near the form submit area (e.g., bordered card)
- Initially hidden (`hidden` class or `display: none`)
- Becomes visible only when successful response is received
- Style with existing design system classes for consistency

**Rendering Logic:**
When response has `success: true` and `format: 'mdx'`:
1. Convert the `message` string to HTML using the markdown library
2. Inject into response panel using `innerHTML`
3. Apply typography classes (`prose` or custom) for proper formatting
4. If markdown conversion throws, treat as failure and show fallback toast

**Accessibility:**
- Add `role="status"` to the response panel
- Ensure proper color contrast
- Test with keyboard navigation and screen readers

**Security:**
- Since content comes from your own n8n instance, it's trusted
- Still avoid allowing script tags in the markdown content
- Keep response panel visually constrained

---

### 4. n8n Workflow Processing & Templating

**In your n8n workflow (after the Webhook node):**

**Template the Personalized Message:**
- Use Set or Function nodes to build a Markdown/MDX string
- Use incoming fields like `{{ $json.name }}`, `{{ $json.subject }}`, `{{ $json.message }}`
- Example structure:
  ```markdown
  # Thanks, {{ name }}!

  I received your message about **{{ subject }}**.

  I'll review it and get back to you within 24-48 hours at {{ email }}.

  In the meantime, feel free to check out [my recent work](/work) or [blog posts](/blog).

  — Nicholai
  ```

**Workflow Branches:**
- **Validation node:** Check for required fields (name, email, message)
  - If missing: Return `{ success: false, error: 'Please fill in all required fields' }`
- **Email notification node:** Send yourself a formatted email with the submission details
- **Optional logging node:** Save to Google Sheets, database, or CRM

**Webhook Response Node:**
- At the end of the workflow, add a "Respond to Webhook" node
- Return JSON matching the contract:
  - Success: `{ success: true, format: 'mdx', message: '...' }`
  - Error: `{ success: false, error: '...' }`
- For unexpected internal errors, either:
  - Let workflow fail (frontend timeout will catch it), or
  - Wrap in try/catch and still return `{ success: false }`

---

### 5. Fallback Toast & Automatic Failure Detection UX

**Toast Notification Implementation:**
- Create a reusable toast function (if not already present)
- Should support both success and error styles
- Position in top-right or bottom-right of viewport
- Auto-dismiss after 5-7 seconds with smooth fade-out

**Error Toast Content:**
```
"We couldn't reach the messaging system. Please try again or email me directly at nicholai@nicholai.work"
```

**Automatic Detection:**
- Trigger error toast for any failure condition from Step 2
- Works even if n8n is completely unreachable (DNS/SSL issues, 500 errors, timeouts)

**User Experience:**
- On failure: **Do not clear the form** (preserves user's work)
- Optional: Add inline text under submit button: "Auto-response unavailable; message will still be delivered via email"
- Ensure toast has `role="alert"` for accessibility

---

### 6. Testing & Validation

**Happy Path Tests:**
- With n8n workflow active and webhook listening:
  1. Submit form with various subject/message combinations
  2. Verify n8n receives correct payload with all fields
  3. Confirm n8n builds expected personalized MDX string
  4. Check that frontend displays rendered response panel with proper formatting
  5. Verify email notification is sent
  6. Test that form resets appropriately

**Failure Path Tests:**
1. **n8n completely down:**
   - Stop n8n instance or point env var to invalid URL
   - Submit form
   - Confirm: Timeout triggers, error toast appears, form data preserved, no response panel shown

2. **n8n returns error:**
   - Modify workflow to return `{ success: false, error: 'Test error' }`
   - Submit form
   - Confirm: Error toast shows n8n's error message, no response panel

3. **Network timeout:**
   - Add artificial delay in n8n workflow (>10 seconds)
   - Confirm: Frontend timeout triggers fallback

4. **Invalid markdown:**
   - Have n8n return malformed markdown that breaks the parser
   - Confirm: Rendering error is caught and fallback toast appears

**Browser & Responsiveness:**
- Test on desktop (Chrome, Firefox, Safari)
- Test on mobile viewport (iOS Safari, Chrome Android)
- Verify response panel and toasts don't break layout
- Check animations and transitions are smooth
- Test with keyboard-only navigation
- Test with screen reader (VoiceOver or NVDA)

**Production Verification:**
- After deploying with env var configured:
  1. Submit real test message from live site
  2. Confirm end-to-end flow works
  3. Check browser console for CORS errors (adjust n8n/proxy if needed)
  4. Verify SSL/HTTPS works correctly
  5. Test from different networks (WiFi, mobile data)

---

### 7. Future-Proofing Options

**Server-Side Proxy (Optional):**
If you want to hide the webhook URL and do MDX→HTML conversion server-side:

1. Create an Astro API route (e.g., `/api/contact.ts`) or Cloudflare Worker
2. Have it:
   - Accept form JSON from browser
   - Add server-side validation/rate limiting
   - Call n8n webhook
   - Convert returned MDX to HTML server-side
   - Return normalized `{ success, html }` to client
3. Frontend code changes minimally (just POST URL changes)

**Benefits:**
- Webhook URL never exposed to client
- Additional security layer
- Server-side rate limiting
- Can add spam protection (honeypot, CAPTCHA)

**Richer MDX Components:**
If you later want actual MDX components (not just markdown):
- Add runtime MDX renderer like `@mdx-js/mdx` on client
- Or render MDX to React components server-side in the proxy route
- Would allow n8n to return interactive components, not just static markdown

---

## Critical Files

- **`src/pages/contact.astro`** - Main file to modify (form markup + client script)
- **`.env`** - Contains `PUBLIC_N8N_WEBHOOK_URL`
- **`env.d.ts`** - Optional TypeScript environment variable typing
- **n8n workflow** - Webhook node + processing nodes + response node

---

## Success Criteria

✅ Form submits to n8n webhook successfully
✅ n8n returns personalized MDX message
✅ Frontend renders markdown as HTML in response panel
✅ Timeout/error conditions trigger fallback toast
✅ Form data preserved on failure
✅ Works on desktop and mobile
✅ Accessible to keyboard and screen reader users
✅ No CORS issues in production
✅ Email notifications sent from n8n