SendPorter Integration Guide
Everything you need to send transactional email through SendPorter: the REST API, the SMTP relay, webhooks, and official SDKs/plugins for the most common platforms. All paths share one pipeline — DKIM signing, automatic retries, bounce-driven suppression, open/click tracking, and event webhooks.
Quickstart
- Create an API key — dashboard → API Keys. Keys look like
sg_live_…and are shown once. - Register your sending domain — dashboard → Domains. Add the
DKIM/SPF DNS records shown, then click Verify. Sends from unregistered
domains are rejected with
422. - Send:
curl -X POST https://sendporter.com/v1/mail/send \
-H "Authorization: Bearer $RELAYMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Orders <orders@yourdomain.com>",
"to": ["customer@example.com"],
"subject": "Your order shipped",
"html": "<h1>It'\''s on the way!</h1>",
"text": "It'\''s on the way!"
}'
# → 202 {"message_ids": ["9b2d5e0c-…"]}
Authentication
Every API request carries your key as a bearer token:
Authorization: Bearer sg_live_…
Keys are organization-scoped. Missing or invalid keys return
401 {"error": "…"}. Create, list, and revoke keys from the dashboard or
via /v1/api-keys.
Sending email — POST /v1/mail/send
| Field | Type | Required | Notes |
|---|---|---|---|
from | string | yes | RFC 5322; "Name <a@b.com>" accepted. Domain must be registered & verified for your org. |
to | string[] | yes | ≥ 1 recipient. One message (and one message id) is created per recipient. |
subject | string | unless template_id | |
html | string | at least one, unless template_id | Both present → multipart/alternative. |
text | string | ||
template_id | UUID | no | Content comes from the stored template; subject/html/text may be omitted. |
template_data | object | no | Values for template placeholders. |
400 rather than ignoring them — a misspelled field never
fails silently. There are intentionally no cc/bcc/attachments/
reply_to/headers fields; use the SMTP relay for those.Response — 202 Accepted:
{"message_ids": ["<uuid-per-recipient>", "…"]}
Track any message afterwards: GET /v1/messages/{id} (status:
queued → sent → delivered, or deferred/bounced/dropped),
GET /v1/messages/{id}/events for the full timeline including opens and clicks,
and POST /v1/messages/{id}/resend.
Templates
Create reusable templates in the dashboard's visual builder or via
POST /v1/templates (name, subject, html_body,
text_body). Placeholders use Go template syntax:
Hello {{.first_name}}, your order {{.order_id}} has shipped.
Note the leading dot — {{.first_name}}, not {{first_name}}. Pass
values at send time via template_data:
{"from": "hello@yourdomain.com", "to": ["c@example.com"],
"template_id": "9b2d5e0c-…",
"template_data": {"first_name": "Priya", "order_id": "1042"}}
SMTP relay
Anything that speaks SMTP can send through SendPorter with zero code changes — and the SMTP path supports everything a raw message can carry: attachments, CC/BCC, Reply-To, custom headers.
| Host | sendporter.com |
| Port | 587 (STARTTLS) |
| Auth | PLAIN — your API key as both username and password |
The MAIL FROM domain must be one of your verified sending domains
(rejected with 550 otherwise); quota exhaustion returns a temporary
452 4.3.1 so well-behaved clients retry next month rather than bouncing.
Webhooks
Subscribe to delivery events with POST /v1/webhooks:
{"url": "https://yourapp.com/hooks/relaymail",
"event_types": ["delivered", "bounced", "complaint", "opened", "clicked"]}
Valid event types: queued, sent, delivered,
deferred, bounced, complaint, dropped,
opened, clicked. The response includes a secret —
shown only once — used to verify deliveries.
Each delivery is a JSON POST, retried on failure (30s → 2m → 10m → 1h → 6h):
{"event_id": 8123, "message_id": "9b2d5e0c-…", "type": "delivered",
"occurred_at": "2026-07-18T10:00:00Z", "metadata": {…}}
Verifying signatures
Every POST carries X-Relaymail-Signature: sha256=<hex> where the digest
is HMAC-SHA256(secret, raw_request_body). Always verify the raw body
with a constant-time compare:
# Python (pip install relaymail)
from relaymail import construct_event
event = construct_event(secret, request.get_data(),
request.headers["X-Relaymail-Signature"])
// Node.js (npm install relaymail)
const { verifyWebhookSignature } = require("relaymail");
const ok = verifyWebhookSignature(secret, rawBody, req.get("X-Relaymail-Signature"));
<?php // composer require relaymail/relaymail-php
$event = \RelayMail\Webhook::constructEvent(
$secret, file_get_contents('php://input'),
$_SERVER['HTTP_X_RELAYMAIL_SIGNATURE'] ?? '');
Errors & quotas
All errors share one shape: {"error": "<message>"} with a meaningful status:
| Status | Meaning |
|---|---|
400 | Malformed body, missing required field, or unknown JSON field. |
401 | Missing/invalid/revoked API key. |
404 | Resource not found (or belongs to another organization). |
409 | Duplicate name/domain, or illegal state transition. |
422 | From-address domain not registered/verified, or sender not on the domain's verified-senders list. |
429 | Monthly sending quota for your plan exhausted. Check GET /v1/billing/usage. |
5xx | SendPorter-side failure — safe to retry. |
Quotas are monthly per organization; GET /v1/billing/usage returns
plan_name, sent_count, monthly_send_limit
(null = unlimited), and the current period bounds.
Endpoint reference (summary)
| Area | Endpoints |
|---|---|
POST /v1/mail/send | |
| Messages | GET /v1/messages, GET /v1/messages/{id}, …/events, …/headers, …/raw, POST …/resend |
| Templates | POST/GET /v1/templates, GET/PUT/DELETE /v1/templates/{id}, POST /v1/templates/assets |
| Domains | POST/GET /v1/domains, POST /v1/domains/{id}/verify, verified-sender management |
| Suppressions | POST/GET /v1/suppressions, DELETE /v1/suppressions/{id} |
| Webhooks | POST/GET /v1/webhooks, GET/PUT/DELETE /v1/webhooks/{id} |
| Contacts & campaigns | /v1/contacts, /v1/contact-lists, /v1/campaigns (schedule, send-now, reports) |
| Account | /v1/api-keys, /v1/billing/plans, /v1/billing/usage, /v1/analytics/summary |
List endpoints paginate with ?limit=&offset=.
Python pip
pip install relaymail
from relaymail import RelayMail
client = RelayMail(api_key="sg_live_...") # or env RELAYMAIL_API_KEY
client.send(
from_="Orders <orders@yourdomain.com>",
to=["customer@example.com"],
subject="Your order shipped",
html="<h1>It's on the way!</h1>",
)
Zero dependencies, Python 3.8+. Typed exceptions
(QuotaExceededError, SenderNotAllowedError, …), full messages /
templates / suppressions / webhooks / usage coverage, and webhook signature
helpers. Source: integrations/python/.
Django pip
pip install django-relaymail
# settings.py
EMAIL_BACKEND = "django_relaymail.backend.RelayMailEmailBackend"
RELAYMAIL_API_KEY = os.environ["RELAYMAIL_API_KEY"]
DEFAULT_FROM_EMAIL = "App <no-reply@yourdomain.com>"
send_mail(), password resets, admin error mail — everything Django sends
now flows through SendPorter. For attachments/CC/BCC use Django's stock SMTP
backend against the relay. Source: integrations/django/.
Node.js npm
npm install relaymail
const { RelayMail } = require("relaymail");
const client = new RelayMail({ apiKey: process.env.RELAYMAIL_API_KEY });
await client.send({
from: "Orders <orders@yourdomain.com>",
to: "customer@example.com",
subject: "Your order shipped",
html: "<h1>It's on the way!</h1>",
});
Zero dependencies (native fetch, Node 18+), TypeScript definitions included,
plus verifyWebhookSignature(). Source: integrations/nodejs/.
PHP composer
composer require relaymail/relaymail-php
$relaymail = new \RelayMail\Client('sg_live_...');
$relaymail->send([
'from' => 'Orders <orders@yourdomain.com>',
'to' => ['customer@example.com'],
'subject' => 'Your order shipped',
'html' => '<h1>It\'s on the way!</h1>',
]);
PHP 8.0+, needs only ext-curl/ext-json. Includes
\RelayMail\Webhook for signature verification.
Source: integrations/php/.
Laravel
Fastest path — pure .env SMTP config, full attachment support:
MAIL_MAILER=smtp
MAIL_HOST=sendporter.com
MAIL_PORT=587
MAIL_ENCRYPTION=tls
MAIL_USERNAME=sg_live_...
MAIL_PASSWORD=sg_live_...
MAIL_FROM_ADDRESS=no-reply@yourdomain.com
Or use the drop-in REST API transport (Laravel 9+) from
integrations/laravel/ — one class plus a Mail::extend('relaymail', …)
registration.
WordPress / WooCommerce plugin
Install the RelayMail Mailer plugin
(integrations/wordpress/relaymail-mailer/), then configure under
Settings → RelayMail: paste an API key, set the From address to a
verified domain, click Send test. All wp_mail() traffic —
including WooCommerce order emails — routes through SendPorter. Two modes:
- SMTP relay (default): attachments, CC/BCC, custom headers all supported.
- REST API: for hosts that block outbound port 587.
Shopify
Shopify doesn't allow replacing its built-in notification transport, so the
integration pattern is a small companion service: Shopify webhooks in →
SendPorter email out (order confirmations, shipping notices, abandoned-cart
nudges). A runnable Node app with Shopify HMAC verification ships in
integrations/shopify/ — point a Shopify webhook
(Settings → Notifications → Webhooks) at it and set three env vars.
Magento 2 composer
composer require relaymail/module-mail
bin/magento module:enable Relaymail_Mail && bin/magento setup:upgrade
Replaces Magento's transactional mail transport app-wide, with a
Test Connection button that also cross-checks your Store Email Addresses
against your verified SendPorter domains. Ships disabled by default and falls
back to Magento's native transport on any SendPorter failure. Source:
integrations/magento/relaymail-mail/.
Everything else — Rails, .NET, Go, Java, cron…
Use the SMTP relay; no SDK required.
# Rails — config/environments/production.rb
config.action_mailer.smtp_settings = {
address: "sendporter.com", port: 587,
user_name: ENV["RELAYMAIL_API_KEY"], password: ENV["RELAYMAIL_API_KEY"],
authentication: :plain, enable_starttls_auto: true,
}
// Go
auth := smtp.PlainAuth("", apiKey, apiKey, "sendporter.com")
smtp.SendMail("sendporter.com:587", auth, from, to, msg)
// .NET
var client = new SmtpClient("sendporter.com", 587) {
EnableSsl = true,
Credentials = new NetworkCredential(apiKey, apiKey),
};
Java/Spring: set spring.mail.host, port=587,
username/password to your API key, and
spring.mail.properties.mail.smtp.starttls.enable=true.
SendPorter — this page is served by the API server at /docs/integrations
and versioned with it. SDK sources live in the repository's
integrations/ directory.