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.SB_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:
- One personal sentence — “Hi Alice, thanks for joining Acme.”
- The single highest-leverage action as a button (NOT three buttons — pick one)
- One paragraphon what to expect (e.g. “You'll get a weekly digest. Reply to any email and we'll see it.”)
- 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 address —
alice@yourdomain.combeatsno-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: truein 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-completedsegment (for drip-sequence enrollment) - Stamp a server-side
welcome_sent_atcolumn so you don't double-fire - Honour any prior unsubscribe — but for transactional this is rarely an issue (transactional bypasses normal unsub by default)