Skip to main content

Documentation Index

Fetch the complete documentation index at: https://loops.so/docs/llms.txt

Use this file to discover all available pages before exploring further.

The “active in the last 30 days” segment is a workhorse. It’s the right audience for product updates, warming sender reputation, and testing new content without hitting less-engaged users. This recipe sets up the segment, wires up the activity signal, and shows how to use it.

What you need

  • A lastActive (date) contact property, updated whenever a user takes a meaningful action
  • An isActive30d (boolean) contact property that your app computes and writes (true/false)

Writing the activity signal

Pick one or two actions that represent real usage, not every click. For most SaaS apps, logins and a core action (send, create, publish, message) are enough. Using the Update contact endpoint:
await fetch("https://app.loops.so/api/v1/contacts/update", {
  method: "PUT",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.LOOPS_API_KEY}`,
  },
  body: JSON.stringify({
    email: user.email,
    userId: user.id,
    lastActive: Date.now(),
    isActive30d: true,
  }),
});
The API accepts custom properties at the top level of the body. The JavaScript SDK nests them under properties. After a contact already exists in Loops with a userId, you can send later updates with only userId. You can also sync this property via Segment, PostHog, or RudderStack if you already track these events.
lastActive needs to be a Date-type contact property. Send a millisecond timestamp and Loops will parse it.
Because Loops filters do not support dynamic date expressions like “today minus 30 days,” compute the rolling window in your app and write isActive30d directly. Common patterns:
  • On activity events: set lastActive to now (or a date you save in your database) and isActive30d to true
  • Daily backfill job: once per day, set isActive30d = (lastActive >= now - 30 days) for all relevant contacts
This keeps segment membership accurate without relying on dynamic date math in the Loops UI. Example daily backfill (Node.js + JavaScript SDK):
syncToLoops.ts
import { LoopsClient } from "loops";

type UserActivityRow = {
  email: string;
  userId: string;
  lastActiveAtMs: number | null;
};

const loops = new LoopsClient(process.env.LOOPS_API_KEY!);
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;

async function syncActive30d(users: UserActivityRow[]) {
  const cutoff = Date.now() - THIRTY_DAYS_MS;

  for (const user of users) {
    const isActive30d =
      user.lastActiveAtMs !== null && user.lastActiveAtMs >= cutoff;

    await loops.updateContact({
      email: user.email,
      userId: user.userId,
      properties: {
        // Keep this in sync with your DB source of truth.
        lastActive: user.lastActiveAtMs,
        isActive30d,
      },
    });
  }
}

Build the segment

Go to the Audience page and in the filters add:
  • isActive30d Is true
  • Subscribed Is true
Click Save segment and name it “Active 30-day users”. Because segments update automatically, membership refreshes as isActive30d changes. You never need to rebuild it.

Using the segment

For product update campaigns

Send product updates to this segment instead of your full audience. Open rates will be substantially higher, which protects deliverability. See Product updates for copy guidance.

For reputation warming

Early on, or after migrating domains (guide), this segment is the right audience for campaign #1. See Your first onboarding emails for the full warming flow.

As an audience filter inside a workflow

Inside a workflow, apply the same filter on a send step. For example, a workflow triggered by Event received (such as a weekly feature-highlight event you fire from a cron job) can use this filter so the email only reaches engaged users. See the available workflow triggers.

Variations

  • 7-day active: add isActive7d as a separate boolean your app computes and writes (true/false).
  • Inactive (inverse): use isActive30d Is false for re-engagement. See Churn risk segment.
  • Power users: combine with a count property like sessionCount30d Greater than 10.

Read more

Filters and segments

Contact properties

Product update emails

Sender reputation

Last modified on May 11, 2026