Recipe

Welcome email

Fires the moment a user confirms their email (or completes onboarding). Sets the tone, surfaces the 2-3 actions you want them to take in their first 24 hours, and crucially — primes Gmail to keep future mail from you in the Primary tab.

Trigger point

Fire from your /verifyendpoint AFTER you mark the user's email confirmed in your DB — not from the signup endpoint. Sending before verification means you waste deliverability on never-confirmed users.

Code

// app/api/verify/route.ts
import { confirmUser } from "@/lib/auth";
import { sendTransactional } from "@/lib/sendbolt";

export async function GET(req: Request) {
  const token = new URL(req.url).searchParams.get("t");
  const user = await confirmUser(token);  // throws on invalid
  await db.users.update(user.id, { emailConfirmedAt: new Date() });

  // Fire-and-forget — don't block the redirect on email delivery
  void sendTransactional({
    to: user.email,
    templateID: process.env.MP_TEMPLATE_WELCOME!,
    vars: {
      FirstName: user.firstName,
      DashboardURL: "https://acme.com/dashboard",
      DocsURL: "https://acme.com/docs",
      SupportEmail: "support@acme.com",
    },
  });

  return Response.redirect("https://acme.com/dashboard?welcome=1", 302);
}

Template structure (what works)

The empirically-best welcome email is short:

  1. One personal sentence — “Hi Alice, thanks for joining Acme.”
  2. The single highest-leverage action as a button (NOT three buttons — pick one)
  3. One paragraphon what to expect (e.g. “You'll get a weekly digest. Reply to any email and we'll see it.”)
  4. Plain signature from a human name + your support address

Don't bury a video, a feature tour, or 11 links. The receiver decides in 2 seconds whether to read or archive — give them one path.

Reputation gotchas

The welcome email is also Gmail's first signal about your domain's trustworthiness. Maximise the chance it lands in Primary:

  • From a real-looking addressalice@yourdomain.com beats no-reply@yourdomain.com
  • Single-column plain HTML — no full-width hero images, no 3-column tables
  • One link maximum — multiple links lean toward Promotions
  • No tracking pixel in the welcome email — set tracking_open_disabled: true in the send payload. The pixel is fine for newsletters; for transactional it's a Promotions signal
  • Reply-to set to a real human — even if you forward replies to your helpdesk, having a person's name on the From makes Gmail treat it like 1:1 mail

Pre-send quality check

Run the welcome subject + body through POST /api/v1/send-quality/preview in CI to catch copy-deck regressions before they ship:

curl -X POST "$SENDBOLT_API_URL/api/v1/send-quality/preview" \
  -H "Authorization: Bearer $SENDBOLT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Welcome to Acme",
    "body_text": "Hi Alice...",
    "body_html": "<p>Hi Alice...</p>",
    "from_domain": "acme.com",
    "has_list_unsub": true,
    "recipient_email": "test@gmail.com"
  }'

# Expected: quality >= 8.0 (anything lower fails the lint)

Add this as a unit test that runs against your latest copy. Fail the build if quality < 7. Catches the “marketing rewrote the welcome email and used three exclamation marks in the subject” class of bug.

Don't forget

  • Add the recipient to your welcome-completed segment (for drip-sequence enrollment)
  • Stamp a server-side welcome_sent_at column so you don't double-fire
  • Honour any prior unsubscribe — but for transactional this is rarely an issue (transactional bypasses normal unsub by default)