import os import logging from datetime import datetime import resend from fastapi import FastAPI, HTTPException, Request from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel, EmailStr logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) resend.api_key = os.environ["RESEND_API_KEY"] OWNER_EMAIL = os.environ["OWNER_EMAIL"] FROM_EMAIL = os.environ.get("FROM_EMAIL", "GoodWalk ") REPLY_TO = os.environ.get("REPLY_TO", "aless@goodwalk.co.nz") LOGO_URL = "https://www.goodwalk.co.nz/wp-content/uploads/2022/06/logo-v6.png" app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["POST"], allow_headers=["*"], ) class BookingSubmission(BaseModel): fullName: str email: EmailStr phone: str petName: str location: str message: str = "" services: list[str] = [] referrer: str = "" page: str = "" # ── Helpers ────────────────────────────────────────────────────────────────── def _get_ip(request: Request) -> str: forwarded = request.headers.get("x-forwarded-for") if forwarded: return forwarded.split(",")[0].strip() return request.client.host if request.client else "unknown" def _parse_ua(ua: str) -> str: if not ua: return "Unknown" browsers = [("Edg/", "Edge"), ("OPR/", "Opera"), ("Chrome/", "Chrome"), ("Firefox/", "Firefox"), ("Safari/", "Safari")] systems = [("Windows NT 10", "Windows 10/11"), ("Windows NT 6", "Windows 8"), ("Mac OS X", "macOS"), ("iPhone", "iPhone"), ("iPad", "iPad"), ("Android", "Android"), ("Linux", "Linux")] browser = next((n for p, n in browsers if p in ua), "Unknown browser") system = next((n for p, n in systems if p in ua), "Unknown OS") return f"{browser} on {system}" def _detail_row(label: str, value: str) -> str: if not value: return "" return f""" {label} {value} """ def _meta_row(label: str, value: str) -> str: if not value: return "" return f""" {label} {value} """ # ── Email templates ────────────────────────────────────────────────────────── def _logo_header(badge_html: str = "", subtitle: str = "") -> str: badge = f'
{badge_html}
' if badge_html else "" sub = f"""
{subtitle}
""" if subtitle else "" return f""" GoodWalk {sub} {badge} """ def client_email(data: BookingSubmission) -> str: services_text = ", ".join(data.services) if data.services else "Not specified" message_row = _detail_row("About the dog", data.message) if data.message else "" return f""" We received your enquiry
{_logo_header(subtitle="Auckland’s favourite dog walking service")}

Thanks, {data.fullName.split()[0]}! 🐾

We’ve received your enquiry and Aless will be in touch shortly to arrange a Meet & Greet with you and {data.petName}.

Your enquiry summary
{_detail_row("Your name", data.fullName)} {_detail_row("Email", data.email)} {_detail_row("Phone", data.phone)} {_detail_row("Dog’s name", data.petName)} {_detail_row("Location", data.location)} {_detail_row("Services", services_text)} {message_row}
What happens next?
Aless will review your details and reach out within 1–2 business days to schedule a free Meet & Greet. No commitment required — just a chance for {data.petName} to make a new best friend.

Questions? Just reply to this email or reach us at {REPLY_TO}.

GoodWalk · Auckland, New Zealand
goodwalk.co.nz
""" def owner_email(data: BookingSubmission, ip: str, browser: str) -> str: services_text = ", ".join(data.services) if data.services else "—" now = datetime.now() submitted_at = now.strftime("%d %b %Y at %I:%M %p").lstrip("0") message_block = f"""
About the dog
{data.message}
""" if data.message else "" badge = """
📩  New lead!
Submitted {submitted_at}
""".format(submitted_at=submitted_at) referrer_row = _meta_row("Came from", data.referrer) if data.referrer else _meta_row("Came from", "Direct / bookmark") page_row = _meta_row("Page", data.page) if data.page else "" return f""" New GoodWalk Lead
{_logo_header(badge_html=badge)}
Owner details
Name {data.fullName}
Email {data.email}
Phone {data.phone}
Dog & services
{message_block}
Dog {data.petName}
Location {data.location}
Services {services_text}
Reply to {data.fullName.split()[0]} Call {data.phone}
Session info
{_meta_row("IP address", ip)} {_meta_row("Browser", browser)} {referrer_row} {page_row}
Sent automatically by GoodWalk booking form
""" # ── Route ──────────────────────────────────────────────────────────────────── @app.post("/submit") async def submit_booking(data: BookingSubmission, request: Request): ip = _get_ip(request) browser = _parse_ua(request.headers.get("user-agent", "")) logger.info("Booking from %s (%s, %s) for dog %s", data.email, ip, browser, data.petName) errors = [] try: resend.Emails.send({ "from": FROM_EMAIL, "to": [data.email], "reply_to": REPLY_TO, "subject": f"We received your enquiry, {data.fullName.split()[0]}! 🐾", "html": client_email(data), }) logger.info("Client confirmation sent to %s", data.email) except Exception as exc: logger.error("Failed to send client email: %s", exc) errors.append("client_email") try: resend.Emails.send({ "from": FROM_EMAIL, "to": [OWNER_EMAIL], "reply_to": data.email, "subject": f"New GoodWalk lead — {data.fullName} ({data.petName})", "html": owner_email(data, ip, browser), }) logger.info("Owner notification sent to %s", OWNER_EMAIL) except Exception as exc: logger.error("Failed to send owner email: %s", exc) errors.append("owner_email") if "client_email" in errors and "owner_email" in errors: raise HTTPException(status_code=500, detail="Failed to send emails. Please try again.") return {"ok": True}