Features

Everything your product says by email.

Four parts, one API, one bill. Codes and receipts, newsletters and launches, sequences that run themselves, and the plumbing that keeps all of it out of the spam folder.

#01 – Transactional mail

Codes, receipts, and alerts your users can count on.

  • One POST, or point your SMTP client at us. Templates, attachments, tags, and an idempotency key so a retried request never sends twice.

  • Typed clients for Node, Python, PHP, Go, Ruby, and Rust — with the same names as the API, so the docs and the autocomplete agree.

  • Point an address at Herald and receive every reply as structured JSON: parsed body, attachments, headers, and a verified sender.

  • A signed event for every delivery, open, click, bounce, and complaint, retried with backoff until you return a 200.

A painted mountain lake at blue hour
Send a message
curl https://api.herald.dev/v1/send \
  -H "Authorization: Bearer $HERALD_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Mara <[email protected]>",
    "to": "[email protected]",
    "subject": "Welcome to Herald",
    "template": "welcome",
    "data": { "name": "Sam" }
  }'
import { Herald } from 'herald';

const herald = new Herald(process.env.HERALD_KEY);

await herald.send({
  from: 'Mara <[email protected]>',
  to: '[email protected]',
  subject: 'Welcome to Herald',
  template: 'welcome',
  data: { name: 'Sam' },
});
from herald import Herald

herald = Herald(os.environ["HERALD_KEY"])

herald.send(
    from_="Mara <[email protected]>",
    to="[email protected]",
    subject="Welcome to Herald",
    template="welcome",
    data={"name": "Sam"},
)
$herald = new Herald\Client(getenv('HERALD_KEY'));

$herald->send([
    'from' => 'Mara <[email protected]>',
    'to' => '[email protected]',
    'subject' => 'Welcome to Herald',
    'template' => 'welcome',
    'data' => ['name' => 'Sam'],
]);
client := herald.New(os.Getenv("HERALD_KEY"))

_, err := client.Send(ctx, herald.Message{
    From:     "Mara <[email protected]>",
    To:       "[email protected]",
    Subject:  "Welcome to Herald",
    Template: "welcome",
    Data:     map[string]any{"name": "Sam"},
})
herald = Herald::Client.new(ENV["HERALD_KEY"])

herald.send(
  from: "Mara <[email protected]>",
  to: "[email protected]",
  subject: "Welcome to Herald",
  template: "welcome",
  data: { name: "Sam" }
)
Send a message
curl https://api.herald.dev/v1/send \
  -H "Authorization: Bearer $HERALD_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Mara <[email protected]>",
    "to": "[email protected]",
    "subject": "Welcome to Herald",
    "template": "welcome",
    "data": { "name": "Sam" }
  }'
import { Herald } from 'herald';

const herald = new Herald(process.env.HERALD_KEY);

await herald.send({
  from: 'Mara <[email protected]>',
  to: '[email protected]',
  subject: 'Welcome to Herald',
  template: 'welcome',
  data: { name: 'Sam' },
});
from herald import Herald

herald = Herald(os.environ["HERALD_KEY"])

herald.send(
    from_="Mara <[email protected]>",
    to="[email protected]",
    subject="Welcome to Herald",
    template="welcome",
    data={"name": "Sam"},
)
$herald = new Herald\Client(getenv('HERALD_KEY'));

$herald->send([
    'from' => 'Mara <[email protected]>',
    'to' => '[email protected]',
    'subject' => 'Welcome to Herald',
    'template' => 'welcome',
    'data' => ['name' => 'Sam'],
]);
client := herald.New(os.Getenv("HERALD_KEY"))

_, err := client.Send(ctx, herald.Message{
    From:     "Mara <[email protected]>",
    To:       "[email protected]",
    Subject:  "Welcome to Herald",
    Template: "welcome",
    Data:     map[string]any{"name": "Sam"},
})
herald = Herald::Client.new(ENV["HERALD_KEY"])

herald.send(
  from: "Mara <[email protected]>",
  to: "[email protected]",
  subject: "Welcome to Herald",
  template: "welcome",
  data: { name: "Sam" }
)
Inbound · parsed
subject
Re: Your invoice for March
reply
Paid this morning, thanks.
attachments
[]
in_reply_to
msg_8f2k
auth
SPF · DKIM · DMARC
Delivery log

#02 – Broadcasts

Newsletters and launches that land in the inbox, not the tab.

  • Write a broadcast, pick a segment, schedule it. Newsletters, launches, and the occasional apology all go out the same way.

  • Herald ramps a new domain over three weeks so mailbox providers learn to trust it before you send your first big list.

  • Filter contacts by property, event, or engagement, and the segment updates itself as people qualify or fall out.

  • Write in markdown, preview in every major client, and ship a message that renders the same in Gmail and Outlook.

Painted moorland hills at dawn

Campaigns

