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

  1. Create an API key — dashboard → API Keys. Keys look like sg_live_… and are shown once.
  2. Register your sending domain — dashboard → Domains. Add the DKIM/SPF DNS records shown, then click Verify. Sends from unregistered domains are rejected with 422.
  3. 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

FieldTypeRequiredNotes
fromstringyesRFC 5322; "Name <a@b.com>" accepted. Domain must be registered & verified for your org.
tostring[]yes≥ 1 recipient. One message (and one message id) is created per recipient.
subjectstringunless template_id
htmlstringat least one, unless template_idBoth present → multipart/alternative.
textstring
template_idUUIDnoContent comes from the stored template; subject/html/text may be omitted.
template_dataobjectnoValues for template placeholders.
Exact field names matter. The API rejects unknown JSON fields with 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.

Response202 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.

Hostsendporter.com
Port587 (STARTTLS)
AuthPLAIN — 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 secretshown 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:

StatusMeaning
400Malformed body, missing required field, or unknown JSON field.
401Missing/invalid/revoked API key.
404Resource not found (or belongs to another organization).
409Duplicate name/domain, or illegal state transition.
422From-address domain not registered/verified, or sender not on the domain's verified-senders list.
429Monthly sending quota for your plan exhausted. Check GET /v1/billing/usage.
5xxSendPorter-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)

AreaEndpoints
MailPOST /v1/mail/send
MessagesGET /v1/messages, GET /v1/messages/{id}, …/events, …/headers, …/raw, POST …/resend
TemplatesPOST/GET /v1/templates, GET/PUT/DELETE /v1/templates/{id}, POST /v1/templates/assets
DomainsPOST/GET /v1/domains, POST /v1/domains/{id}/verify, verified-sender management
SuppressionsPOST/GET /v1/suppressions, DELETE /v1/suppressions/{id}
WebhooksPOST/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:

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.