Push Channel

Norq compiles push templates to a structured JSON payload with title, body, image, and action URL. The payload works with FCM, APNs, and web push providers.

Pipeline: Markdown -> AST (parse tree) -> { title, body, image, action_url, platforms } JSON

Template files

  • push.md -- Markdown mode
  • push.json -- Native JSON mode (for custom payload structures)

Frontmatter

---
title: "Order shipped!"
enabled: true
ios:
  sound: "default"
  badge: 1
android:
  channel_id: "orders"
web:
  icon: "https://cdn.example.com/icon.png"
---
Field Required Description
title No Notification title. Fallback: :::header content, then first heading.
enabled No true (default) or false.
ios No iOS-specific overrides (sound, badge, etc.)
android No Android-specific overrides (channel_id, etc.)
web No Web push overrides (icon, etc.)

Directive compilation

Directive Push output
:::header Title source (highest priority after frontmatter title)
:::footer Ignored
:::action action_url extracted from the first link
:::callout Body text with warning symbol prefix
:::hero image URL extracted for the push image field
:::fields Body text lines ("Key: Value")
:::media Rendered as text (graceful degradation)
:::columns Stacked as text
:::list Text lines
:::highlight Body text with star prefix
:::centered Plain text (no alignment in push)
:::raw Ignored

Example

---
title: "Order #{{order.id}} shipped"
---
 
::: hero
![](https://cdn.example.com/shipped-banner.png)
:::
 
Hey {{user.first_name}}, your order is on its way!
 
::: action
[Track Order]({{tracking_url}}){primary}
:::

Compiled output

Norq compiles the above template into a structured push payload:

{
  "title": "Order #ORD-123 shipped",
  "body": "Hey Gaurav, your order is on its way!",
  "image": "https://cdn.example.com/shipped-banner.png",
  "action_url": "https://track.example.com/123",
  "platforms": ["ios", "android", "web"]
}

Title resolution priority

The push title is resolved in this order:

  1. title field in frontmatter
  2. Content of :::header directive
  3. First heading (#, ##, etc.) in the body
  4. Empty string (linter warns)

Testing

tests:
  - name: "Push payload under 4KB"
    channel: push
    sample: "New user"
    assert:
      payload_bytes: { lte: 4096 }
 
  - name: "Push has title"
    channel: push
    sample: "New user"
    assert:
      subject: { not_contains: "" }

Best practices

  • Keep push body under 100 characters for good display on all platforms
  • Use :::hero for the notification image (extracted automatically)
  • The first :::action link becomes the tap-through URL
  • Use platform overrides in frontmatter for platform-specific behavior (sounds, badges)
  • Push content is plain text -- bold/italic formatting is stripped