cloudiful-notifier 0.2.1

Async notification delivery library for ntfy, generic webhooks, DingTalk robots, and SMTP email.
Documentation

cloudiful-notifier

cloudiful-notifier is a small async delivery library for outbound notifications.

Current providers:

  • ntfy
  • generic JSON webhooks
  • DingTalk custom robots
  • SMTP email

The crate focuses on transport concerns only:

  • provider config
  • provider-neutral message envelopes
  • request signing and header handling
  • uniform delivery results

It intentionally does not own application-specific concepts such as user settings, notification templates, rule storage, or MCP tool surfaces.

Internal layout

  • cloudiful-notifier: only published crate, with Notifier
  • internal core module: shared message, result, error, and trait types
  • internal ntfy module
  • internal generic JSON webhook module
  • internal DingTalk module
  • internal SMTP email module

Features

Default features include all providers.

[dependencies]
cloudiful-notifier = { version = "0.2", default-features = false, features = ["webhook"] }

Available provider features:

  • ntfy
  • webhook
  • dingtalk
  • email

Example

use cloudiful_notifier::{
    MessageEnvelope, Notifier, WebhookChannel,
};
use serde_json::json;
use std::collections::BTreeMap;

let mut extra_headers = BTreeMap::new();
extra_headers.insert("x-source".to_string(), "pricing".to_string());

let channel = WebhookChannel {
    url: "https://example.com/hooks/alerts".to_string(),
    bearer_token: Some("token-123".to_string()),
    extra_headers,
};

let mut message = MessageEnvelope::new("Threshold exceeded").with_title("Market alert");
message
    .metadata
    .insert("symbol".to_string(), json!("600519.SH"));
message
    .metadata
    .insert("threshold".to_string(), json!(1500.0));

let client = reqwest::Client::new();
let notifier = Notifier::new(client);
# let _ = notifier.send(&channel, &message).await;

Webhook payloads use the generic envelope shape:

{
  "title": "Market alert",
  "body": "Threshold exceeded",
  "metadata": {
    "symbol": "600519.SH",
    "threshold": 1500.0
  }
}

Text-oriented providers treat the envelope differently:

  • ntfy: title maps to the Title header, body is sent as-is
  • dingtalk: message text is title + "\n" + body when a title exists
  • email: title maps to Subject, body is plain text, optional html_body adds an HTML alternative part

Email example

use cloudiful_notifier::{
    EmailChannel, EmailTlsMode, MessageEnvelope, Notifier,
};

let channel = EmailChannel {
    smtp_host: "smtp.example.com".to_string(),
    smtp_port: Some(587),
    tls_mode: EmailTlsMode::StartTls,
    username: Some("smtp-user".to_string()),
    password: Some("smtp-pass".to_string()),
    from: "Ops <ops@example.com>".to_string(),
    to: vec!["alice@example.com".to_string()],
    reply_to: Some("noreply@example.com".to_string()),
};

let message = MessageEnvelope::new("Threshold exceeded")
    .with_title("Market alert")
    .with_html_body("<p><strong>Threshold exceeded</strong></p>");

let client = reqwest::Client::new();
let notifier = Notifier::new(client);
# let _ = notifier.send(&channel, &message).await;

Publishing

This repository publishes a single crate: cloudiful-notifier. Provider and core implementations stay internal to the package.