Email — Resend + template matrix (draft)
Job: one send pipeline in Middleware. Frontend never holds the Resend key. Core does not send mail.
v1 stub still says emails are not live. Sibling routes.json and the explorer already have the folder. Proposed winner after promotion:
Middleware Backend · proposed winner · live on explorer
POST https://middleware-dev.bookdirect.live/api/v1/bookdirect/emails/send
Authorization: Bearer {access_token}
Content-Type: application/json{
"template": "booking_confirmed_guest",
"booking_uuid": "{booking_uuid}"
}JWT must be the booking guest or the property owner. Middleware loads Core booking + property, renders HTML, sends via Resend (SMTP fallback), writes email_event_{template} on the booking.
Payments / refunds call the same service in-process — do not HTTP-loop.
Do not publish the 146 KB React Email source dump. Port templates in the Middleware templates/ package. This page is keys, recipients, and CTAs.
ENV (names only)
| Name | Layer | Purpose |
|---|---|---|
RESEND_API_KEY | Middleware | Send. Alias SMTP_API_KEY during cutover |
RESEND_FROM | Middleware | Default From. Alias SMTP_FROM |
SMTP_ENDPOINT | Middleware | Default https://api.resend.com/emails if still named SMTP |
SITE_URL | Middleware | CTA base (https://bookdirect.ng) |
EMAIL_RENDER_SECRET | Middleware + admin preview | Preview render only |
Guest mail stays BookDirect-branded (reservation@send.bookdirect.ng). Partner signup CTAs may point at MelonQ. Decide @melonq.com From later — not for guest booking mail.
Live template keys (implement these first)
Middleware EMAIL_TEMPLATES today:
| Key | To | When |
|---|---|---|
booking_pending_guest | Guest | Create / modify (guest HTML) |
booking_awaiting_response_guest | Guest | Waiting on hotel |
booking_awaiting_response_owner | Owner | New booking — hotel first |
booking_confirmed_guest | Guest | Accept / confirm |
booking_confirmed_owner | Owner | Confirmed |
booking_rejected_guest | Guest | Reject |
booking_cancelled_guest | Guest | Cancel |
booking_cancelled_owner | Owner | Cancel |
Product fact to keep: a new stay emails the hotel first. Guest create HTML is mainly modifications.
Booking ref in the body is Core booking.meta_key (see booking ID).
Legacy matrix → live keys (focus)
Do not rename live keys. Map Convex / hub triggerIds when you port.
| Legacy / hub id | Live key(s) | Port now? |
|---|---|---|
new_booking_pending / booking_email_html | booking_awaiting_response_owner (+ guest pending on modify) | Yes |
accepted_booking / booking_confirmed | booking_confirmed_guest + _owner | Yes |
rejected_booking | booking_rejected_guest | Yes |
cancelled_booking | booking_cancelled_* | Yes |
booking_payment / refund / payout | not in EMAIL_TEMPLATES yet | After payments wire |
user_signup / password_reset | Core auth mail or later keys | Auth stays Core until a guest-mail trigger exists |
new_booking_demandpro_flexi | — | Only if Flexi plans survive MelonQ |
| Unsigned marketing 14–23 | — | Product gate; not lodging v1 |
| Recon / invoice / no-show | — | Finance workstream |
Full numbered file list stays in the legacy repo (lib/emails/templates/0x-*.tsx). Do not copy those files into DevDocs.
CTAs
| Audience | Button | URL |
|---|---|---|
| Guest | View / manage | {SITE_URL}/manage-booking?ref={reference} |
| Guest | Confirmation | {SITE_URL}/booking/confirmation?ref= |
| Guest | Cancel / modify | /cancel-booking?ref= · /modify-booking?ref= |
| Hotel | Accept / Reject | Middleware token confirm — not a Next /api winner |
Proposed token URL (after you add it):
POST https://middleware-dev.bookdirect.live/api/v1/bookdirect/bookings/action
or keep GET .../emails/action?token= on Middleware. Flexi “sign up” hrefs go to onboarding.bookdirect.melonq.com / melonq.com/signin. Token confirm stays on the booking host.
Idempotent send key: (template, booking_uuid, eventVersion).
Defaults (copy rules)
- Hotel To → CC
reservation@unless suppressed - Guest To → BCC
reservation@unless suppressed - Payment / refund → CC
finance@; no reservation BCC - Marketing / internal → suppress auto reservation copy
Communication hub (MelonQ admin) can override enable / Reply-To / CC later. Do not rename live template keys without a migration.
IDOR
- Public browse key must not
POST .../emails/send - Bearer: guest of that booking or property
owned_by - No card data in logs. Store Resend
id+email_event_*only
Called by Middleware, not by frontend
Resend POST https://api.resend.com/emails
Core PUT .../meta-data/entity/booking/{uuid} for email_event_*
Promote into v1 Middleware when the activation matrix row leaves “to implement.”