Skip to content
Documentation menu

Get started

SendGrid, Mailgun, Postmark and Resend

Already sending through an email API? Keep your code. Point the SDK at MailBench and every message lands in your inbox instead of being delivered.

How it works

MailBench answers the same send requests as SendGrid, Mailgun, Postmark and Resend, with the same success and error shapes, so their SDKs behave as they do against the real service. Change two settings in your staging or CI environment:

  • The base URL, from the provider's to MailBench's (below).
  • The API key, to your inbox's SMTP username and password joined by a colon:your-inbox-user:your-inbox-pass. Copy them from the inbox in the dashboard.

Nothing is delivered. Each message counts toward your monthly quota exactly like one sent over SMTP, and the same size, attachment and rate limits apply.

SendGrid

Base URL https://app.mailbench.dev/sendgrid instead of api.sendgrid.com. Supported: POST /v3/mail/send.

Key
SENDGRID_API_KEY=SG.your-inbox-user:your-inbox-pass

The SG. prefix is optional; it stops the Node SDK warning about the key format.

Node
import sgMail from "@sendgrid/mail"
import client from "@sendgrid/client"

sgMail.setClient(client)
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
// After setApiKey, which resets the base URL to SendGrid's:
client.setDefaultRequest("baseUrl", "https://app.mailbench.dev/sendgrid/")

await sgMail.send({ to: "ann@example.com", from: "app@example.com", subject: "Welcome", text: "Hi Ann" })
curl
curl https://app.mailbench.dev/sendgrid/v3/mail/send \
  -H "Authorization: Bearer $SENDGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"personalizations":[{"to":[{"email":"ann@example.com"}]}],
       "from":{"email":"app@example.com"},"subject":"Welcome",
       "content":[{"type":"text/plain","value":"Hi Ann"}]}'
  • Each personalization becomes its own message, with its subject, headers and substitutions applied.
  • A dynamic template (template_id) is stored as a note listing the template ID and its dynamic_template_data, since MailBench cannot render SendGrid-hosted templates.
  • Python: SendGridAPIClient(api_key, host="https://app.mailbench.dev/sendgrid"). PHP: new \SendGrid($key, ["host" => "https://app.mailbench.dev/sendgrid"]).

Mailgun

Base URL https://app.mailbench.dev/mailgun instead of api.mailgun.net. Supported: POST /v3/{domain}/messages, POST /v3/{domain}/messages.mime.

Key
MAILGUN_API_KEY=your-inbox-user:your-inbox-pass

Sent as Basic auth with the user "api", as the SDKs do. Any domain works.

Node
import Mailgun from "mailgun.js"
import FormData from "form-data"

const mg = new Mailgun(FormData).client({
  username: "api",
  key: process.env.MAILGUN_API_KEY,
  url: "https://app.mailbench.dev/mailgun",
})

await mg.messages.create("mg.example.com", { from: "app@example.com", to: ["ann@example.com"], subject: "Welcome", text: "Hi Ann" })
curl
curl https://app.mailbench.dev/mailgun/v3/mg.example.com/messages \
  -u "api:$MAILGUN_API_KEY" \
  -F from="app@example.com" -F to="ann@example.com" \
  -F subject="Welcome" -F text="Hi Ann"
  • Attachments and inline files, h: headers, o:tag and v: variables are kept on the captured message.
  • A stored template is saved as a note with its t:variables.
  • PHP: Mailgun::create($key, "https://app.mailbench.dev/mailgun").

Postmark

Base URL https://app.mailbench.dev/postmark instead of api.postmarkapp.com. Supported: POST /email, POST /email/batch, POST /email/withTemplate.

Key
POSTMARK_SERVER_TOKEN=your-inbox-user:your-inbox-pass

Sent in the X-Postmark-Server-Token header.

Node
import * as postmark from "postmark"

const client = new postmark.ServerClient(process.env.POSTMARK_SERVER_TOKEN, {
  requestHost: "app.mailbench.dev/postmark",
})

await client.sendEmail({ From: "app@example.com", To: "ann@example.com", Subject: "Welcome", TextBody: "Hi Ann" })
curl
curl https://app.mailbench.dev/postmark/email \
  -H "X-Postmark-Server-Token: $POSTMARK_SERVER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"From":"app@example.com","To":"ann@example.com","Subject":"Welcome","TextBody":"Hi Ann"}'
  • A batch answers one result per message, failures included, as Postmark does.
  • Tag, MessageStream and Metadata are kept as X-PM- headers on the captured message.
  • A template send is stored as a note with the TemplateModel.

Resend

Base URL https://app.mailbench.dev/resend instead of api.resend.com. Supported: POST /emails, POST /emails/batch.

Key
RESEND_API_KEY=your-inbox-user:your-inbox-pass
Node
// The Node SDK reads RESEND_BASE_URL:
// RESEND_BASE_URL=https://app.mailbench.dev/resend
import { Resend } from "resend"

const resend = new Resend(process.env.RESEND_API_KEY)
await resend.emails.send({ from: "app@example.com", to: ["ann@example.com"], subject: "Welcome", html: "<p>Hi Ann</p>" })
curl
curl https://app.mailbench.dev/resend/emails \
  -H "Authorization: Bearer $RESEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from":"app@example.com","to":"ann@example.com","subject":"Welcome","html":"<p>Hi Ann</p>"}'
  • A batch is checked as a whole before anything is stored, as Resend does.
  • Attachments must carry their content. Attachments by path (a URL) are refused: MailBench never fetches a URL you send it.

Limits and errors

A message over the size limit is refused with 413; more than about ten messages a second per inbox, or a full monthly quota, with 429. Repeated wrong keys lock out the sending IP for a while, as for SMTP logins. Batches take up to 50 messages, and each message up to 100 recipients. Errors use the provider's own format, so your SDK reports them as usual.