SendPlug

SENDPLUG API · V1

Plug email into
your product.

Self-host a small transactional email API through Gmail or Google Workspace. The hosted beta lets you try the same API before deploying it on your own server.

OPEN SOURCESELF-HOSTEDREST + JSONRESEND SDK SUBSET

01 · QUICKSTART

First email in three steps

STEP 1Connect GmailUse a Google App Password in the dashboard, then run the sender test. Account sign-in is identity-only.
STEP 2Create a tokenSelect that sender and grant the send and status scopes.
STEP 3Call the APIStore the token server-side and point an official Resend SDK at SendPlug.
Hosted beta: set SENDPLUG_BASE_URL=https://sendplug.nirmaker.com. Accounts are free during the beta; the documented burst and daily sender limits apply.

See the official Resend setup guides for Node.js and Python, then use the SendPlug base URL shown below.

SHELL
npm install resend
export SENDPLUG_BASE_URL="https://sendplug.nirmaker.com"
NODE · OFFICIAL RESEND SDK
import { Resend } from "resend";

const resend = new Resend(process.env.SENDPLUG_API_TOKEN, {
  baseUrl: process.env.SENDPLUG_BASE_URL
});
const { data, error } = await resend.emails.send({
  from: "[email protected]",
  to: ["[email protected]"],
  subject: "Welcome",
  text: "Your account is ready."
});
if (error) throw error;
console.log(data.id);
SHELL
pip install resend
export RESEND_API_KEY="$SENDPLUG_API_TOKEN"
export RESEND_API_URL="$SENDPLUG_BASE_URL"
PYTHON · OFFICIAL RESEND SDK
import resend
result = resend.Emails.send({
    "from": "[email protected]",
    "to": ["[email protected]"],
    "subject": "Welcome",
    "text": "Your account is ready.",
})

02 · RESEND SUBSET

Compatible where it counts

POST /emails returns { "id": "…" } for official SDK send calls. It accepts from, to, cc, bcc, subject, text, and html. At least one of text or html is required.

to is exactly one address. Address strings may include a display name, but SendPlug ignores the display name and uses the configured sender identity. cc and bcc allow up to 10 each. The from address must match the token-bound Gmail sender and never selects credentials.

This is not full Resend compatibility. Attachments, templates, tags, scheduling, batch sends, custom headers, reply-to, webhooks, domains, and idempotency keys are not supported.

03 · AUTHENTICATION

One token, one sender

Every request uses an API token in the HTTP Authorization header.

HTTP HEADER
Authorization: Bearer $SENDPLUG_API_TOKEN

A token is bound to the Google sender selected when it was created. A token cannot send through another sender or read another sender's delivery records.

Tokens are shown once and stored by SendPlug only as keyed hashes. Keep them in your server's secret manager or environment—not browser code, mobile applications, repositories, or logs.

Scopes

ScopeAllows
sendQueue email through the token's selected sender.
statusRead delivery status for email sent by that sender.

04 · NATIVE API

Queue application email

POST /api/v1/send validates the request, selects the sender from the token, creates a status record, and returns immediately with HTTP 202 Accepted.

FieldTypeRules
to REQUIREDstring[]Exactly one valid email address.
subject REQUIREDstring1–998 characters.
bodystringPlain-text content. Recommended as a fallback when HTML is supplied.
htmlstring | nullOptional HTML alternative.
ccstring[] | nullUp to 10 valid email addresses.
bccstring[] | nullUp to 10 valid email addresses.
sender_idstring | nullOmit when using an API token. If supplied, it must match the token's sender.

Accepted response

202 · APPLICATION/JSON
{
  "status": "queued",
  "message_id": "5f…@sendplug",
  "sender_id": "sender_…"
}
A 202 response means the job was queued. It does not mean Gmail accepted or delivered the message. Poll the returned message_id.

JavaScript example

SERVER-SIDE JAVASCRIPT
const response = await fetch(
  `${process.env.SENDPLUG_BASE_URL}/api/v1/send`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SENDPLUG_API_TOKEN}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      to: ["[email protected]"],
      subject: "Welcome",
      body: "Your account is ready."
    })
  }
);

if (!response.ok) throw new Error(`Send failed: ${response.status}`);
const queued = await response.json();

05 · STATUS

Read delivery state

Use the message_id from the send response. The token needs the status scope and must belong to the same sender.

SHELL
curl "$SENDPLUG_BASE_URL/api/v1/emails/$MESSAGE_ID" \
  -H "Authorization: Bearer $SENDPLUG_API_TOKEN"
queuedWaiting for a worker or retry.
sendingReserved against quota and connecting to Gmail.
sentAccepted by Gmail.
failedStopped after a permanent or terminal error.
200 · APPLICATION/JSON
{
  "status": "sent",
  "message_id": "5f…@sendplug",
  "to": ["[email protected]"],
  "subject": "Welcome",
  "sender_id": "sender_…",
  "created_at": "2026-07-23T10:00:00+00:00",
  "updated_at": "2026-07-23T10:00:02+00:00",
  "error": null,
  "details": {
    "recipients": ["[email protected]"],
    "sender_id": "sender_…",
    "subject": "Welcome",
    "sent_at": "2026-07-23T10:00:02+00:00"
  }
}
“Sent” is not inbox delivery. It means Gmail accepted the SMTP message. SendPlug does not currently report bounces, opens, clicks, spam placement, or recipient engagement.

06 · LIMITS

Safety before volume

The default sender safety limit is 400 recipients per UTC day. The administrator can configure another limit, while Gmail and Google Workspace enforce their own independent limits.

  • Every to, cc, and bcc recipient counts toward the sender's daily limit.
  • Quota is reserved atomically by the worker to prevent concurrent jobs from oversending.
  • The default HTTP burst is 10 sends per API token per 60 seconds. A 429 response includes Retry-After. This counter is process-local for the single-VM MVP.
  • A request can queue and later become failed if no daily quota remains. Read the status record's error.
  • Transient Gmail/network delivery failures retry automatically with backoff; permanent failures do not.
No idempotency protection. Retry 429 only after Retry-After. Do not automatically retry ambiguous timeouts or 5xx responses unless duplicate email is acceptable.

Status records expire 7 days after their last status update. Persist any application-level audit information you need beyond that window.

07 · ERRORS

HTTP errors

StatusMeaningAction
400The selected sender is unavailable or invalid.Confirm the token's sender is still active.
401The token is missing, malformed, invalid, or revoked.Use a current token in the Bearer header.
403The token lacks the required scope or requests another sender.Grant the correct scope; omit sender_id.
404The status does not exist, expired, or belongs to another sender.Check the message ID and token sender.
422The JSON body failed field validation.Inspect the response's detail.
429The per-token send burst was exceeded.Wait for the Retry-After seconds before retrying.
ERROR SHAPE
{ "detail": "Invalid or revoked API token" }

08 · SECURITY

Production checklist

  • Call SendPlug from your backend, server action, worker, or trusted function.
  • Never embed API tokens in browser JavaScript, mobile binaries, URLs, or analytics events.
  • Grant only the scopes each application needs.
  • Rotate with overlap: create a new same-sender key, deploy it, verify last_used_at, then revoke the old key.
  • App Passwords reach SendPlug over HTTPS and are encrypted before database storage; this is not end-to-end encryption.
  • Do not send cold outreach or email people who did not request it.