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.

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

Available provider features:

- `ntfy`
- `webhook`
- `dingtalk`
- `email`

## Example

```rust
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:

```json
{
  "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

```rust
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.