← Noti5 · Integrations

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.

Need access? Noti5 is currently invite-only for clients of The Bumblebee Branding Company. If you'd like to use it for your own apps, email hello@noti5.app.

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.

See setup ↓

WordPress Ready

Mu-plugin auto-pairs on first page load. Replaces wp_mail(), captures form submissions (CF7 / WPForms / Gravity / Fluent / Elementor), optional admin 2FA.

See setup ↓

Android (Kotlin) Ready

Single-file client using OkHttp. Send transactional email from native Android apps. Async, fire-and-forget.

See setup ↓

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.

See API ↓

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:

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

FieldRequired?Notes
event_typeyesSemantic tag for your logs, e.g. user.welcome, order.shipped. Free-form, ≤100 chars.
channelyesemail · whatsapp · auto (heuristic by recipient shape)
recipientyesEmail address or E.164-format phone number
template_codeeither / orResolves to a pre-registered template. Provide this OR (subject + body_html / body_text)
variablesoptionalObject — values substituted into template Mustache placeholders
subject + body_html / body_textraw mode onlyInline content when no template_code
from_nameoptionalOverrides the site's resolved brand name in the From header
reply_tooptionalOverrides the site's default reply_to_email
metadataoptionalFree-form object attached to the message log

Other endpoints

MethodEndpointPurpose
POST/v1/notify/batchSend the same template to many recipients in one call
POST/v1/otp/sendGenerate + deliver a one-time password (email or WhatsApp)
POST/v1/otp/verifyVerify a user-supplied code against an active OTP
POST/v1/otp/resendRe-issue an existing OTP (subject to cooldown)
GET/v1/messagesList sent messages with filters
GET/v1/messages/{uuid}Single message + delivery timeline
GET/v1/siteThis 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.

  1. Copy Noti5MailTransport.php and Noti5ServiceProvider.php into app/Mail/ and app/Providers/
  2. Register the provider in config/app.php or inline in AppServiceProvider::boot()
  3. 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'),
    ],
  4. Set in .env:
    MAIL_MAILER=noti5
    NOTI5_API_KEY=n5_live_xxx
    NOTI5_API_BASE=https://noti5.app/v1
  5. 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.

  1. Download the zip (BBC team: /admin/integrations)
  2. Extract into wp-content/mu-plugins/
  3. Visit any page on the WP site — plugin auto-pings Noti5 to request approval
  4. Approve the pending site in Noti5 admin (or BBC ops does it for you)
  5. Within 5 min the plugin pulls its API key and starts routing mail

What the plugin does:

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

HTTPCodeWhat it means
401unauthenticatedMissing / malformed Authorization header
401invalid_api_keyKey doesn't exist or has been revoked
401api_key_expiredKey passed its expires_at
403ip_not_allowedSource IP not in this key's whitelist
403site_suspendedThe Site this key belongs to has been suspended
422validation_failedOne or more body fields are invalid (see error.fields)
422recipient_suppressedRecipient is on the suppression list — terminal
429rate_limitedToo 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

EventFires when
message.sentMessage accepted by the provider (MailerSend / Meta)
message.deliveredProvider confirmed delivery to recipient mailbox / device
message.readRecipient opened (WhatsApp read receipt; email open pixel)
message.failedProvider returned a transient or terminal failure
message.bouncedRecipient address hard-bounced; auto-added to suppressions
message.rejectedSend blocked before provider (e.g. recipient suppressed)
message.reply.receivedInbound 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);
}