moon-resend

    A typed MoonBit SDK for Resend with a moonfetch HTTP adapter

    resend
    email
    sdk
    saas
    moonbit
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    1 hour ago
    Downloads
    1

    #moon-resend

    面向 Resend 邮件平台的类型化 MoonBit SDK,提供 transport-neutral 核心和基于 moonfetch 的真实 HTTPS adapter,适用于 MoonBit Web 应用、SaaS 服务、后台任务和自动化工具。

    CI

    The first milestone intentionally focuses on a reliable transactional-email loop:

    • build and validate send-email requests;
    • HTML and text bodies, CC/BCC/reply-to, attachments, tags and scheduling;
    • idempotency keys;
    • retrieve, reschedule and cancel request builders;
    • typed parsing of retrieved email metadata and delivery state;
    • batch sending for up to 100 messages;
    • typed success and API error parsing;
    • an optional real HTTPS adapter powered by moonfetch.

    #Quick start

    ///|
    let client = @resend.Client::new("re_xxxxxxxxx").unwrap()

    ///|
    let email = @resend.SendEmailParams::new(
    "Acme <onboarding@example.com>",
    ["user@example.com"],
    "Welcome",
    html="<strong>It works!</strong>",
    text="It works!",
    )

    ///|
    let request = client
    .send_request(email, idempotency_key="welcome-user-42")
    .unwrap()

    HttpRequest is deliberately transport-neutral. This keeps request construction and error behavior deterministic while the optional adapter provides real network I/O.

    #Send over HTTPS

    The hua1104/moon-resend/adapter package provides the supported moonfetch integration:

    ///|
    async fn send_welcome(api_key : String) -> Unit {
    let client = @resend.Client::new(api_key).unwrap()
    let email = @resend.SendEmailParams::new(
    "Acme <onboarding@example.com>",
    ["user@example.com"],
    "Welcome",
    html="<strong>It works!</strong>",
    text="It works!",
    )
    match @adapter.send(client, email, idempotency_key="welcome-user-42") {
    Ok(sent) => println("sent: " + sent.id())
    Err(_) => println("send failed")
    }
    }

    The adapter also exposes send_batch, retrieve, update, cancel, and low-level execute. It disables redirects to protect the authorization header and converts network, TLS and timeout failures to ResendError::Transport.

    #Run the live example

    Use a Resend API key, a sender on your verified domain, and a recipient you control. The values stay in environment variables and are never committed:

    $env:RESEND_API_KEY = "re_..." $env:RESEND_FROM = "Demo <demo@your-verified-domain.example>" $env:RESEND_TO = "you@example.com" moon run cmd/send-live --target native

    The regular test suite never sends an email and requires no secret.

    #Deliberate scope

    This is not advertised as a complete Resend API implementation. The November milestone prioritizes send, batch send, retrieve, reschedule, cancel, validation, errors, tests and one reproducible example. Webhook verification, domains, contacts and broadcasts remain roadmap items until the core loop is stable.

    See MILESTONES.md for the delivery plan, architecture for the package boundary, and competition showcase for the coverage matrix and demo script.

    #Competition roadmap

    • November: stabilize the typed request/response core and publish an auditable MVP.
    • December: harden the moonfetch adapter, add mock-server integration tests, and implement webhook signature verification.
    • January: add domains/contacts APIs, examples, API reference docs and Mooncakes packaging.

    The milestones are deliberately usable on their own. Later competition entries extend the SDK with new capabilities instead of submitting the same work split across repositories.

    #Development

    moon check moon test moon info moon fmt

    The live adapter example also compiles in CI on the native target. On Windows, native builds require MSVC Build Tools with the C++ workload and Windows SDK; the default wasm checks do not.

    #License

    Apache-2.0

    ApiError

    pub struct ApiError {
    status : Int
    name : String
    message : String
    raw_body : String
    }

    ApiError::message

    fn ApiError::message(self : ApiError) -> String

    ApiError::name

    fn ApiError::name(self : ApiError) -> String

    ApiError::raw_body

    fn ApiError::raw_body(self : ApiError) -> String

    ApiError::status

    fn ApiError::status(self : ApiError) -> Int

    Attachment

    pub struct Attachment {
    content : String
    filename : String
    content_type : String?
    content_id : String?
    }

    A file attachment. The content must be Base64 encoded as required by Resend.

    Attachment::new

    fn Attachment::new(content : String, filename : String, content_type? : String, content_id? : String) -> Attachment

    BatchEmailIds

    pub struct BatchEmailIds {
    data : Array[EmailId]
    }

    IDs returned by Resend POST /emails/batch.

    BatchEmailIds::data

    Client

    pub struct Client {
    api_key : String
    base_url : String
    }

    Client::batch_send_request

    fn Client::batch_send_request(self : Client, emails : Array[SendEmailParams], idempotency_key? : String) -> Result[HttpRequest, ResendError]

    Build a POST /emails/batch request. Resend currently accepts up to 100 items.

    Client::cancel_request

    fn Client::cancel_request(self : Client, id : String) -> Result[HttpRequest, ResendError]

    Client::new

    fn Client::new(api_key : String, base_url? : String) -> Result[Client, ResendError]

    Client::parse_batch_response

    fn Client::parse_batch_response(_self : Client, response : HttpResponse) -> Result[BatchEmailIds, ResendError]

    Client::parse_id_response

    fn Client::parse_id_response(_self : Client, response : HttpResponse) -> Result[EmailId, ResendError]

    Parse the shared response used by send and cancel.

    Client::parse_retrieve_response

    fn Client::parse_retrieve_response(_self : Client, response : HttpResponse) -> Result[RetrievedEmail, ResendError]

    Client::retrieve_request

    fn Client::retrieve_request(self : Client, id : String) -> Result[HttpRequest, ResendError]

    Client::send_request

    fn Client::send_request(self : Client, params : SendEmailParams, idempotency_key? : String) -> Result[HttpRequest, ResendError]

    Client::update_request

    fn Client::update_request(self : Client, id : String, params : UpdateEmailParams) -> Result[HttpRequest, ResendError]

    Reschedule an email that has not been sent yet.

    EmailId

    pub struct EmailId {
    id : String
    }

    EmailId::id

    fn EmailId::id(self : EmailId) -> String

    HttpRequest

    pub struct HttpRequest {
    verb : String
    url : String
    headers : Array[(String, String)]
    body : String?
    }

    A transport-neutral HTTP request. Adapters can map this value to any MoonBit HTTP client without coupling the SDK core to one runtime.

    HttpRequest::body

    fn HttpRequest::body(self : HttpRequest) -> String?

    HttpRequest::headers

    fn HttpRequest::headers(self : HttpRequest) -> Array[(String, String)]

    HttpRequest::new

    fn HttpRequest::new(verb : String, url : String, headers? : Array[(String, String)], body? : String) -> HttpRequest

    Construct a custom request for adapters and tests. Most applications should use the request builders on Client.

    HttpRequest::url

    fn HttpRequest::url(self : HttpRequest) -> String

    HttpRequest::verb

    fn HttpRequest::verb(self : HttpRequest) -> String

    HttpResponse

    pub struct HttpResponse {
    status : Int
    headers : Array[(String, String)]
    body : String
    }

    A transport-neutral HTTP response returned by an adapter or test fake.

    HttpResponse::body

    fn HttpResponse::body(self : HttpResponse) -> String

    HttpResponse::headers

    fn HttpResponse::headers(self : HttpResponse) -> Array[(String, String)]

    HttpResponse::new

    fn HttpResponse::new(status : Int, body : String, headers? : Array[(String, String)]) -> HttpResponse

    HttpResponse::status

    fn HttpResponse::status(self : HttpResponse) -> Int

    ResendError

    pub enum ResendError {
    InvalidInput(String)
    Api(ApiError)
    InvalidResponse(String)
    Transport(String)
    }

    ResendError::invalid_input

    fn ResendError::invalid_input(message : String) -> ResendError

    Create an input error from an adapter or extension package.

    ResendError::message

    fn ResendError::message(self : ResendError) -> String

    A concise description suitable for application logs.

    ResendError::transport

    fn ResendError::transport(message : String) -> ResendError

    Create a network, TLS or timeout error from a transport adapter.

    RetrievedEmail

    pub struct RetrievedEmail {
    id : String
    from : String
    to : Array[String]
    subject : String
    created_at : String
    html : String?
    text : String?
    cc : Array[String]
    bcc : Array[String]
    reply_to : Array[String]
    last_event : String?
    scheduled_at : String?
    }

    The stable subset returned by Resend GET /emails/:id. Optional fields stay optional because Resend omits content fields in some delivery states.

    RetrievedEmail::bcc

    fn RetrievedEmail::bcc(self : RetrievedEmail) -> Array[String]

    RetrievedEmail::cc

    fn RetrievedEmail::cc(self : RetrievedEmail) -> Array[String]

    RetrievedEmail::created_at

    fn RetrievedEmail::created_at(self : RetrievedEmail) -> String

    RetrievedEmail::html

    fn RetrievedEmail::html(self : RetrievedEmail) -> String?

    RetrievedEmail::id

    fn RetrievedEmail::id(self : RetrievedEmail) -> String

    RetrievedEmail::last_event

    fn RetrievedEmail::last_event(self : RetrievedEmail) -> String?

    RetrievedEmail::recipients

    fn RetrievedEmail::recipients(self : RetrievedEmail) -> Array[String]

    RetrievedEmail::reply_to

    fn RetrievedEmail::reply_to(self : RetrievedEmail) -> Array[String]

    RetrievedEmail::scheduled_at

    fn RetrievedEmail::scheduled_at(self : RetrievedEmail) -> String?

    RetrievedEmail::sender

    fn RetrievedEmail::sender(self : RetrievedEmail) -> String

    RetrievedEmail::subject

    fn RetrievedEmail::subject(self : RetrievedEmail) -> String

    RetrievedEmail::text

    fn RetrievedEmail::text(self : RetrievedEmail) -> String?

    SendEmailParams

    pub struct SendEmailParams {
    from : String
    to : Array[String]
    subject : String
    html : String?
    text : String?
    cc : Array[String]
    bcc : Array[String]
    reply_to : Array[String]
    attachments : Array[Attachment]
    tags : Array[(String, String)]
    scheduled_at : String?
    }

    Parameters accepted by Resend POST /emails.

    SendEmailParams::json_string

    fn SendEmailParams::json_string(self : SendEmailParams) -> String

    SendEmailParams::new

    fn SendEmailParams::new(from : String, to : Array[String], subject : String, html? : String, text? : String, cc? : Array[String], bcc? : Array[String], reply_to? : Array[String], attachments? : Array[Attachment], tags? : Array[(String, String)], scheduled_at? : String) -> SendEmailParams

    SendEmailParams::to_json

    UpdateEmailParams

    pub struct UpdateEmailParams {
    scheduled_at : String
    }

    Parameters accepted by Resend PATCH /emails/:id.

    UpdateEmailParams::new

    fn UpdateEmailParams::new(scheduled_at : String) -> UpdateEmailParams