Segflow
Open Source & Self-Hosted

An open-source, full-code alternative to Customer.io

Segflow lets you write marketing automation flows in pure code. You can define user segments with SQL queries, write campaign logic in TypeScript, and create email templates in React + Tailwind.

// Define your user properties
interface UserAttributes {
  email: string;
  name: string;
}

const config: SegflowConfig<UserAttributes> = {
  // Define user segments with SQL
  segments: {
    'inactive-users': {
      evaluator: (db) => db
        .select({ id: schema.users.id })
        .from(schema.users)
        .innerJoin(schema.events, eq(schema.events.userId, schema.users.id))
        .where(eq(schema.events.name, 'login'))
        .groupBy(schema.users.id)
        .having(sql`MAX(schema.events.createdAt) < NOW() - INTERVAL 30 DAY`)
    }
  },

  // Define campaign logic with TypeScript
  campaigns: {
    'winback-campaign': {
      segments: ['inactive-users'],
      behavior: 'dynamic',  // Auto-exits when user becomes active again
      flow: function* (ctx, rt) {
        for (let i = 0; i < 10; i++) {
          yield rt.sendEmail('winback');
          yield rt.wait({ days: 2 ** i }); // Wait 1, 2, then 4 days
        }
      }
    }
  }
};

Key Concepts

Segments = SQL Queries

Define user groups with the full power of SQL:

segments: {
  'big-spenders': {
    evaluator: (db) => db
      .select({ id: schema.users.id })
      .from(schema.users)
      .innerJoin(schema.events, eq(schema.events.userId, schema.users.id))
      .where(eq(schema.events.name, 'purchase'))
      .groupBy(schema.users.id)
      .having(sql`sum(${schema.events.attributes}->'$.amount') > 1000`)
  }
}

Campaigns = Generator Functions

Write complex flows with regular TypeScript:

campaigns: {
  'onboarding': {
    segments: ['new-users'],
    behavior: 'static',
    flow: function* (ctx, rt) {
      yield rt.sendEmail('welcome');
      yield rt.wait({ days: 1 });
      
      if (!ctx.user.profileCompleted) {
        yield rt.sendEmail('complete-profile-reminder');
        yield rt.wait({ days: 3 });
      }
      
      yield rt.sendEmail('feature-highlights');
    }
  }
}

Templates = React Components

Design emails with familiar tools:

templates: {
  'welcome': {
    subject: (user) => `Welcome ${user.name}!`,
    component: ({ user }) => (
      <Html>
        <Head />
        <Tailwind>
          <Body>
            <Preview className="text-blue-600">Welcome aboard!</Preview>
            <Text className="text-slate-600">Thanks for joining us {user.name}!</Text>
            <Button href="https://example.com/get-started">
              Get Started
            </Button>
          </Body>
        </Tailwind>
      </Html>
    )
  }
}

Events & Users = Simple API

Track user activity and trigger emails with a straightforward client:

// Create or update users
await client.createUser('user123', {
  email: '[email protected]',
  name: 'Jane'
});

// Track login events
await client.emit('user123', 'login');

// Track purchase events
await client.emit('user123', 'purchase', {
  orderId: 'ord_123',
  amount: 99.99
});

Transactions = Event-Triggered Emails

Send immediate, event-triggered emails like password resets and order confirmations:

transactions: {
  'password-reset': {
    event: 'reset_password_requested',
    subject: (user) => `Reset Your Password`,
    component: ({ user, event }) => (
      <Html>
        <Head />
        <Tailwind>
          <Body>
            <Preview>Reset your password</Preview>
            <Text className="text-slate-600">Click the link below to reset your password:</Text>
            <Button href={event.resetLink}>
              Reset Password
            </Button>
          </Body>
        </Tailwind>
      </Html>
    )
  }
}

// Trigger the email immediately when needed
await client.emit('user123', 'reset_password_requested', {
  resetLink: 'https://example.com/reset/abc123'
});

Frequently Asked Questions

How often do campaigns run?

Campaigns run in real-time. As soon as a user enters a segment, they start receiving the campaign. For `dynamic` campaigns, users exit immediately when they no longer match the segment criteria.

How do you prevent duplicate sends?

Segflow tracks each user's progress through campaigns in its database. Users can only be at one step in a campaign at a time.

What databases are supported?

Segflow uses MySQL to track its own state. Your application can use any database - you just need to send user events to Segflow via its API.

What email providers are supported?

Currently Postmark and Amazon SES. Adding new providers is straightforward through the open-source codebase.

Because the best marketing automation is the one you can actually program.

Segflow empowers developers to build sophisticated marketing automation workflows tailored to their unique business needs, all while maintaining full control over their code and data.

Get Started