Norq

Custom Providers

Norq ships with ten built-in providers — email (resend, sendgrid), SMS (twilio, bird), push (fcm, apns, expo, webpush), all-channel (suprsend), and the debug console provider. For other delivery services, define custom providers directly in norq.config.yaml — no code required.

Custom providers (HTTP-adapter shape) support email, SMS, and WhatsApp channels. Push is supported only via the four built-in push providers (fcm, apns, expo, webpush) — the HTTP-adapter shape can’t express the per-platform routing, FCM OAuth2 token exchange, APNs JWT signing, or VAPID Web Push encryption that real push delivery requires.

How it works

Custom providers are declarative HTTP adapters. You define the endpoint, auth, and request body in YAML. Norq builds the HTTP request from the compiled template output and the SDK executes it.

norq.config.yaml → compile template → build HTTP request → SDK sends

The provider never sees raw templates — only compiled payloads (HTML, plain text, JSON).

Single-channel example (Mailgun)

providers:
  mailgun:
    channels: [email]
    config:
      domain: { required: true }
      api_key: { required: true, secret: true }
    auth:
      type: basic
      username: api
      password: "{{secrets.api_key}}"
    send:
      method: POST
      url: "https://api.mailgun.net/v3/{{config.domain}}/messages"
      body:
        from: "notifications@{{config.domain}}"
        to: "{{input.recipient}}"
        subject: "{{input.subject}}"
        html: "{{input.html}}"
      response:
        success_status: [200]
        message_id_path: "$.id"
 
routing:
  email: mailgun

Multi-channel example (internal gateway)

A single provider can handle multiple channels with per-channel send definitions:

providers:
  internal:
    channels: [email, sms]
    config:
      base_url: { required: true }
      api_key: { required: true, secret: true }
    auth:
      type: bearer
      token: "{{secrets.api_key}}"
    send:
      email:
        method: POST
        url: "{{config.base_url}}/v1/email"
        body:
          to: "{{input.recipient}}"
          subject: "{{input.subject}}"
          html: "{{input.html}}"
        response:
          success_status: [200]
          message_id_path: "$.id"
      sms:
        method: POST
        url: "{{config.base_url}}/v1/sms"
        body:
          to: "{{input.recipient}}"
          text: "{{input.body}}"
        response:
          success_status: [200]
          message_id_path: "$.id"
 
routing:
  email: internal
  sms: internal

Template placeholders

Use Handlebars-style placeholders in URLs, headers, and body values:

Prefix Source Example
{{config.*}} Config fields (from YAML or env vars) {{config.domain}}
{{secrets.*}} Secret config fields (secret: true) {{secrets.api_key}}
{{input.*}} Compiled message payload {{input.subject}}, {{input.html}}, {{input.body}}

Input fields by channel

Channel Available {{input.*}} fields
email recipient, subject, html, text
sms recipient, body
whatsapp recipient, type, payload

Authentication

Three auth types are supported:

# Bearer token
auth:
  type: bearer
  token: "{{secrets.api_key}}"
 
# HTTP Basic
auth:
  type: basic
  username: api
  password: "{{secrets.api_key}}"
 
# API key (header or query param)
auth:
  type: api_key
  header: "X-API-Key"           # or: query: "api_key"
  value: "{{secrets.api_key}}"

Config fields

Config fields define what credentials the provider needs. Mark sensitive values with secret: true — they’re resolved from environment variables and never logged.

config:
  api_key: { required: true, secret: true }
  domain: { required: true }
  from_name: { required: false }

Environment variables are substituted at load time using ${VAR} syntax:

providers:
  mailgun:
    channels: [email]
    config:
      domain: ${MAILGUN_DOMAIN}
      api_key: ${MAILGUN_API_KEY}

Secret values can also be loaded from files or inlined directly. See Secret values in the config reference for the three accepted forms (env var ref, file path, inline literal).

Response handling

The response block tells Norq how to interpret the provider’s HTTP response:

response:
  success_status: [200, 201, 202]
  message_id_path: "$.id"
  • success_status — HTTP status codes that indicate success
  • message_id_path — JSONPath to extract the message ID from the response body

Routing

Map channels to providers in the routing: section. The key must match a provider defined under providers::

routing:
  email: mailgun
  sms: twilio
  whatsapp: internal

Channels without a routing entry are skipped by norq send (even if the template exists).

Validation

Norq validates custom providers at config load time (error codes P001–P012):

  • Missing required config fields
  • Unknown channel names
  • Invalid auth configuration
  • Malformed URL templates
  • Missing send definitions for declared channels

Run norq doctor to check your provider configuration.