Send a welcome email after signup with Loops
Add a signup event and a Loops Workflow for one welcome email. Map current marketing permission, configure the message, and test retries and opt-outs before release.
Who this is for
Add a welcome email to an existing Node.js or TypeScript signup flow. Your app records permission and emits the signup event. The Loops Workflow sends the message according to the contact’s subscription and suppression state.
Give this to your agent
Ask your coding agent to connect the existing signup flow to a welcome email, including the sending category and tests:
Add an automated welcome email to this app so new signups receive one onboarding message. Choose the email category and sending path that fit this message, keep credentials server-side, trigger it only after signup is committed, respect consent and unsubscribe state, add a safe test path, and document any dashboard configuration I need to finish.Install the SDK
npm install loopsThe official package is loops. Version 7 supports TypeScript, ESM, and CommonJS and requires Node 18 or later. Create one server-only client. The API key grants account access and must never reach browser code.
// lib/loops.ts (server only)
import { LoopsClient } from "loops";
if (!process.env.LOOPS_API_KEY) {
throw new Error("LOOPS_API_KEY is not set");
}
export const loops = new LoopsClient(process.env.LOOPS_API_KEY);Set the server-only API key
Generate a key under Settings -> API in Loops. Store it in your deployment secrets and local .env file. Never commit it, log it, or expose it through NEXT_PUBLIC_, VITE_, browser code, or a mobile client.
# .env (server only)
LOOPS_API_KEY=Send one signup event
Call this after signup is committed and the contact already exists in Loops with reconciled permission state. Sync consent through the app’s preference-update path before emitting the event. The event can create a missing contact, so do not use it as an unchecked substitute for that setup. Keep the same key and payload for every retry of this signup event.
import { loops } from "./lib/loops";
type NewUser = {
id: string;
email: string;
firstName?: string | null;
};
export async function onUserCreated(user: NewUser) {
return loops.sendEvent({
email: user.email,
userId: user.id,
eventName: "user_signed_up",
contactProperties: {
...(user.firstName == null ? {} : { firstName: user.firstName }),
},
eventProperties: { source: "product_signup" },
headers: { "Idempotency-Key": "signup:" + user.id },
});
}The event deliberately omits subscribed so a retry does not restore an old consent value. Keep subscription and list preferences current through a separate, ordered sync that preserves newer opt-outs. The event name is case-sensitive. contactProperties persist on the contact. eventProperties supply data to triggered emails. Built-in double opt-in currently gates Form endpoints. API-created contacts need your app’s own permission checks.
Configure the Workflow in Loops
1. Workflows → New → Start from scratch.
2. Trigger → Event received → user_signed_up → One time.
3. Add a Send email node immediately after the trigger.
4. Write the welcome email and add fallbacks for optional properties.
5. Select a mailing list if signup includes consent for a named category.
6. Send a preview, then click Start.The Event received trigger must match user_signed_up exactly. Choose One time so the first signup enters once, add the email immediately after the trigger, send a preview, then Start the Workflow. Dashboard configuration remains a human-reviewed step.
Test the whole path
First use an address at example.com or test.com to check event processing without delivery. Then test with an opted-in inbox and confirm the send record. Exercise retries with the same key and payload, a contact with subscribed: false, a newer unsubscribe, a pending confirmation, and a suppressed recipient. Run the project’s TypeScript check.
Common failure modes
Install the loops package.
Keep LOOPS_API_KEY server-side.
Match the case-sensitive event name user_signed_up.
Start the workflow before sending the qualifying event.
Check subscription permission: unsubscribed contacts will not receive workflow email.
Verify the sending domain and check for recipient suppressions.
Reuse the same Idempotency-Key when retrying an event.
Consent and email category
A welcome or onboarding email is marketing in Loops, even when signup triggers it. Send it through a Workflow so unsubscribe and suppression state are honored. Reserve transactional email for action-completing messages such as account verification, password resets, receipts, and security alerts.
Related: welcome email examples, onboarding email examples, and send email from AI agents.
Common questions
Is a welcome email transactional or marketing?
Why use a signup event instead of Contact added?
Why didn’t the welcome email send?