Skip to content

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

Email 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_FROM and MAIL_FROM secrets, both Alhuda 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.in is verified in Resend (DKIM resend._domainkey, return path on send) and in Mailjet (DKIM mailjet._domainkey). Google Workspace signs its own mail with google._domainkey. DMARC is quarantine.
  • Supabase Auth sends through custom SMTP: smtp.resend.com:465, user resend, a Resend key with sending access to alhudatravels.in only, and a limit of 30 emails an hour. The Site URL is https://alhudatravels.in, and only alhudatravels.in, www.alhudatravels.in and travel.alhuda.co.in may be redirected to. The Redirect URLs list (Authentication → URL Configuration) must hold the wildcard entries https://alhudatravels.in/**, https://www.alhudatravels.in/** and https://travel.alhuda.co.in/**. Only the owner can set them, in the dashboard.

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 scope https://www.googleapis.com/auth/gmail.send in the Workspace admin console, and the Gmail API is enabled in the Google Cloud project alhuda-erp-data. It signs in as the mailbox in MAIL_GMAIL_SENDER and sends with the From in MAIL_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 (default info@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-intake and admin-users still 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 a details row, 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.

  1. Edit the wording in scripts/dev/build-auth-email-templates.ts.
  2. Run node scripts/dev/build-auth-email-templates.ts. It rewrites supabase/templates/*.html and subjects.json. config.toml points the local stack at those files.
  3. Push to production with the Management API (PATCH /v1/projects/<ref>/config/auth, the mailer_subjects_<kind> and mailer_templates_<kind>_content fields). It needs the Supabase CLI's access token. Never paste that token into chat.
  4. 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.