Plug Noti5 into any app.
Noti5 is an HTTP API. Send any backend's transactional email or WhatsApp through Noti5 — get delivery tracking, suppressions, branded templates, and a single dashboard across all your apps. Below are the integrations we maintain, plus the API contract for everyone else.
Supported platforms
Each platform has a small adapter so existing app code doesn't change. Adapters translate the platform's native mail / notification call → Noti5's HTTP API.
Laravel Ready
Symfony Mailer transport. Existing Mail::to()->send() calls flow through Noti5 unchanged. ~5 min install.
WordPress Ready
Mu-plugin auto-pairs on first page load. Replaces wp_mail(), captures form submissions (CF7 / WPForms / Gravity / Fluent / Elementor), optional admin 2FA.
Android (Kotlin) Ready
Single-file client using OkHttp. Send transactional email from native Android apps. Async, fire-and-forget.
iOS (Swift) Soon
Single-file URLSession wrapper. Released when first iOS app onboards.
Any backend (HTTP) Direct API
Node, Python, Go, Ruby, .NET, anything that can POST JSON over HTTPS. Hit /v1/notify directly.
Zapier / Make Deferred
Use "Webhooks by Zapier" or Make's HTTP module to hit /v1/notify. Official integration when there's volume demand.
Authentication
Every request must include a Bearer token in the Authorization header. Each app (or "Site" in Noti5 terms) gets its own API key generated by the Noti5 admin.
Authorization: Bearer n5_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keys can be:
- Live or test environment
- Locked to specific source IPs (CIDR allowed)
- Set to expire on a specific date
- Revoked anytime; revocation is instant
The core endpoint: POST /v1/notify
One endpoint covers every transactional email or WhatsApp send. Two modes:
Mode 1 — Template send (recommended for repeat events)
Reference a pre-registered template by code; pass variables; Noti5 renders + ships.
POST https://noti5.app/v1/notify
Authorization: Bearer n5_live_xxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"event_type": "user.welcome",
"channel": "email",
"recipient": "user@example.com",
"template_code": "user_welcome",
"variables": {
"user_name": "Priya",
"verify_url": "https://yourapp.com/verify?t=abc"
}
}
Mode 2 — Raw send (good for arbitrary transactional emails)
Skip the template; supply subject and HTML body inline. Used by the Laravel mail transport for any Mail::send() call.
POST https://noti5.app/v1/notify
Authorization: Bearer n5_live_xxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"event_type": "order.confirmation",
"channel": "email",
"recipient": "user@example.com",
"subject": "Order #1234 confirmed",
"body_html": "<h1>Your order is in</h1><p>Tracking link to follow.</p>",
"from_name": "Acme Co",
"reply_to": "support@acme.com"
}
Response (both modes)
HTTP 202 Accepted
{
"message": {
"id": "8ede7469-f3b3-4892-b312-3708b3fca442",
"status": "queued",
"channel": "email",
"recipient": "user@example.com",
"queued_at": "2026-05-20T10:34:53+00:00"
}
}
Required fields
| Field | Required? | Notes |
|---|---|---|
event_type | yes | Semantic tag for your logs, e.g. user.welcome, order.shipped. Free-form, ≤100 chars. |
channel | yes | email · whatsapp · auto (heuristic by recipient shape) |
recipient | yes | Email address or E.164-format phone number |
template_code | either / or | Resolves to a pre-registered template. Provide this OR (subject + body_html / body_text) |
variables | optional | Object — values substituted into template Mustache placeholders |
subject + body_html / body_text | raw mode only | Inline content when no template_code |
from_name | optional | Overrides the site's resolved brand name in the From header |
reply_to | optional | Overrides the site's default reply_to_email |
metadata | optional | Free-form object attached to the message log |
Other endpoints
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /v1/notify/batch | Send the same template to many recipients in one call |
| POST | /v1/otp/send | Generate + deliver a one-time password (email or WhatsApp) |
| POST | /v1/otp/verify | Verify a user-supplied code against an active OTP |
| POST | /v1/otp/resend | Re-issue an existing OTP (subject to cooldown) |
| GET | /v1/messages | List sent messages with filters |
| GET | /v1/messages/{uuid} | Single message + delivery timeline |
| GET | /v1/site | This site's config + active templates + resolved features |
Laravel
Drop-in Symfony Mailer transport. Existing app code stays unchanged — every Mail::to()->send() now flows through Noti5.
- Copy
Noti5MailTransport.phpandNoti5ServiceProvider.phpintoapp/Mail/andapp/Providers/ - Register the provider in
config/app.phpor inline inAppServiceProvider::boot() - Add a mailer in
config/mail.php:'noti5' => [ 'transport' => 'noti5', 'api_key' => env('NOTI5_API_KEY'), 'api_base' => env('NOTI5_API_BASE', 'https://noti5.app/v1'), ], - Set in
.env:MAIL_MAILER=noti5 NOTI5_API_KEY=n5_live_xxx NOTI5_API_BASE=https://noti5.app/v1 - Smoke-test:
php artisan tinker > Mail::raw('Hello', fn(\$m) => \$m->to('you@example.com')->subject('Noti5 test'));
The package source is maintained in the Noti5 codebase — BBC team members can grab the zip from /admin/integrations (auth required).
WordPress
The Noti5 Connector mu-plugin replaces every WordPress mail call with a Noti5 API call. Drop the folder into wp-content/mu-plugins/, hit any page once, and the plugin auto-pairs.
- Download the zip (BBC team:
/admin/integrations) - Extract into
wp-content/mu-plugins/ - Visit any page on the WP site — plugin auto-pings Noti5 to request approval
- Approve the pending site in Noti5 admin (or BBC ops does it for you)
- Within 5 min the plugin pulls its API key and starts routing mail
What the plugin does:
- Replaces
wp_mail()with Noti5 send (SMTP plugins like WP Mail SMTP / Fluent SMTP / etc. stand down automatically when detected) - Detects WP system mail (password reset, fatal error, new user, etc.) and routes through designed branded templates
- Hooks form submission events on CF7, WPForms, Gravity Forms, Fluent Forms, Elementor Pro Forms
- Optional wp-admin 2FA requires OTP on login
- Per-site features are togglable from Noti5 admin without editing
wp-config.php
Android (Kotlin)
Single Kotlin file using OkHttp. Async, fire-and-forget by default.
// Application.onCreate()
Noti5Client.init(apiKey = BuildConfig.NOTI5_API_KEY)
// Anywhere
Noti5Client.sendEmail(
to = "user@example.com",
subject = "Welcome to MyApp",
bodyHtml = "<h1>Hi</h1><p>Thanks for joining.</p>",
eventType = "user.signup",
) { result ->
result.onSuccess { id -> Log.i("App", "Sent: $id") }
}
Gradle dependency
implementation("com.squareup.okhttp3:okhttp:4.12.0")
AndroidManifest permission
<uses-permission android:name="android.permission.INTERNET"/>
Any backend (HTTP)
No framework? Just POST.
cURL
curl -X POST "https://noti5.app/v1/notify" \
-H "Authorization: Bearer n5_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"event_type": "user.welcome",
"channel": "email",
"recipient": "user@example.com",
"subject": "Welcome",
"body_html": "<h1>Welcome aboard</h1>"
}'
Node.js (fetch)
await fetch("https://noti5.app/v1/notify", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.NOTI5_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
event_type: "user.welcome",
channel: "email",
recipient: "user@example.com",
subject: "Welcome",
body_html: "<h1>Welcome</h1>",
}),
});
Python (requests)
import requests, os
requests.post(
"https://noti5.app/v1/notify",
headers={"Authorization": f"Bearer {os.environ['NOTI5_API_KEY']}"},
json={
"event_type": "user.welcome",
"channel": "email",
"recipient": "user@example.com",
"subject": "Welcome",
"body_html": "<h1>Welcome</h1>",
},
timeout=15,
).raise_for_status()
Error format
All errors share one envelope.
HTTP 4xx
{
"error": {
"code": "invalid_signature",
"message": "Webhook signature failed verification.",
"request_id": "req_eXOIKDzst5J5EjKT",
"documentation_url": "https://docs.noti5.app/errors/invalid_signature"
}
}
Common codes
| HTTP | Code | What it means |
|---|---|---|
| 401 | unauthenticated | Missing / malformed Authorization header |
| 401 | invalid_api_key | Key doesn't exist or has been revoked |
| 401 | api_key_expired | Key passed its expires_at |
| 403 | ip_not_allowed | Source IP not in this key's whitelist |
| 403 | site_suspended | The Site this key belongs to has been suspended |
| 422 | validation_failed | One or more body fields are invalid (see error.fields) |
| 422 | recipient_suppressed | Recipient is on the suppression list — terminal |
| 429 | rate_limited | Too many requests; back off |
Outbound webhooks
If your Site has a webhook_url configured, Noti5 fires events back as POST JSON. Body is HMAC-SHA256 signed via the Site's webhook_secret (sent in X-Noti5-Signature header).
Events
| Event | Fires when |
|---|---|
message.sent | Message accepted by the provider (MailerSend / Meta) |
message.delivered | Provider confirmed delivery to recipient mailbox / device |
message.read | Recipient opened (WhatsApp read receipt; email open pixel) |
message.failed | Provider returned a transient or terminal failure |
message.bounced | Recipient address hard-bounced; auto-added to suppressions |
message.rejected | Send blocked before provider (e.g. recipient suppressed) |
message.reply.received | Inbound reply arrived for one of your sends (only if reply_strategy=webhook / both) |
Payload shape
POST <your webhook_url>
X-Noti5-Signature: sha256=<hmac of body using webhook_secret>
Content-Type: application/json
{
"event": "message.delivered",
"occurred_at": "2026-05-20T10:34:53Z",
"data": {
"message_id": "8ede7469-f3b3-4892-b312-3708b3fca442",
"channel": "email",
"event_type": "user.welcome",
"recipient": "user@example.com",
"status": "delivered"
}
}
Verifying the signature (Laravel example)
$expected = 'sha256=' . hash_hmac('sha256', $request->getContent(), env('NOTI5_WEBHOOK_SECRET'));
if (! hash_equals($expected, $request->header('X-Noti5-Signature'))) {
abort(401);
}