Skip to content
Claim your free business email

Email marketing software for developers: what to look for when a product sends

Published Updated 15 min readBy the Rasket team

Email marketing software for developers: a terminal window sending a stream of envelopes along glowing lines.

What email marketing software is, when the sender is a product

Email marketing software is the system that holds a list of people who asked to hear from you, lets you write to them as a group, and makes sure the rules that govern bulk email are kept: consent, a working unsubscribe, an address you can be found at. Most email marketing tools are built around the person writing the campaign. You upload a spreadsheet, drag blocks into a template and press send.

That works when the list is a newsletter audience that lives in a spreadsheet. It works less well when the list is your product’s users. Then the facts that decide who should get an email — which plan someone is on, whether they finished onboarding, when their trial ends — are already in your database, and they change every minute. A tool that wants a weekly CSV export is always a little out of date, and the export itself is a job somebody has to own.

So this post looks at email marketing software for developers: the criteria that matter when the thing feeding the list is code. Each section names one criterion, says why it matters, and shows the request that does it in Rasket, taken from our OpenAPI document. At the end there is a checklist you can take to any vendor. In Rasket the marketing side is called Marketing email, and a send to a segment is a Campaign — the broadcasts resource in the API.

Contacts as data, with typed properties

The first thing to check is what a contact is. In a lot of software it is an email address with a bag of free-text fields. That is fine until a segment compares a field that is sometimes "10" and sometimes 10, or a template prints an empty string because one import spelled the column Plan and another plan.

What you want instead is a schema. You declare each custom property once, with a type and a fallback, and the API refuses a value that does not match. In Rasket that is POST /contact-properties: a key of letters, digits and underscores, a type of string or number, and a fallback_value used when a contact has no value of its own.

curl https://api.rasket.com/contact-properties \  -H "Authorization: Bearer $RASKET_API_KEY" \  -H "User-Agent: acme-billing/1.0" \  -H "Content-Type: application/json" \  -d '{ "key": "plan", "type": "string", "fallback_value": "free" }'

Then a contact carries properties whose keys must all be declared. Setting a key to null removes it. Creating a contact for an address you already hold updates it and returns the same id, so your signup handler and your nightly sync can both call it without a lookup first.

curl https://api.rasket.com/contacts \  -H "Authorization: Bearer $RASKET_API_KEY" \  -H "User-Agent: acme-billing/1.0" \  -H "Content-Type: application/json" \  -d '{    "email": "ronald.williams@example.com",    "first_name": "Ronald",    "properties": { "plan": "pro" },    "topics": [{ "id": "'"$TOPIC_ID"'", "subscription": "opt_in" }]  }'

A contact can be addressed by its id or by its email address on every contacts call. That sounds minor. It means the code that knows a user’s email — which is all of your code — never has to store a second identifier just to tell the email marketing software that something changed.

One unsubscribe switch per contact is too blunt for a product. Someone who is tired of the monthly newsletter may still want to hear when an invoice is ready or a feature they asked for ships. The answer is topics: named lists a contact opts in to or out of individually, each with a default for people who never chose.

The part that is easy to overlook is the record. Article 7 of the GDPR says that where processing rests on consent, the controller must be able to demonstrate that the person consented, and that withdrawing must be as easy as giving. A boolean column cannot demonstrate anything. In Rasket every subscription change writes a consent record with its source and time, so the answer to “how did this person end up on this list?” is a lookup rather than an investigation.

curl -X PATCH https://api.rasket.com/contacts/$CONTACT_ID/topics \  -H "Authorization: Bearer $RASKET_API_KEY" \  -H "User-Agent: acme-billing/1.0" \  -H "Content-Type: application/json" \  -d '{ "topics": [{ "id": "'"$TOPIC_ID"'", "subscription": "opt_out" }] }'

Withdrawal also has to work from the inbox. RFC 8058 defines the List-Unsubscribe-Post header that lets a mail client unsubscribe someone with a single request and no web page, and Gmail’s sender guidelines require one-click unsubscribe for marketing mail from anyone sending in bulk. Every Rasket campaign carries those headers, and the hosted preference page lists the public topics so a contact can leave one without leaving all of them.

Consent is yours to collect

Software can record consent; it cannot create it. Only mail people who asked for it, and never a bought or scraped list. A form with a confirmation step is the strongest record you can have of where an opt-in came from.

Segments that are evaluated when the campaign sends

A segment is a saved question about your contacts: everyone on the Pro plan who opted in to product news, say. The question to ask a vendor is when the answer is computed. If a segment is a static list built when you saved it, a user who upgraded this morning is missing from this afternoon’s campaign, and someone who unsubscribed an hour ago is still on it.

