scribasedocs

Concepts

Mail

Scribase Mail Studio is a separate, open-source product (caelum0x/scribase-mail-studio, AGPL-3.0, hosted at mail.scribase.com) that serves a Resend-compatible API at mail-api.scribase.com. It is not part of the per-project Control API described elsewhere in these docs — a Mail Studio API key authenticates against Mail Studio directly, the same way a Resend API key authenticates against api.resend.com.

Existing code written against the resend SDK or Resend's REST API keeps working unchanged: swap the API key and point the client at Mail Studio's base URL (see Migrate from Resend below).

HTTP API

The routes below are served at mail-api.scribase.com, in the same request and response shapes Resend uses:

Resource Routes
Emails POST /emails, POST /emails/batch, GET /emails, GET /emails/:id, PATCH /emails/:id, POST /emails/:id/cancel, GET /emails/:id/attachments
Received emails GET /emails/received, GET /emails/received/:id, GET /emails/received/:id/attachments
Domains POST /domains, GET /domains, GET /domains/:id, PATCH /domains/:id, DELETE /domains/:id, POST /domains/:id/verify
API keys POST /api-keys, GET /api-keys, DELETE /api-keys/:id
Contacts GET, POST /contacts, GET, PATCH, DELETE /contacts/:id, GET, PUT /contacts/:id/topics/:topicId
Audiences and segments /audiences (CRUD + contacts), /segments (CRUD + contacts), /contact-properties (CRUD)
Topics GET, POST /topics, GET, PATCH, DELETE /topics/:id
Broadcasts CRUD /broadcasts, POST /broadcasts/:id/send, POST /broadcasts/:id/cancel, POST /broadcasts/:id/duplicate
Templates CRUD /templates, POST /templates/:id/publish, POST /templates/:id/duplicate, GET /templates/:id/versions
Webhooks CRUD /webhooks, POST /webhooks/:id/signing-secret/rotate, GET /webhooks/:id/events
Suppressions and logs GET, POST /suppressions, DELETE /suppressions/:email, GET /logs, GET /logs/:id

Not yet implemented (kept honest on purpose, not hidden): automations and custom events (/automations, /events), the Inboxes API for agent inboxes, OAuth apps acting on a user account, domain claim (/domains/:id/claim), and enforced TLS per domain (the setting is stored; delivery is opportunistic TLS).

Sending an email

Terminal
curl -X POST https://mail-api.scribase.com/emails \
  -H "Authorization: Bearer $SCRIBASE_MAIL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <hello@acme.com>",
    "to": ["you@example.com"],
    "subject": "It works",
    "html": "<p>Sent through Scribase Mail Studio.</p>"
  }'
TypeScript
import { Resend } from 'resend';

const resend = new Resend(process.env.SCRIBASE_MAIL_KEY, {
  baseUrl: 'https://mail-api.scribase.com',
});

await resend.emails.send({
  from: 'Acme <hello@acme.com>',
  to: ['you@example.com'],
  subject: 'It works',
  html: '<p>Sent through Scribase Mail Studio.</p>',
});

Before sending real traffic, send to the test addresses delivered@test.scribase.com, bounced@test.scribase.com, and complained@test.scribase.com — each produces the matching event without sending real mail, which is useful in CI.

Domains

Add and verify the domain you send from before sending real traffic:

Terminal
curl -X POST https://mail-api.scribase.com/domains \
  -H "Authorization: Bearer $SCRIBASE_MAIL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "acme.com"}'

Add the DKIM and SPF records the response returns at your DNS provider, keep your existing DMARC record, then confirm:

Terminal
curl -X POST https://mail-api.scribase.com/domains/<id>/verify \
  -H "Authorization: Bearer $SCRIBASE_MAIL_KEY"

Webhooks

Webhook payloads (email.sent, delivered, bounced, complained, opened, clicked, received, and more) are signed in the Svix format — svix-id, svix-timestamp, svix-signature headers — so resend.webhooks.verify() and the svix library verify them unchanged:

TypeScript
import { Resend } from 'resend';

const resend = new Resend(process.env.SCRIBASE_MAIL_KEY, {
  baseUrl: 'https://mail-api.scribase.com',
});

export async function POST(req: Request) {
  const payload = await req.text();
  const event = resend.webhooks.verify({
    payload,
    headers: {
      id: req.headers.get('svix-id')!,
      timestamp: req.headers.get('svix-timestamp')!,
      signature: req.headers.get('svix-signature')!,
    },
    webhookSecret: process.env.SCRIBASE_MAIL_WEBHOOK_SECRET!, // whsec_...
  });
  // event.type: 'email.delivered', 'email.bounced', 'email.received', ...
  return new Response('ok');
}

Migrate from Resend

There is no separate import tool for Resend, by design: Mail Studio serves the same request and response shapes Resend does, so moving over is a configuration change, not a data migration.

  1. Create an account or self-host. Sign up at mail.scribase.com for the hosted service, or clone caelum0x/scribase-mail-studio and run it with Docker on your own servers. Both expose the same API.
  2. Add and verify your sending domain (see Domains above). Keep your existing DMARC record.
  3. Create an API key in the Mail Studio dashboard and store it as a new secret, for example SCRIBASE_MAIL_KEY. Keys are sent the same way as before: Authorization: Bearer <key>.
  4. Point the SDK at the new base URL. The resend Node SDK takes a baseUrl option and also reads RESEND_BASE_URL — either works, and nothing else in your sending code changes:
    Terminal
    RESEND_BASE_URL=https://mail-api.scribase.com
  5. Send a test email to the test addresses above before real traffic.
  6. Move webhooks. Re-create the webhook endpoints (dashboard or POST /webhooks) and store the new whsec_... signing secret.
  7. Switch traffic, then watch the first sends. The dashboard shows each email with its events and the API request log (GET /logs). New senders start on warm-up caps that rise as sending stays clean.

See the full walkthrough with copy-paste Node, curl, and Python examples at scribase.com/mail/migrate-from-resend.

Self-hosting

Terminal
git clone https://github.com/caelum0x/scribase-mail-studio
cd scribase-mail-studio
# follow the self-host section of the README