SendOps

Audience

Contacts, lists, and segments — where your email already lives

One contact per person, with typed attributes. Static lists you control. Dynamic segments written in SendQL that stay current on their own. All of it on top of your own SES account — no CSV round-trips, no sync jobs, no contact tax. Keep a million leads and email each one only when it's relevant: storing contacts costs nothing on any plan, including Free.

The problem

SES has contact lists. It doesn't have an audience.

SES gives you a contact list: addresses, topics, opt-outs. That's storage, not an audience model. There are no typed attributes, no segments, no view of what a contact has actually done.

So the real audience data ends up everywhere else. Plan and signup date live in your app database. The "engaged users" list is a spreadsheet someone exported last quarter. And if you bolt on an ESP to fill the gap, you pay per contact to store data you already own — and now consent lives in two places that disagree.

SendOps puts the audience layer next to the sending: one contact per person, on top of your SES account, with your app's data flowing in.

Contacts

One contact per person. Everything attached to it.

Not "a row in a list." A contact is a single record for your whole org — attributes, memberships, consent, and history in one place.

Typed attributes

Attributes live in a registry with real types — strings, numbers, booleans, datetimes, enums. Writes are validated against it. No more guessing whether plan is a string or a number this week.

Import, sync, or push

Get contacts in however they exist today: CSV import, sync from your SES contact list, or push from your app through the API. They all land on the same contact.

The contact 360

Open a contact and see everything attached to it: which lists and segments it belongs to, its message timeline, and whether it currently exists in SES.

Deliverability on the record

Engagement and undeliverable status live on the contact itself. If an address bounces or goes quiet, you see it where you'd look first — not in a separate report.

Lists & Segments

Two primitives. No ambiguity.

A list is membership you assign. A segment is membership you describe. Everything else builds on those two.

Static lists

You add contacts. You remove contacts. Nothing changes until you do. Managed straight from the dashboard — beta testers, VIP customers, the people who asked for early access.

Dynamic segments

One SendQL predicate defines membership. As attributes and events change, contacts enter and exit on their own — continuously re-evaluated, with a force re-evaluation when you want it now. Every transition is recorded, so you can see exactly when a contact joined or left.

Consent is mirrored to SES with stricter-wins semantics. An unsubscribe in SendOps is pushed back to your SES contact list, and nothing coming from SES can quietly re-subscribe someone who opted out.

SendQL

Segments are queries, not click-paths

SendQL is a real query language over your audience. Attributes, message events, and your product's activities are all first-class — so "trial users who clicked this week but never ordered" is one readable predicate, not twelve dropdowns.

  • Definitions live where you want. Keep them as files in a connected git repo — PR-reviewed, synced on merge — or manage them in the app. Git holds the definition; SendOps holds the membership.

  • Plain text, on purpose. A segment is a few lines anyone can read in review — and because it's just text, coding agents can author and maintain segments too.

Trial users showing intent but not converting:

attr.plan = "trial"
  and now - attr.signup_date between 3d and 14d
  and count(click within 7d) >= 1
  and not exists(order)

Engaged subscribers who are safe to send to:

subscribed to "newsletter"
  and not suppressed
  and not exists(complaint within 180d)

Activities

Your product's events are audience signals

Email events only tell half the story. What a contact does in your product — signs up, upgrades, uses a feature, places an order — is the half that matters for who should hear from you next.

Send those events to SendOps through the Activity API and they attach to the contact. From there they're segmentable in SendQL — sum(amount of order within 90d) > 500 — and they can trigger workflows, so an upgrade or a quiet week starts the right email on its own.

One stream, two uses. The same activity feeds segment membership and workflow triggers — you send it once.

Next to the email history. Activities land on the same contact record as sends, opens, and clicks — one timeline, not two systems.

An audience layer the whole team can use

The same contacts, lists, and segments — reached the way each person actually works.

For Marketers

Build audiences without filing an engineering ticket. Segments update themselves as attributes and events change — no exported CSV going stale in a shared drive.

For Developers

Audiences as code. Segment definitions are plain text, so they live in your repo, go through PR review, and sync automatically. Contacts and attributes flow in through the API.

For Operators

Consent and suppression in one place. Unsubscribes and opt-outs are mirrored to SES with stricter-wins semantics — a suppressed contact stays suppressed, everywhere.

Your audience, in your platform

Contacts, lists, and segments on top of your own SES account. Not rented from an ESP.

Free plan available. No contact limits. Connects to your SES account in two minutes.