---
title: Build dynamic email campaigns
description: When someone sign up for you product, send a welcome email, wait 2 days, check activity then send personalized follow up
slug: email-sequencing
group: gtm
trigger: slack member_joined_channel
steps:
  - "resend: send welcome email"
  - "sleep: wait 2 days"
  - "posthog: check if the user deployed a project"
  - "slack: ask if we should invite to call"
  - "resend: send follow up email"
integrations:
  - slack
  - posthog
  - resend
outcome: Nurture both active and inactive users. Provide links to docs and communities to help unblock them.
publishedAt: "2026-07-14"
updatedAt: "2026-07-14"
---

Basic email sequencing is table-stakes these days. Before Terse, you would need to use something like Costumer.io or Iterable to do this.

For engineers, that is painful since it takes us out of our flow and they push you to use their analytics system as well.

Terse gives you a code-first option where you can leverage your current stack to build this out right in your coding agent or IDE.

## How it works

1. **Trigger** — WorkOs new user event
2. Send welcome email with resend
3. Wait 2 days
4. Check Posthog Activity
   5 Send slack message with User + engagement level and prompt which email to send
5. Send the follow up email

## Requirements

- A Terse project (`terse init`) with the **Resend**, **Posthog**, **Slack** and **WorkOs**
  integrations connected (`terse integrate connect resend`, then `posthog` etc..).
- Your email templates ready to go in Resend (terse generate will bring them into the workspace for you!)
- Ideally some existing users and analytics so the agent can probe for test data.

## The job

```typescript
import { createJob, slack, sleep, waitForInput } from "terse-sdk";
import {
  PosthogProject,
  ResendTemplates,
  SlackChannel,
  Triggers,
  toolbox,
} from "../terse.generated";

createJob({
  name: "User signup sequence",
  triggers: [Triggers.workOS.onUserCreated()],
  durable: true,
  filter: async (event) => !event.user.email.endsWith("@useterse.ai"),
  onTrigger: async (event) => {
    const { id, email, firstName } = event.user;

    await toolbox.resend.sendTemplate({
      templateId: ResendTemplates.TerseWelcome.templateId,
      to: [email],
      variables: firstName ? { USER_NAME: firstName } : {},
      idempotencyKey: `signup-welcome/${id}`,
    });

    await sleep("2d");

    const deployed = await hasDeployedProject(id, event.createdAt);
    if (!deployed) {
      await toolbox.resend.sendTemplate({
        templateId: ResendTemplates.TerseNudge.templateId,
        to: [email],
        variables: {},
        idempotencyKey: `signup-nudge/${id}`,
      });
      return;
    }

    const decision = await waitForInput({
      via: slack({ channel: SlackChannel.NewSignups.channelId }),
      prompt: `${email} deployed a project in their first two days. Invite them to a call, or just ask for feedback?`,
      options: [
        { id: "call", label: "Feedback email with call invite" },
        { id: "basic", label: "Basic feedback email" },
      ],
    });

    switch (decision.choice) {
      case "call":
        await toolbox.resend.sendTemplate({
          templateId: ResendTemplates.TerseFeedback.templateId,
          to: [email],
          variables: firstName ? { USER_NAME: firstName } : {},
          idempotencyKey: `signup-feedback/${id}`,
        });
        return;
      case "basic":
        await toolbox.resend.sendTemplate({
          templateId: ResendTemplates.TerseFeedbackBasic.templateId,
          to: [email],
          variables: firstName ? { USER_NAME: firstName } : {},
          idempotencyKey: `signup-feedback/${id}`,
        });
        return;
      default:
        throw new Error(`Unexpected feedback choice: ${decision.choice}`);
    }
  },
});

async function hasDeployedProject(
  userId: string,
  signedUpAt: string,
): Promise<boolean> {
  const result = await toolbox.posthog.searchEvents({
    projectId: PosthogProject.Terse.projectId,
    eventName: "project_deployed",
    distinctId: userId,
    dateFrom: signedUpAt.replace("T", " ").slice(0, 19),
    limit: 1,
  });
  return result.totalEvents > 0;
}
```

## Make it yours

- Choose which Posthog event "Engagement" means to you@
- Add another email follow up after 7 days.
- Add personalized messages (via llm, or prompt in slack for text)

This is a very powerful way to do sequencing. Your imagination is the limit here!
