Outgoing email
Every email the system sends comes from Alhuda Travels <info@alhudatravels.in> and uses one design: the burgundy header with the Alhuda logo, a gold line, a details table, one button, and our address, phone and email in the footer. Replies land in the info@ Google Group.
Who sends what
| Sent by | Through | |
|---|---|---|
| Booking created / status changes, quotations, payment receipts, refunds, wallet top-ups, B2B bookings, visa updates, booking reminders, messages, codes. Every booking e-mail carries the booking block (Booking e-mails and reminders) | mailer edge function |
Google Workspace (Gmail API), then Resend, then Mailjet — COMM-040 |
| New website enquiry (to staff) | lead-intake |
Resend |
| New visa application (to staff) | visa-intake |
Resend |
| Staff password reset by an administrator | admin-users |
Resend |
| Staff password reset asked for on Forgot password? (the link goes to the @alhudatravels.in mailbox, ACC-072) | auth-login |
Resend |
| Password reset, invitation, sign-in link, email change, signup confirmation, verification code | Supabase Auth | Resend SMTP |
Staff notifications (enquiries, visa applications) go to info@alhudatravels.in. The inbound email capture (Email to info@ becomes a lead) skips mail from our own domains, so these never turn into leads themselves.
The sender and the domain
- Sender: the
RESEND_FROMandMAIL_FROMsecrets, bothAlhuda Travels <info@alhudatravels.in>. These beat anything saved on the Integration settings screen. The sender is info@, not no-reply@, because the code sets no separate Reply-To, and a customer's reply has to reach someone. - Domain:
alhudatravels.inis verified in Resend (DKIMresend._domainkey, return path onsend) and in Mailjet (DKIMmailjet._domainkey). Google Workspace signs its own mail withgoogle._domainkey. DMARC isquarantine. - Supabase Auth sends through custom SMTP:
smtp.resend.com:465, userresend, a Resend key with sending access toalhudatravels.inonly, and a limit of 30 emails an hour. The Site URL ishttps://alhudatravels.in, and onlyalhudatravels.in,www.alhudatravels.inandtravel.alhuda.co.inmay be redirected to. The Redirect URLs list (Authentication → URL Configuration) must hold the wildcard entrieshttps://alhudatravels.in/**,https://www.alhudatravels.in/**andhttps://travel.alhuda.co.in/**. Only the owner can set them, in the dashboard.
Password-reset links
Every reset link, staff or portal, carries a redirect back to
/reset-password?mode=recovery&returnTo=… on one of the three domains above.
auth-login builds it: the three domains are always allowed, and the function
secrets SITE_URL (default https://alhudatravels.in) and AUTH_ALLOWED_ORIGINS
(optional, comma-separated) only add to them. A staff link (a company address) is
made by auth-login and sent through Resend; a customer's or partner's goes through
Supabase Auth mail. Both depend on the Redirect URLs list above: a redirect missing
from it makes Supabase send the person to the Site URL root, and the app then moves
them to the new-password form. Details and checks:
Security → auth-login and
ACC-072.
Google Workspace sending (COMM-040)
The mailer and the Send a test email button on Admin → Integration settings send through Google Workspace first, when it is set up:
- How: the Drive service account (
GOOGLE_DRIVE_CLIENT_EMAIL,GOOGLE_DRIVE_PRIVATE_KEY) has domain-wide delegation for the scopehttps://www.googleapis.com/auth/gmail.sendin the Workspace admin console, and the Gmail API is enabled in the Google Cloud projectalhuda-erp-data. It signs in as the mailbox inMAIL_GMAIL_SENDERand sends with the From inMAIL_GMAIL_FROM. - Settings:
MAIL_GMAIL_SENDER=admin@alhudatravels.in;MAIL_GMAIL_FROM=Alhuda Travels <noreply@alhudatravels.in>, a Send mail as alias of admin@ — Gmail refuses a From that is neither the mailbox nor one of its aliases.MAIL_REPLY_TO(defaultinfo@alhudatravels.in) is added to every Gmail message: the From is a no-reply address, so a customer's reply has to reach someone. - Off: without
MAIL_GMAIL_SENDER(or the service account), Gmail is skipped and mail goes through Resend, then Mailjet, as before. If Gmail refuses a message, the same message goes through Resend. - Sent folder: every message sent this way appears in admin@'s Sent folder.
- Not changed: Supabase Auth mail (password reset, invitations) still goes
through Resend SMTP;
lead-intake,visa-intakeandadmin-usersstill send through Resend. - Check: Admin → Integration settings → Send a test email sends through the same chain and says which provider accepted it, or why each refused.
The design
One file: supabase/functions/_shared/emailLayout.ts. It is plain TypeScript,
so both the edge functions and Node can import it.
- Anything a person or the database supplies goes through
escapeHtml, or is passed as adetailsrow, which is escaped for you. A name typed as<script>arrives as text. - A button URL is used as given, so Supabase's
{{ .ConfirmationURL }}placeholder survives into the Auth templates. audience: "staff"adds a line saying the email is an internal notification.
Tested in src/services/emailLayout.test.ts.
Changing the Supabase login emails
The six Auth templates are generated from the same layout. Don't edit
supabase/templates/*.html by hand.
- Edit the wording in
scripts/dev/build-auth-email-templates.ts. - Run
node scripts/dev/build-auth-email-templates.ts. It rewritessupabase/templates/*.htmlandsubjects.json.config.tomlpoints the local stack at those files. - Push to production with the Management API (
PATCH /v1/projects/<ref>/config/auth, themailer_subjects_<kind>andmailer_templates_<kind>_contentfields). It needs the Supabase CLI's access token. Never paste that token into chat. - Send yourself a password reset from alhudatravels.in/customer/auth and check it.
Checking delivery
- Resend → Logs shows every email the ERP and Supabase sent, and whether the receiving server accepted it.
- In Gmail, ⋮ → Show original shows SPF, DKIM and DMARC. All three should say PASS.
Not done
- No separate Reply-To. That's why the sender is info@. A
no-reply@sender would bounce customers' replies. - The WhatsApp number isn't in the email footer yet. It goes in once the Meta app is Live and messages reach the ERP.