Skip to main content

arcly_http_identity/
notify.rs

1//! Delivery of identity messages (verification links, reset links, OTP codes,
2//! magic links). The app supplies the transport — the crate links no mail/SMS
3//! SDK, matching the `OAuth2Provider` / `SecretSource` pattern.
4
5use async_trait::async_trait;
6
7use crate::error::Result;
8
9/// A message the engine wants delivered to a user out-of-band.
10#[derive(Debug, Clone)]
11pub struct Notification {
12    /// Where to deliver — an email address or phone number.
13    pub to: String,
14    /// Delivery channel.
15    pub channel: Channel,
16    /// What this message is for (drives template selection app-side).
17    pub kind: NotificationKind,
18    /// The token / code / link the user must act on.
19    pub payload: String,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum Channel {
24    Email,
25    Sms,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum NotificationKind {
30    EmailVerification,
31    PhoneVerification,
32    PasswordReset,
33    MagicLink,
34    LoginOtp,
35}
36
37/// Transport for out-of-band identity messages. Implement with SES / Twilio /
38/// SMTP / a queue. Returning `Ok(())` means "accepted for delivery".
39#[async_trait]
40pub trait Notifier: Send + Sync + 'static {
41    async fn send(&self, message: Notification) -> Result<()>;
42}
43
44/// A `Notifier` that logs at `info` and drops the message — for local dev and
45/// tests. **Never** use in production: it would silently swallow reset links.
46pub struct LogNotifier;
47
48#[async_trait]
49impl Notifier for LogNotifier {
50    async fn send(&self, message: Notification) -> Result<()> {
51        tracing::info!(
52            to = %message.to,
53            channel = ?message.channel,
54            kind = ?message.kind,
55            payload = %message.payload,
56            "identity notification (LogNotifier — not actually delivered)"
57        );
58        Ok(())
59    }
60}