infobip-sms-sdk 0.1.0

Async Rust SDK for the Infobip SMS API: send messages, manage scheduled bulks, query delivery reports and logs, fetch inbound SMS, and parse webhook payloads.
Documentation
//! Types shared across multiple endpoints.
//!
//! These crop up in send responses, delivery reports, logs, and webhook
//! payloads. None of them are tied to one specific endpoint, so they
//! live here.

use serde::{Deserialize, Serialize};

/// Reported alongside every sent message and on every delivery report.
///
/// The `(group_id, group_name, id, name)` quadruple identifies the
/// status; `description` and `action` are human-readable supplements.
/// See Infobip's [response status and error codes][1] page for the
/// canonical mapping.
///
/// [1]: https://www.infobip.com/docs/essentials/response-status-and-error-codes
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Status {
    /// Numeric ID of the broad status group
    /// (matches [`MessageGeneralStatus`]).
    pub group_id: Option<i32>,
    /// Group name, e.g. `"DELIVERED"`, `"PENDING"`.
    pub group_name: Option<String>,
    /// Numeric ID of the specific status.
    pub id: Option<i32>,
    /// Specific status name, e.g. `"DELIVERED_TO_HANDSET"`.
    pub name: Option<String>,
    /// Human-readable description of the status.
    pub description: Option<String>,
    /// Suggested action to recover from a non-terminal status.
    pub action: Option<String>,
}

/// High-level status group reported on a message.
///
/// Mirrors the API's `MessageGeneralStatus` enum. Each variant maps to
/// a section in the [response status and error codes][1] documentation.
///
/// [1]: https://www.infobip.com/docs/essentials/response-status-and-error-codes
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum MessageGeneralStatus {
    /// The platform accepted the message but hasn't yet attempted
    /// delivery.
    Accepted,
    /// Delivery is in progress; a terminal status will follow.
    Pending,
    /// The message will not be delivered (e.g. invalid handset).
    Undeliverable,
    /// The message was successfully delivered to the handset.
    Delivered,
    /// The validity period elapsed before delivery completed.
    Expired,
    /// The platform rejected the message before sending it.
    Rejected,
}

/// Error category attached to a [`MessageError`].
///
/// Mirrors the API's `MessageErrorGroup` enum.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum MessageErrorGroup {
    /// No error.
    Ok,
    /// Errors caused by the handset (e.g. handset unreachable).
    HandsetErrors,
    /// Errors caused by the user (e.g. invalid number).
    UserErrors,
    /// Errors caused by the operator network.
    OperatorErrors,
}

/// Per-message error reported on delivery reports and logs.
///
/// See Infobip's [error codes documentation][1] for the meaning of each
/// `(group_id, id)` pair.
///
/// [1]: https://www.infobip.com/docs/essentials/response-status-and-error-codes#error-codes
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageError {
    /// Numeric error group ID.
    pub group_id: Option<i32>,
    /// Error group name (typed enum).
    pub group_name: Option<MessageErrorGroup>,
    /// Numeric error ID.
    pub id: Option<i32>,
    /// Specific error name (e.g. `"NO_ERROR"`,
    /// `"EC_ABSENT_SUBSCRIBER"`).
    pub name: Option<String>,
    /// Human-readable description.
    pub description: Option<String>,
    /// Whether the error is permanent (`true`) or transient (`false`).
    pub permanent: Option<bool>,
}

/// Per-SMS price reported on delivery reports, logs, and inbound
/// messages.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Price {
    /// Cost of one SMS part, in [`Self::currency`].
    pub price_per_message: Option<f64>,
    /// ISO 4217 currency code, e.g. `"EUR"`.
    pub currency: Option<String>,
}

/// Platform routing options.
///
/// Used both on send requests (to *select* an entity / application) and
/// on responses (to *report* which routing was applied). For the
/// platform model, see Infobip's [application and entity management
/// docs][1].
///
/// [1]: https://www.infobip.com/docs/cpaas-x/application-and-entity-management
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Platform {
    /// Entity ID to use, when sending; entity ID that was used, when
    /// reporting.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub entity_id: Option<String>,
    /// Application ID to use / that was used.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub application_id: Option<String>,
}

/// Region-specific options imposed by local regulations.
///
/// Set the variant for the destination region. Multiple options may be
/// set if a single send targets several regions, but each individual
/// SMS is governed by exactly one of them.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegionalOptions {
    /// Required for promotional / transactional SMS to Indian numbers
    /// under DLT regulations.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub india_dlt: Option<IndiaDltOptions>,
    /// Required for promotional SMS to Turkish numbers under IYS
    /// regulations.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub turkey_iys: Option<TurkeyIysOptions>,
    /// Optional metadata for SMS to South Korean numbers.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub south_korea: Option<SouthKoreaOptions>,
}