Rasket segments are filters, evaluated when they are read and again at send time, never copied into a list. A filter is a tree of all, any and not groups over field conditions — email, the name fields, unsubscribed, created_at or any properties.<key> — and topic conditions. You can also add a contact to a segment explicitly when a rule will not express it.

curl https://api.rasket.com/segments \  -H "Authorization: Bearer $RASKET_API_KEY" \  -H "User-Agent: acme-billing/1.0" \  -H "Content-Type: application/json" \  -d '{    "name": "Pro plan, opted in to product news",    "filter": {      "all": [        { "field": "properties.plan", "op": "eq", "value": "pro" },        { "topic": "'"$TOPIC_ID"'", "subscription": "opt_in" }      ]    }  }'

Because the filter is evaluated at send time, the recipients a campaign reports and the members the segment lists cannot disagree. Before you send, POST /segments/preview answers how many contacts a filter would match without saving it.

Automations triggered by your product's events

The emails that do the most for a product are rarely campaigns. They are the second day of a trial, the nudge to someone who connected a domain and never sent, the follow-up after an export finished. Each one is triggered by something the product saw, which is why email marketing software for a product should take events from your code and start a workflow from them.

In Rasket, an automation is a graph of steps. Exactly one is a trigger on an event name; the rest send a template, wait for a duration or for another event, branch on a condition, update the contact, add them to a segment or change a topic. Your code does one thing: it sends the event.

curl https://api.rasket.com/events/send \  -H "Authorization: Bearer $RASKET_API_KEY" \  -H "User-Agent: acme-billing/1.0" \  -H "Content-Type: application/json" \  -d '{    "event": "trial.started",    "email": "ronald.williams@example.com",    "payload": { "trial_days": 14 }  }'

The call answers 202: the event is durable when it returns, and every enabled automation triggering on trial.started enrols the contact on the next tick. Two calls are two events and two runs, so send it from the place in your code where the trial actually starts, once. Each run is readable afterwards, step by step, which is the part that saves an afternoon the first time someone asks why they got an email they did not expect.

Transactional and marketing in one account, kept apart

A product sends two kinds of email. Transactional mail — receipts, password resets, sign-in links — is triggered by one person’s action and expected immediately. Marketing mail goes to many people at once and needs their consent. The transactional versus marketing post covers the difference in full. The buying question is whether one piece of software should do both.

There is a strong case for one account. One domain setup, one set of DNS records, one suppression list that both kinds of mail obey, one log to search when a customer says an email never came, and one bill. Running two vendors means two of each, and the suppression lists drift: someone who complained about a campaign keeps getting campaigns from the other system.

There is an equally strong case for keeping them apart inside that account. Rasket meters campaigns separately from transactional volume, so a large campaign answers marketing_quota_exceeded rather than spending the quota a password reset needs. And you should send the two from different subdomains, so complaints about a newsletter are scored against the newsletter’s domain and not against your receipts. One account, two meters, two sending domains: that is the shape to look for.

Templates with versions you can roll back

Templates are code that marketers edit. Whoever edits them, a template that a campaign, an automation and a transactional send all use is a shared dependency, and a shared dependency needs versions. The question for a vendor is whether a change takes effect the moment it is saved, and whether you can get yesterday’s version back.

Rasket templates separate the version you are editing from the version sends use. Edits write a new current version; nothing changes for recipients until you publish. Restoring an old version copies it into a new draft, so the history is never rewritten, and sends keep using the published version until you publish again.

# Which version is current, and which one sends usecurl https://api.rasket.com/templates/$TEMPLATE_ID/versions \  -H "Authorization: Bearer $RASKET_API_KEY" \  -H "User-Agent: acme-billing/1.0"
# Copy version 3 into a new draft, then publish it when it looks rightcurl -X POST https://api.rasket.com/templates/$TEMPLATE_ID/versions/3/restore \  -H "Authorization: Bearer $RASKET_API_KEY" \  -H "User-Agent: acme-billing/1.0"
curl -X POST https://api.rasket.com/templates/$TEMPLATE_ID/publish \  -H "Authorization: Bearer $RASKET_API_KEY" \  -H "User-Agent: acme-billing/1.0"

Templates also carry merge variables such as {{{FIRST_NAME|there}}}, with a fallback after the bar, and any declared contact property works the same way. That is where the typed properties from earlier pay off: a property with a fallback cannot print an empty greeting.

Campaigns you can send from code

A campaign is one message to everyone in a segment who is subscribed to its topic. In software built around an editor, sending one from code is often an afterthought, if it is possible at all. If your product announces releases, or your team writes the monthly update in a repository, you want the email marketing API to create and send the campaign the same way the dashboard does.

