Build dynamic email campaigns

Nurture both active and inactive users. Provide links to docs and communities to help unblock them.

  1. onslack member_joined_channel
  2. resendsend welcome email
  3. sleepwait 2 days
  4. posthogcheck if the user deployed a project
  5. slackask if we should invite to call
  6. resendsend follow up email

Build it with your coding agent

Paste into any coding agent with the Terse skills installed. No skills yet? Install them in one command.

/terse-create build https://www.useterse.ai/use-cases/email-sequencing.md

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!