/// India DLT (Distributed Ledger Technology) parameters for SMS to
/// Indian numbers.
///
/// All Indian SMS senders must be registered under DLT and must include
/// their assigned principal entity ID with each send.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IndiaDltOptions {
    /// Registered DLT content template ID matching the message body.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content_template_id: Option<String>,
    /// Your assigned DLT principal entity ID. **Required.**
    pub principal_entity_id: String,
    /// Your assigned telemarketer ID (required for aggregators).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub telemarketer_id: Option<String>,
}

/// Turkey IYS (Iletişim Yönetim Sistemi) parameters for promotional SMS
/// to Turkish numbers.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TurkeyIysOptions {
    /// Brand code derived from your VAT number. Defaults to the value
    /// configured on your account if omitted.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub brand_code: Option<i32>,
    /// Whether the recipient is an individual (`Bireysel`) or a
    /// commercial entity (`Tacir`). **Required.**
    pub recipient_type: IysRecipientType,
}

/// Recipient classification under Turkey's IYS regulations.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum IysRecipientType {
    /// Individual recipient.
    Bireysel,
    /// Commercial entity.
    Tacir,
}

/// Optional South Korea metadata.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SouthKoreaOptions {
    /// Title displayed on the message.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    /// Reseller's 9-digit business registration number.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reseller_code: Option<i32>,
}

/// How long the platform should keep retrying delivery before giving
/// up.
///
/// Validity periods longer than 48 hours are silently capped at 48
/// hours by the API.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ValidityPeriod {
    /// Numeric amount of [`Self::time_unit`].
    pub amount: i32,
    /// Unit for [`Self::amount`]. Defaults to
    /// [`ValidityPeriodTimeUnit::Minutes`] when omitted.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub time_unit: Option<ValidityPeriodTimeUnit>,
}

/// Time unit for [`ValidityPeriod::amount`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ValidityPeriodTimeUnit {
    /// Seconds.
    Seconds,
    /// Minutes (the API default).
    Minutes,
    /// Hours.
    Hours,
}

/// Restricts when messages may be delivered.
///
/// Useful when local regulations forbid commercial SMS outside business
/// hours, or to spread a marketing send over a longer window. Times are
/// UTC.
///
/// `from` and `to` should both be set (or both omitted) and must differ
/// by at least one hour.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeliveryTimeWindow {
    /// Days of the week on which delivery is permitted. Must contain
    /// at least one entry.
    pub days: Vec<DeliveryDay>,
    /// Start of the daily window (UTC).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub from: Option<DeliveryTime>,
    /// End of the daily window (UTC).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub to: Option<DeliveryTime>,
}

/// One day of the week in a [`DeliveryTimeWindow`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[allow(missing_docs)]
pub enum DeliveryDay {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday,
}

/// A wall-clock time of day (UTC) used as the bound of a
/// [`DeliveryTimeWindow`].
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeliveryTime {
    /// Hour (0–23).
    pub hour: u8,
    /// Minute (0–59).
    pub minute: u8,
}

/// Caps the rate at which a bulk send is dispatched.
///
/// Useful when downstream systems can't handle a flood of replies.
/// Without a limit, Infobip dispatches as fast as the underlying
/// infrastructure allows.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SendingSpeedLimit {
    /// Number of messages per [`Self::time_unit`].
    pub amount: i32,
    /// Unit for [`Self::amount`]. Defaults to
    /// [`SpeedLimitTimeUnit::Minute`] when omitted.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub time_unit: Option<SpeedLimitTimeUnit>,
}

/// Time unit for [`SendingSpeedLimit::amount`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum SpeedLimitTimeUnit {
    /// Per minute (the API default).
    Minute,
    /// Per hour.
    Hour,
    /// Per day.
    Day,
}

/// Cursor pagination metadata returned by `GET /sms/3/logs`.
///
/// Only populated when the request was made with `useCursor=true`. To
/// fetch the next page, set
/// [`crate::models::logs::LogsQuery::cursor`] to
/// [`Self::next_cursor`] and resend.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CursorPageInfo {
    /// The `limit` you requested for this page (echoed back).
    pub limit: Option<i32>,
    /// Opaque cursor for the next page. `None` indicates the last
    /// page.
    pub next_cursor: Option<String>,
}

/// Lifecycle status of a scheduled bulk.
///
/// Used by `GET/PUT /sms/1/bulks/status`. Pause a `Pending` bulk by
/// `PUT`-ing `Paused`, resume it with `Pending`, or cancel it with
/// `Canceled`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BulkStatus {
    /// Scheduled and waiting for its `sendAt` time.
    Pending,
    /// Manually paused.
    Paused,
    /// Currently being dispatched.
    Processing,
    /// Manually canceled.
    Canceled,
    /// Dispatch finished successfully.
    Finished,
    /// Dispatch failed terminally.
    Failed,
}