curl https://api.rasket.com/broadcasts \  -H "Authorization: Bearer $RASKET_API_KEY" \  -H "User-Agent: acme-billing/1.0" \  -H "Content-Type: application/json" \  -d '{    "name": "October product update",    "segment_id": "'"$SEGMENT_ID"'",    "topic_id": "'"$TOPIC_ID"'",    "from": "Acme <news@news.acme.example>",    "subject": "What shipped in October",    "template": { "id": "monthly-digest" }  }'

The send runs through a gate first, and you can read the gate before you send. The checklist answers with every condition, passed or not, in the order the send checks them, and a failed condition carries the same error the send would return. Among them are the rules the law and the mailbox providers set: the FTC’s CAN-SPAM guidance requires a valid physical postal address in every commercial email and a working way to opt out, so Rasket refuses to send without a postal address on file and appends an unsubscribe footer to a body that lacks one.

# Every condition of the send gate, passed or notcurl https://api.rasket.com/broadcasts/$BROADCAST_ID/checklist \  -H "Authorization: Bearer $RASKET_API_KEY" \  -H "User-Agent: acme-billing/1.0"
# Queue it, or pass scheduled_at to send latercurl https://api.rasket.com/broadcasts/$BROADCAST_ID/send \  -H "Authorization: Bearer $RASKET_API_KEY" \  -H "User-Agent: acme-billing/1.0" \  -H "Content-Type: application/json" \  -d '{ "scheduled_at": "2026-10-15T09:00:00Z" }'

Afterwards, GET /broadcasts/{id}/recipients lists who was sent, delivered, opened, clicked or bounced, and /clicked-links which links they followed. The Campaigns page shows the same thing in the dashboard, and the newsletter API post walks through the whole sequence end to end.

Webhooks for what happened next

Email marketing software holds facts your product needs back: that someone unsubscribed, that an address bounced, that a complaint came in, that an automation failed. If the only way to learn them is to log in to a dashboard, your product will keep offering a newsletter checkbox to someone who opted out last week.

Look for webhooks that cover contacts and automations as well as messages, signed so you can verify where they came from, and replayable when your endpoint was down. Rasket’s webhooks do all three: the events include contact.updated, email.complained and automation.run.failed, every delivery is signed, and a missed event can be replayed.

curl https://api.rasket.com/webhooks \  -H "Authorization: Bearer $RASKET_API_KEY" \  -H "User-Agent: acme-billing/1.0" \  -H "Content-Type: application/json" \  -d '{    "endpoint": "https://acme.example/hooks/rasket",    "events": [      "contact.updated",      "email.complained",      "email.bounced",      "automation.run.failed"    ]  }'

An email marketing API with a published contract

Every vendor says it has an API. The useful questions are narrower. Can the API do everything the dashboard does, or only send? Is it described by a machine-readable document you can generate a client from and test against? Do lists paginate the same way everywhere, do errors have stable names you can branch on, and do retries have an idempotency key?

Rasket’s API is described by an OpenAPI document that covers contacts, properties, topics, segments, templates, campaigns, automations and webhooks, not only sending. The contacts and campaigns references document the calls in this post, and every sample here was checked against that document. Typed clients for TypeScript and Python are on the way; until they are published, every example here is plain curl, which is also the fastest way to judge an API before you commit to it.

Tooling for AI agents

A growing share of setup work is done by an assistant in an editor or a terminal: add a domain, draft a template, build a segment, check why a campaign did not go. The question is whether the software gives that assistant a safe door, or whether the only option is pasting an API key into a prompt.

Rasket runs a remote MCP server. An assistant connects to it with OAuth, so it acts with a token you granted and can revoke, never with your key, and the tools it sees can be narrowed to the jobs you want it to do.

claude mcp add --transport http rasket https://api.rasket.com/mcp

The AI agents page describes the rest of what an assistant can read, including Markdown at every docs URL, and the MCP post explains the trust decision in more detail.

A buyer's checklist for email marketing software

Feature lists all look alike. The way to compare email marketing software is to try the same small job on each candidate and write down where it gets hard. This is the job, in the order we would do it, and every step takes minutes on a trial account.

  1. Declare a typed property — Create a contact property with a type and a fallback, then try to set a value of the wrong type on a contact and see whether the API refuses it.
  2. Record and withdraw consent — Create a topic, opt a test contact in, opt them out of that topic alone, and look for a record of each change with its source and time.
  3. Build a segment and change a contact — Save a segment from a property and a topic, then change the test contact so they no longer match and check that the segment drops them without a rebuild.
  4. Trigger an automation from an event — Send one product event from code and confirm that the automation triggering on it starts a run you can read step by step afterwards.
  5. Send a campaign through the API — Create a campaign from a published template, read the send checklist, send it to yourself and confirm the one-click unsubscribe headers are present.
  6. Listen for what happened — Register a webhook for unsubscribes, complaints and failed automations, unsubscribe from the test campaign, and check that a signed event arrives.