Broadcasts to a segment of your contacts.

  • Sending 24 Mar, 2026 · 10:24

    Inbound is live

    Subject: Replies now arrive as JSON

  • Sent 20 Mar, 2026 · 09:40

    March notes

    Subject: What we shipped, and one thing we broke

  • Sent 12 Mar, 2026 · 08:15

    Warmup week three

    Subject: Your domain is ready for the big list

  • Draft Not scheduled

    Pricing update

    Subject: A smaller bill for most of you

Domain warmup
  • 1
  • 3
  • 5
  • 7
  • 9
  • 11
  • 13
  • 14

Day 14 of 21 · cap doubles while bounces stay low

Segment · Active on Pro
  • Where plan is pro
  • And last_seen within 30 days
  • And emails_opened at least 3

2,410 contacts match right now

Editor · March notes

# What we shipped

Inbound mail is live. Point an address at Herald and every reply arrives as JSON.

[Read the changelog](/changelog)

What we shipped

Inbound mail is live. Point an address at Herald and every reply arrives as JSON.

Read the changelog

#03 – Automations

Sequences that send themselves, from signup to renewal.

  • Welcome series, onboarding nudges, and win-back campaigns that run themselves once a contact enters them.

  • Start a sequence from a signup, a property change, or an event your product sends, and stop it the moment they convert.

  • Opens, clicks, and conversions per step, so you can see which message does the work and which one gets skipped.

A painted alpine valley at dusk
  1. Trigger

    Contact is added to New signups

  2. Welcome to Herald From Mara <[email protected]>
  3. Wait for 2 days

  4. Your first send, step by step Skipped if they have already sent
  5. Wait for 5 days

  6. Sequence ends

    When every email has gone out, or the moment the contact upgrades.

Triggers
  • contact.created Starts Welcome series
  • plan.changed → pro Starts Getting the most from Pro
  • invoice.overdue Starts Payment reminders
  • contact.inactive_30d Starts Win-back
Analytics · last 30 days
  • Delivered 98.6% +0.4

  • Opened 61.2% +3.1

  • Clicked 18.7% +1.9

  • Upgraded 4.3% +0.8

#04 – Deliverability

The part nobody sees until it breaks.

  • SPF, DKIM, and DMARC set up in one screen, with a check that tells you exactly which record is missing.

  • Above 500,000 messages a month you get your own IP and a reputation nobody else can dent.

  • Bounces, complaints, and unsubscribes land on a shared suppression list so no workspace can email someone who opted out.

  • Seed tests against the big providers before every campaign, and a live log of every delivery, deferral, and bounce.

A painted mountain lake at blue hour
Domain · mail.acme.com
  • TXT mail.acme.com Verified
  • CNAME herald._domainkey.mail.acme.com Verified
  • TXT _dmarc.mail.acme.com Verified
  • MX inbound.mail.acme.com Pending
Domain warmup
  • 1
  • 3
  • 5
  • 7
  • 9
  • 11
  • 13
  • 14

Day 14 of 21 · cap doubles while bounces stay low

Delivery log
Analytics · last 30 days
  • Delivered 98.6% +0.4

  • Opened 61.2% +3.1

  • Clicked 18.7% +1.9

  • Upgraded 4.3% +0.8

Workspaces

Every product, one bill.

Create a workspace for each product, client, or environment you run, each with its own domains, contacts, templates, and API keys — and one invoice at the end of the month.

Painted moorland hills at dawn with a chalk path over the ridge
Acme
  • Acme mail.acme.com
  • Frostline frostline.io
  • Staging staging.acme.com
  • New workspace

Questions, answered.

Volume, overage, warmup, and what happens when a message bounces.

What does "priced by volume" mean?

You pay for the emails you send, not the contacts you store. A list of 200,000 people you email once a quarter costs almost nothing; a list of 500 people you email hourly costs more. That is the fair way round.

What happens if I go over my plan?

Nothing stops. We keep sending and bill the extra at the plan's overage rate, which is shown on the plan before you pick it. You can set a hard cap in settings if you would rather we paused.

Do I need to warm up a new domain?

Yes, and Herald does it for you. A new domain ramps over about three weeks, starting with your most engaged contacts, so mailbox providers see a sender that behaves before they see one that sends in bulk.

What happens when a message bounces?

A hard bounce adds the address to your suppression list and fires a bounce webhook. A soft bounce is retried for up to 72 hours. Either way the delivery log shows you what happened and why.

Can my agent really send email on its own?

Yes. The MCP server exposes send, draft, and report tools, and the skill file teaches the agent the rules: idempotency keys, rate limits, template syntax, and when it should ask a human first. You choose which tools each key can call.

Can I bring my own IP?

On Scale and Enterprise you get a dedicated IP that only your workspaces send from. Below that you share a pool we manage and monitor, which for most senders is the better deal.

Send your first message today.

Three thousand emails a month are free, no card required. Verify a domain, grab a key, and your first send is one request away.