mailkite 0.13.0

Official MailKite SDK for Rust — inbound email → webhook, sending, templates, broadcasts, at-rest encryption, and webhook signature verification.
Documentation
// Conformance runner: drive the Rust SDK through the shared cases and print one
// JSON line per case. Invoked by ../../conformance/run.mjs. Reads MK_BASE_URL /
// MK_CASES / MK_API_KEY from the env, extracting each method's args EXACTLY as
// the Go runner does.

use mailkite::{AttachmentUpload, Client, Error};
use serde_json::{json, Value};

fn main() {
    let cases_path = std::env::var("MK_CASES").expect("MK_CASES not set");
    let base_url =
        std::env::var("MK_BASE_URL").unwrap_or_else(|_| "https://api.mailkite.dev".to_string());
    let api_key = std::env::var("MK_API_KEY").unwrap_or_default();

    let raw = std::fs::read_to_string(&cases_path).expect("read MK_CASES");
    let file: Value = serde_json::from_str(&raw).expect("parse MK_CASES");
    let empty: Vec<Value> = Vec::new();
    let cases = file
        .get("cases")
        .and_then(|v| v.as_array())
        .unwrap_or(&empty);

    let mk = Client::new_with_base_url(api_key, base_url);

    for case in cases {
        let name = case.get("name").and_then(|v| v.as_str()).unwrap_or("");
        let method = case.get("method").and_then(|v| v.as_str()).unwrap_or("");
        let args = case
            .get("args")
            .cloned()
            .unwrap_or_else(|| Value::Object(Default::default()));

        let mut out = serde_json::Map::new();
        out.insert("name".into(), json!(name));
        match dispatch(&mk, method, &args) {
            Ok(result) => {
                out.insert("result".into(), result);
            }
            Err(e) => {
                out.insert(
                    "error".into(),
                    json!({ "status": e.status, "message": e.message }),
                );
            }
        }
        println!("{}", serde_json::to_string(&Value::Object(out)).unwrap());
    }
}

fn str_of<'a>(a: &'a Value, k: &str) -> &'a str {
    a.get(k).and_then(|v| v.as_str()).unwrap_or("")
}

fn i64_of(a: &Value, k: &str) -> Option<i64> {
    a.get(k).and_then(|v| v.as_i64())
}

fn dispatch(mk: &Client, method: &str, a: &Value) -> Result<Value, Error> {
    let id = str_of(a, "id");
    match method {
        "send" => mk.send(a.clone()),
        "uploadAttachment" => {
            let up = AttachmentUpload {
                url: a.get("url").and_then(|v| v.as_str()).map(String::from),
                content: a.get("content").and_then(|v| v.as_str()).map(String::from),
                filename: a.get("filename").and_then(|v| v.as_str()).map(String::from),
                content_type: a.get("contentType").and_then(|v| v.as_str()).map(String::from),
                path: a.get("path").and_then(|v| v.as_str()).map(String::from),
                retention_days: i64_of(a, "retentionDays"),
                bytes: a
                    .get("bytes")
                    .and_then(|v| v.as_str())
                    .map(|s| s.as_bytes().to_vec()),
            };
            mk.upload_attachment(up)
        }
        "agent" => mk.agent(a.clone()),
        "route" => mk.route(a.clone()),
        "listTemplates" => mk.list_templates(),
        "listBaseTemplates" => mk.list_base_templates(),
        "getTemplate" => mk.get_template(id),
        "createTemplate" => mk.create_template(a.clone()),
        "listDomains" => mk.list_domains(),
        "createDomain" => mk.create_domain(a.clone()),
        "getDomain" => mk.get_domain(id),
        "deleteDomain" => mk.delete_domain(id),
        "verifyDomain" => mk.verify_domain(id),
        "setWebhook" => mk.set_webhook(id, json!({ "url": a.get("url") })),
        "deleteWebhook" => mk.delete_webhook(id),
        "testWebhook" => mk.test_webhook(id),
        "checkDomainAvailability" => mk.check_domain_availability(str_of(a, "domain")),
        "registerDomain" => mk.register_domain(a.clone()),
        "semanticSearch" => mk.semantic_search(str_of(a, "query")),
        "listRoutes" => mk.list_routes(),
        "createRoute" => mk.create_route(a.clone()),
        "deleteRoute" => mk.delete_route(id),
        "listMessages" => mk.list_messages(
            i64_of(a, "before"),
            i64_of(a, "limit"),
            a.get("search").and_then(|v| v.as_str()),
        ),
        "getMessage" => mk.get_message(id),
        "retryDelivery" => mk.retry_delivery(id),
        "verifyWebhook" => Ok(Value::Bool(mailkite::verify_webhook_with_tolerance(
            str_of(a, "signature"),
            str_of(a, "payload"),
            str_of(a, "secret"),
            i64_of(a, "toleranceMs").unwrap_or(0),
        ))),
        "replyOk" => Ok(json!(mailkite::reply_ok())),
        "replySpam" => Ok(json!(mailkite::reply_spam())),
        "replyDrop" => Ok(json!(mailkite::reply_drop())),
        "replyBlockSender" => Ok(json!(mailkite::reply_block_sender())),
        "listLists" => mk.list_lists(),
        "createList" => mk.create_list(a.clone()),
        "getList" => mk.get_list(id),
        "updateList" => mk.update_list(id, json!({ "name": a.get("name") })),
        "deleteList" => mk.delete_list(id),
        "listListContacts" => mk.list_list_contacts(
            id,
            i64_of(a, "before"),
            i64_of(a, "limit"),
            a.get("search").and_then(|v| v.as_str()),
        ),
        "addListContacts" => mk.add_list_contacts(id, json!({ "contactIds": a.get("contactIds") })),
        "removeListContact" => mk.remove_list_contact(id, str_of(a, "contactId")),
        "listBroadcasts" => mk.list_broadcasts(),
        "createBroadcast" => mk.create_broadcast(a.clone()),
        "getBroadcast" => mk.get_broadcast(id),
        "updateBroadcast" => mk.update_broadcast(id, json!({ "subject": a.get("subject") })),
        "deleteBroadcast" => mk.delete_broadcast(id),
        "sendBroadcast" => {
            let body = match a.get("scheduledAt") {
                Some(v) => json!({ "scheduledAt": v }),
                None => Value::Null,
            };
            mk.send_broadcast(id, body)
        }
        "decrypt" => {
            mailkite::decrypt(str_of(a, "envelope"), str_of(a, "privateKey")).map(Value::String)
        }
        "encryptRoundtrip" => {
            let envelope = mailkite::encrypt(str_of(a, "plaintext"), str_of(a, "publicKey"))?;
            mailkite::decrypt(&envelope, str_of(a, "privateKey")).map(Value::String)
        }
        _ => Err(Error {
            status: 0,
            message: format!("unknown method {}", method),
            body: None,
        }),
    }
}