The questions to answer

  • Can I declare a contact property with a type, and does the API refuse a value of the wrong type?
  • Can a contact opt out of one topic and stay subscribed to another, and is there a record of each change with its source and time?
  • Is a segment evaluated when the campaign sends, or frozen when it was saved?
  • Can my code start an automation by sending an event, without an export?
  • Do transactional and marketing email share one suppression list but not one quota?
  • Can I restore an old template version without changing what sends use?
  • Can I create, check, schedule and send a campaign through the API alone?
  • Do webhooks cover unsubscribes and failed automations, signed and replayable?
  • Is the API described by an OpenAPI document that matches what it does?
  • Can an assistant work with it without ever seeing my API key?
  • Does every campaign carry one-click unsubscribe headers and a postal address without my having to remember them?

If the answers are mostly yes, the software was built for a product to drive. If they are mostly “through the dashboard”, it was built for a person to drive, which may be exactly what your team needs, and is worth knowing before the first import.

Frequently asked questions

What is email marketing software?

It is the system that holds a list of people who asked to hear from you, lets you write to them as a group or automatically, and keeps the rules of bulk email for you: a record of consent, a one-click unsubscribe, a postal address in every message and a suppression list that every send obeys.

What makes email marketing software good for developers?

That a product can drive it without a person in the loop. Contacts with typed properties your code keeps current, segments evaluated at send time, automations started by events your code sends, campaigns created over an API, webhooks back, and an OpenAPI document that describes all of it accurately.

Should transactional and marketing email use the same software?

One account is simpler: one domain setup, one suppression list and one log. But keep the two apart inside it. Rasket meters campaigns separately so a large send cannot use up the quota a receipt needs, and sending each kind from its own subdomain keeps a newsletter's complaints off your receipts.

Is there an email marketing API for sending campaigns from code?

Yes. In Rasket a campaign is the broadcasts resource: create a draft naming a segment, a topic and a template, read its checklist to see every condition the send will check, then send it now or at a scheduled time. Who was sent, delivered, opened, clicked or bounced is readable from the API afterwards.

How do I prove a contact consented to marketing email?

Keep a record per change, not a boolean. Rasket writes a consent record with its source and time every time a contact's subscription to a topic changes, which is what the GDPR's requirement to be able to demonstrate consent asks for. Where the opt-in came from, ideally a confirmed form, is your side of that record.

Can an AI assistant manage my email marketing tools safely?

It can if the software gives it a door that is not your API key. Rasket runs a remote MCP server that an assistant connects to over OAuth, so it acts with a grant you can narrow and revoke. It can add a domain, draft a template or build a segment, and you can see and undo what it did.

Sources

  1. RFC 8058: Signaling One-Click Functionality for List Email Headers — RFC Editor, read 2026-10-01
  2. Email sender guidelines — Google, read 2026-10-01
  3. CAN-SPAM Act: A Compliance Guide for Business — Federal Trade Commission, read 2026-10-01
  4. Art. 7 GDPR: Conditions for consent — Intersoft Consulting, read 2026-10-01
  • Marketing email — An email marketing platform on the API you already send with: campaigns to contacts and segments, automations from your product's events, a brand-aware editor.
  • Campaigns — An audience you own: contacts with typed properties, segments, topics people subscribe to, and campaigns sent through the same pipeline as your other mail.
  • Automations — Workflows that run per contact: start on a custom event or a segment change, wait, branch, and send. Every run pins a version and is inspectable.
  • Templates — Write an email once, publish a version, and send it by ID or alias with typed variables. Every send records the exact version it used.
  • For AI agents — An MCP server, a setup skill, Markdown at every docs URL and an OpenAPI document, so an assistant can set Rasket up and send for you with no key in the prompt.
  • Topic — A topic is a category a reader can subscribe to on its own: product news, billing notices, a weekly digest. Topics turn unsubscribe from a single switch into…
  • Segment — A segment is a named group of contacts described by a rule rather than listed by hand: everyone who signed up this month, everyone on a particular plan,…
  • Newsletter API: send campaigns from your code — Send a newsletter over an API in six calls: create a topic, add contacts with consent, build a segment, create the campaign, pass the gate and read the report.

Start sending this morning

Sign up, verify a domain and send your first email in minutes.