Markdown

Senders

Updated Nov 2, 20263 min read

A sender is a saved From: the name people see, an address on one of your verified domains, an optional reply-to address, and a short handle. Send with sender: "billing" instead of writing the address in every call, give each template the sender it goes out as, and pick a sender on each automation email. Change a sender once and every automation email and template using it picks up the change.

In the dashboard: Domains › Senders.

Base URL: https://api.groundmail.dev

Auth:

Reading senders needs the domains:read scope; creating, changing and deleting them needs domains:write.

HTTP
Authorization: Bearer gm_...

Send as a sender

Pass sender (a handle or id) on POST /emails or each item of a batch:

The email goes out as the sender's name and address, and replies go to its reply-to address unless the request sets reply_to.

Who an email is from, in order:

  1. from (and reply_to) on the request.
  2. sender on the request.
  3. The template's sender, when you send a template that has one.
  4. Your default sender.

So with a default sender, or a sender on the template, a send can leave out from entirely. A from you pass always wins. A sender your team doesn't have returns 422 with a validation_error.

TypeScript
await groundmail.emails.send({
  sender: 'billing',
  to: user.email,
  template: { id: 'receipt', variables: { amount: '$20.00' } },
})

The sender object

Field Notes
name The name people see in their inbox. Up to 120 characters
address The address it sends from. Must be on one of your verified domains
from Name and address as the inbox shows them
reply_to Where replies go. null means replies go to address
handle What sends use (sender: "billing"). Lowercase letters, digits and hyphens, unique on your team
is_default Sends with no from, sender or template sender go out as the default, and new automations start with it. At most one per team
used_by How many automations and templates send as it
JSON
{
  "id": "4f0c7e2a-6b1d-4c55-9a3e-2d8f6a1b9c10",
  "name": "Acme Billing",
  "address": "billing@mail.acme.com",
  "from": "Acme Billing <billing@mail.acme.com>",
  "reply_to": "billing@acme.com",
  "handle": "billing",
  "is_default": false,
  "used_by": { "automations": 2, "templates": 3 },
  "created_at": "2026-11-02T10:00:00.000Z",
  "updated_at": "2026-11-02T10:00:00.000Z"
}

List senders

GET/senders

The default sender comes first.

Terminal
curl https://api.groundmail.dev/senders \
  -H "Authorization: Bearer $GROUNDMAIL_API_KEY"
JSON
{ "data": [ { "id": "…", "handle": "default", "is_default": true, "...": "…" } ] }

Add a sender

POST/senders
Field Required Notes
name Yes The name people see
address Yes On one of your verified domains. Add and verify a domain first to use a new one
reply_to No Leave it out to get replies at address. Use an inbox someone reads
handle No Made from name when left out (Acme Billing → acme-billing)
is_default No Make it the default. Your first sender becomes the default unless this is false; making another sender the default takes it from the previous one

Returns 201 with { "sender": { … } }. A handle another sender already uses returns 409 conflict; an address on a domain that isn't verified returns 400 validation_error.

Terminal
curl -X POST https://api.groundmail.dev/senders \
  -H "Authorization: Bearer $GROUNDMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme Billing", "address": "billing@mail.acme.com", "reply_to": "billing@acme.com", "handle": "billing" }'

Retrieve, update, delete

  • GET /senders/{id}: id or handle. Returns { "sender": { … } }.
  • PATCH /senders/{id}: any of name, address, reply_to (null sends replies to address), handle, is_default. Automation emails using the sender pick up the change on their next send. Renaming handle updates the automation emails that use it; code that sends with the old handle needs the new one.
  • DELETE /senders/{id}: returns { "ok": true }.

A sender that an automation email or a template uses can't be deleted. The 409 names them, and details lists them, so you can pick another sender on each first:

Deleting the default sender is allowed: sends with no from then need one again.

JSON
{
  "error": {
    "message": "Sender \"billing\" is used by 1 automation (Receipts) and 1 template (Receipt). Pick another sender on them, then delete it.",
    "code": "conflict",
    "details": { "automations": ["Receipts"], "templates": ["Receipt"] }
  }
}

Templates and automations

  • Templates: set sender_id (a sender id or handle) on create or update, or pick Sends as in the template editor. Sends of that template without from or sender go out as it, and so does the editor's Send a test.
  • Automations: an email step can carry email.sender (a handle) instead of email.from. It's looked up each time the step sends. See Who an email step is from.

SDK

TypeScript
import { Groundmail } from 'groundmail'

const groundmail = new Groundmail(process.env.GROUNDMAIL_API_KEY!)

await groundmail.senders.create({
  name: 'Acme Billing',
  address: 'billing@mail.acme.com',
  reply_to: 'billing@acme.com',
  handle: 'billing',
})

const { data } = await groundmail.senders.list()
await groundmail.senders.update('billing', { reply_to: 'accounts@acme.com' })
await groundmail.senders.remove('billing')

CLI

Terminal
groundmail senders list
groundmail senders create --name "Acme Billing" --address billing@mail.acme.com --reply-to billing@acme.com --handle billing
groundmail senders get billing
groundmail senders update billing --default
groundmail senders delete billing --yes

groundmail emails send --sender billing --to user@example.com --subject "Your receipt" --html "<p>Thanks!</p>"
groundmail templates update --id receipt --sender billing

OpenAPI

Full schemas: openapi.yaml (Senders tag).

Was this page helpful?