fn encode_addresses(ArrayView[Address], utf8? : Bool) -> String
01
The message itself
Addresses, ordered headers, a tree of parts, and a builder that picks the right nesting for you.
fn format_date(@moondate.Moment) -> String
fn parse_address(StringView) -> Address raise Refused
fn parse_addresses(StringView) -> Array[Address] raise Refused
fn parse_date(StringView) -> @moondate.Moment raise Refused
enum Address { Mailbox(Mailbox); Group(name~ : String, members~ : Array[Mailbox]) }
fn Address::encode(Self, utf8? : Bool) -> String
fn Address::mailboxes(Self) -> Array[Mailbox]
struct Attachment
name : Stringmedia_type : Stringdata : Bytesinline : Boolid : String?
fn Attachment::new(String, Bytes, media_type? : String, inline? : Bool, id? : String) -> Self
enum Body { Leaf(Bytes); Multipart(Array[Message]); Embedded(Message) }
struct Headers // private fields
fn Headers::add(Self, String, String) -> Unit raise Refused
fn Headers::all(Self, StringView) -> Array[String]
fn Headers::get(Self, StringView) -> String?
fn Headers::iter(Self) -> Iter[(String, String)]
fn Headers::length(Self) -> Int32
fn Headers::new() -> Self
fn Headers::remove(Self, StringView) -> Unit
fn Headers::set(Self, String, String) -> Unit raise Refused
struct Mailbox { name : String?; address : String }
fn Mailbox::encode(Self, utf8? : Bool) -> String
fn Mailbox::new(String, name? : String) -> Self
struct Message { headers : Headers; body : Body }
fn Message::addresses(Self, StringView) -> Array[Address] raise Refused
fn Message::attachments(Self) -> Array[Attachment]
fn Message::bcc(Self) -> Array[Address] raise Refused
fn Message::cc(Self) -> Array[Address] raise Refused
fn Message::content_type(Self) -> @moonmime.MediaType
fn Message::date(Self) -> @moondate.Moment? raise Refused
fn Message::encode(Self, utf8? : Bool) -> Bytes
fn Message::from(Self) -> Array[Mailbox] raise Refused
fn Message::html(Self, charsets? : Map[String, &@moonmime.Charset]) -> String? raise Refused
fn Message::message_id(Self) -> String?
fn Message::new(from~ : Mailbox, to~ : Array[Address], subject~ : String, text? : String, html? : String, attachments? : Array[Attachment], cc? : Array[Address], date? : @moondate.Moment, message_id? : String) -> Self raise Refused
fn Message::parse(BytesView, strict? : Bool) -> Self raise Refused
fn Message::reply_to(Self) -> Array[Address] raise Refused
fn Message::subject(Self) -> String?
fn Message::text(Self, charsets? : Map[String, &@moonmime.Charset]) -> String? raise Refused
fn Message::to(Self) -> Array[Address] raise Refused
suberror Refused
Header(at~ : Int32)Address(at~ : Int32)Boundary(at~ : Int32)Encoding(at~ : Int32)Charset(name~ : String)Date
02
Sign it, then check the claim
One signature over the headers you name and the body you hash, and a verdict for every signature a letter carries.
fn sign(BytesView, Key, domain~ : String, selector~ : String, headers? : Array[String], header_canon? : Canon, body_canon? : Canon, timestamp? : Int64, expire? : Int64, length? : Bool, identity? : String) -> String raise Refused
let signed : Array[String]
fn verify(BytesView, (String) -> String? raise, now~ : Int64) -> Array[Verdict]
enum Canon { Simple; Relaxed }
enum Key { Rsa(@rsa.PrivateKey); Ed25519(@ed25519.PrivateKey) }
enum Verdict
Pass(domain~ : String, selector~ : String)Fail(domain~ : String, reason~ : String)PermError(String)TempError(String)
suberror Refused { Missing(String); Malformed(String) }
03
Mechanisms, as a trait
Six mechanisms behind one trait, so a mechanism written once works on all three protocols.
fn prep(StringView, unassigned? : Bool) -> String raise Refused
fn respond(&Mechanism, StringView) -> String raise Refused
struct CramMd5 // private fields
impl Mechanism for CramMd5
fn CramMd5::finish(Self) -> Unit raise Refused
fn CramMd5::name(Self) -> String
fn CramMd5::new(String, String) -> Self
fn CramMd5::start(Self) -> Bytes?
fn CramMd5::step(Self, BytesView) -> Bytes raise Refused
struct Login // private fields
impl Mechanism for Login
fn Login::finish(Self) -> Unit raise Refused
fn Login::name(Self) -> String
fn Login::new(String, String) -> Self
fn Login::start(Self) -> Bytes?
fn Login::step(Self, BytesView) -> Bytes raise Refused
struct OAuthBearer // private fields
impl Mechanism for OAuthBearer
fn OAuthBearer::error(Self) -> String?
fn OAuthBearer::finish(Self) -> Unit raise Refused
fn OAuthBearer::name(Self) -> String
fn OAuthBearer::new(String, username? : String, host? : String, port? : Int32) -> Self
fn OAuthBearer::start(Self) -> Bytes?
fn OAuthBearer::step(Self, BytesView) -> Bytes raise Refused
struct Plain // private fields
impl Mechanism for Plain
fn Plain::finish(Self) -> Unit raise Refused
fn Plain::name(Self) -> String
fn Plain::new(String, String, authzid? : String) -> Self
fn Plain::start(Self) -> Bytes?
fn Plain::step(Self, BytesView) -> Bytes raise Refused
struct ScramSha256 // private fields
impl Mechanism for ScramSha256
fn ScramSha256::finish(Self) -> Unit raise Refused
fn ScramSha256::name(Self) -> String
fn ScramSha256::new(String, String, nonce~ : String) -> Self raise Refused
fn ScramSha256::start(Self) -> Bytes?
fn ScramSha256::step(Self, BytesView) -> Bytes raise Refused
struct XOAuth2 // private fields
impl Mechanism for XOAuth2
fn XOAuth2::error(Self) -> String?
fn XOAuth2::finish(Self) -> Unit raise Refused
fn XOAuth2::name(Self) -> String
fn XOAuth2::new(String, String) -> Self
fn XOAuth2::start(Self) -> Bytes?
fn XOAuth2::step(Self, BytesView) -> Bytes raise Refused
trait Mechanism
fn name(Self) -> Stringfn start(Self) -> Bytes?fn step(Self, BytesView) -> Bytes raise Refusedfn finish(Self) -> Unit raise Refused = _
suberror Refused
Prep(Char)Malformed(String)Server(String)Signature
04
Both ends of the relay
A client that paces commands by the extensions a server announced, and a server that answers the protocol itself.
let limits : Limits
enum Body { SevenBit; EightBit; Binary }
struct Client // private fields
fn Client::auth(Self, &@sasl.Mechanism) -> Unit raise Refused
fn Client::bdat(Self, BytesView, last? : Bool) -> Unit raise Refused
fn Client::data(Self, BytesView) -> Unit raise Refused
fn Client::ehlo(Self, String) -> Unit raise Refused
fn Client::expn(Self, String) -> Unit raise Refused
fn Client::extensions(Self) -> Map[String, String]
fn Client::feed(Self, BytesView) -> Array[Event] raise Refused
fn Client::has(Self, String) -> Bool
fn Client::helo(Self, String) -> Unit raise Refused
fn Client::help(Self, topic? : String) -> Unit raise Refused
fn Client::mail(Self, String, size? : Int32, body? : Body, utf8? : Bool, ret? : Ret, envid? : String, auth? : String) -> Unit raise Refused
fn Client::new(lmtp? : Bool, limits? : Limits) -> Self
fn Client::next_write(Self) -> Bytes?
fn Client::noop(Self) -> Unit
fn Client::pending(Self) -> Int32
fn Client::quit(Self) -> Unit
fn Client::rcpt(Self, String, notify? : Array[Notify], orcpt? : String) -> Unit raise Refused
fn Client::rset(Self) -> Unit
fn Client::send(Self, String, Array[String], BytesView) -> Unit raise Refused
fn Client::starttls(Self) -> Unit raise Refused
fn Client::vrfy(Self, String) -> Unit raise Refused
enum Command
GreetingHello(String)StartTlsAuth(String)Mail(String)Rcpt(String)DataBdat(Int32)Delivery(String)RsetNoopVrfy(String)Expn(String)HelpQuit
enum Event { Reply(Command, Reply); Sasl(@sasl.Refused) }
struct Limits { line : Int32; recipients : Int32; size : Int32 }
fn Limits::new(line? : Int32, recipients? : Int32, size? : Int32) -> Self
enum Notify { Success; Failure; Delay }
struct Reply { code : Int32; lines : Array[String] }
fn Reply::encode(Self) -> Bytes
fn Reply::is_ok(Self) -> Bool
fn Reply::new(Int32, StringView) -> Self
fn Reply::status(Self) -> String?
fn Reply::text(Self) -> String
enum Request
Hello(String, extended~ : Bool)StartTlsAuth(String, Bytes?)Response(Bytes)Mail(String, Map[String, String?])Rcpt(String, Map[String, String?])Data(Bytes)Vrfy(String)Expn(String)RsetQuit
enum Ret { Full; Headers }
struct Server // private fields
fn Server::accept(Self) -> Unit raise Refused
fn Server::challenge(Self, BytesView) -> Unit raise Refused
fn Server::closed(Self) -> Bool
fn Server::feed(Self, BytesView) -> Unit
fn Server::new(String, lmtp? : Bool, starttls? : Bool, auth? : Array[String], greeting? : String, limits? : Limits) -> Self
fn Server::next(Self) -> Request?
fn Server::next_write(Self) -> Bytes?
fn Server::reply(Self, Int32, String) -> Unit raise Refused
suberror Refused
Malformed(String)Exceeded(limit~ : Int32, got~ : Int32)Order(String)Unsupported(String)
05
Stay in the mailbox
IMAP4rev2 with IDLE and CONDSTORE. Every command gets a tag and every response comes back decoded.
let limits : Limits
struct Client // private fields
fn Client::append(Self, String, BytesView, flags? : Array[String], date? : String) -> Int32 raise Refused
fn Client::authenticate(Self, &@sasl.Mechanism) -> Int32 raise Refused
fn Client::capabilities(Self) -> Array[String]
fn Client::capability(Self) -> Int32
fn Client::close(Self) -> Int32 raise Refused
fn Client::copy(Self, String, String, uid? : Bool) -> Int32 raise Refused
fn Client::create(Self, String) -> Int32 raise Refused
fn Client::delete(Self, String) -> Int32 raise Refused
fn Client::done(Self) -> Unit raise Refused
fn Client::enable(Self, Array[String]) -> Int32 raise Refused
fn Client::examine(Self, String, condstore? : Bool) -> Int32 raise Refused
fn Client::expunge(Self) -> Int32 raise Refused
fn Client::feed(Self, BytesView) -> Array[Event] raise Refused
fn Client::fetch(Self, String, String, uid? : Bool, changedsince? : Int64) -> Int32 raise Refused
fn Client::has(Self, String) -> Bool
fn Client::id(Self, Array[(String, String)]) -> Int32 raise Refused
fn Client::idle(Self) -> Int32 raise Refused
fn Client::list(Self, String, String) -> Int32 raise Refused
fn Client::login(Self, String, String) -> Int32 raise Refused
fn Client::logout(Self) -> Int32
fn Client::move_to(Self, String, String, uid? : Bool) -> Int32 raise Refused
fn Client::namespaces(Self) -> Int32 raise Refused
fn Client::new(limits? : Limits) -> Self
fn Client::next_write(Self) -> Bytes?
fn Client::noop(Self) -> Int32
fn Client::pending(Self) -> Int32
fn Client::rename(Self, String, String) -> Int32 raise Refused
fn Client::search(Self, String, uid? : Bool) -> Int32 raise Refused
fn Client::select(Self, String, condstore? : Bool) -> Int32 raise Refused
fn Client::starttls(Self) -> Int32 raise Refused
fn Client::state(Self) -> State
fn Client::status(Self, String, Array[String]) -> Int32 raise Refused
fn Client::store(Self, String, String, uid? : Bool, unchangedsince? : Int64) -> Int32 raise Refused
fn Client::subscribe(Self, String) -> Int32 raise Refused
fn Client::uid_expunge(Self, String) -> Int32 raise Refused
fn Client::unselect(Self) -> Int32 raise Refused
fn Client::unsubscribe(Self, String) -> Int32 raise Refused
enum Code
AlertCapability(Array[String])PermanentFlags(Array[String])ReadOnlyReadWriteTryCreateUidNext(Int64)UidValidity(Int64)HighestModseq(Int64)NoModseqModified(String)AppendUid(Int64, String)CopyUid(Int64, String, String)ClosedOther(String, String?)
enum Event
Data(Response)Done(Int32, Status, Code?, String)Sasl(@sasl.Refused)
struct Limits { line : Int32; literal : Int32 }
fn Limits::new(line? : Int32, literal? : Int32) -> Self
enum Response
Condition(String, Code?, String)Capability(Array[String])Enabled(Array[String])Flags(Array[String])Exists(Int64)Expunge(Int64)List(flags~ : Array[String], delimiter~ : String?, name~ : String)Status(String, Map[String, Int64])Esearch(tag~ : String?, uid~ : Bool, items~ : Map[String, Value])Search(Array[Int64], modseq~ : Int64?)Fetch(Int64, Map[String, Value])Namespace(Value, Value, Value)Other(String, Array[Value])
enum State
NotAuthenticatedAuthenticatedSelected(String)Logout
enum Status { Ok; No; Bad }
enum Value
NilNumber(Int64)Atom(String)Text(Bytes)List(Array[Value])
fn Value::list(Self) -> Array[Self]?
fn Value::number(Self) -> Int64?
fn Value::text(Self) -> String?
suberror Refused
Malformed(String)Exceeded(limit~ : Int32, got~ : Int32)Order(String)Unsupported(String)
06
Pick up and go
The small one: a greeting, a login, a list, and the messages themselves.
let limits : Limits
struct Client // private fields
fn Client::apop(Self, String, String) -> Unit raise Refused
fn Client::auth(Self, &@sasl.Mechanism) -> Unit raise Refused
fn Client::capa(Self) -> Unit
fn Client::capabilities(Self) -> Array[String]
fn Client::dele(Self, Int32) -> Unit raise Refused
fn Client::feed(Self, BytesView) -> Array[Event] raise Refused
fn Client::has(Self, String) -> Bool
fn Client::list(Self, message? : Int32) -> Unit raise Refused
fn Client::new(limits? : Limits) -> Self
fn Client::next_write(Self) -> Bytes?
fn Client::noop(Self) -> Unit raise Refused
fn Client::pass(Self, String) -> Unit raise Refused
fn Client::pending(Self) -> Int32
fn Client::quit(Self) -> Unit
fn Client::retr(Self, Int32) -> Unit raise Refused
fn Client::rset(Self) -> Unit raise Refused
fn Client::stat(Self) -> Unit raise Refused
fn Client::state(Self) -> State
fn Client::stls(Self) -> Unit raise Refused
fn Client::top(Self, Int32, Int32) -> Unit raise Refused
fn Client::uidl(Self, message? : Int32) -> Unit raise Refused
fn Client::user(Self, String) -> Unit raise Refused
enum Command
GreetingUser(String)PassApop(String)Auth(String)StlsCapaStatList(Int32?)Uidl(Int32?)Retr(Int32)Top(Int32, Int32)Dele(Int32)RsetNoopQuit
enum Event { Reply(Command, Reply); Sasl(@sasl.Refused) }
struct Limits { line : Int32; message : Int32 }
fn Limits::new(line? : Int32, message? : Int32) -> Self
enum Reply
Greeting(text~ : String, timestamp~ : String?)Ok(String)Err(String)Stat(count~ : Int32, size~ : Int64)Listing(Array[(Int32, Int64)])Uids(Array[(Int32, String)])Content(Bytes)Capabilities(Array[String])
enum State { Authorization; Transaction; Update }
suberror Refused
Malformed(String)Exceeded(limit~ : Int32, got~ : Int32)Order(String)Unsupported(String)
07
The protocol that fits in a page
JMAP as documents: the session, the request envelope, the answers. No socket, which is how a browser can speak it.
fn created_id(Response, String, String) -> String raise Refused
fn discovery_url(String) -> String
fn email_ids(Response, String) -> Array[String] raise Refused
fn emails(Response, String) -> Array[Email] raise Refused
fn identities(Response, String) -> Array[Identity] raise Refused
fn mailboxes(Response, String) -> Array[Mailbox] raise Refused
fn resolve(String, String) -> String raise Refused
struct Call { name : String; args : Json; tag : String }
struct Draft
from : String?to : Array[String]cc : Array[String]bcc : Array[String]subject : Stringtext : String
fn Draft::new(to~ : Array[String], subject~ : String, text~ : String) -> Self
struct Email
id : Stringsubject : Stringfrom : String?received_at : String?preview : Stringunread : Bool
struct Identity { id : String; name : String?; email : String }
struct Invocation { name : String; args : Json; tag : String }
struct Mailbox
id : Stringname : Stringrole : String?parent_id : String?total : Int32unread : Int32
struct Request { capabilities : Array[String]; calls : Array[Call] }
fn Request::call(Self, String, Json) -> String
fn Request::email_destroy(Self, String, String) -> String
fn Request::email_get(Self, String, Array[String], Array[String]) -> String
fn Request::email_mark_seen(Self, String, String, Bool) -> String
fn Request::email_move(Self, String, String, String) -> String
fn Request::email_query(Self, String, String, Int32) -> String
fn Request::email_set_draft(Self, String, String, Draft) -> String
fn Request::email_set_update(Self, String, String, Array[(String, Json)]) -> String
fn Request::email_submission_set(Self, String, String, String) -> String
fn Request::identity_get(Self, String) -> String
fn Request::mailbox_get(Self, String) -> String
fn Request::new() -> Self
fn Request::to_json(Self) -> Json
fn Request::to_string(Self) -> String
fn Request::using_capability(Self, String) -> Unit
struct Response { invocations : Array[Invocation]; session_state : String }
fn Response::answer(Self, String) -> Invocation raise Refused
fn Response::invocation(Self, String) -> Invocation?
fn Response::parse(String) -> Self raise Refused
struct Session
username : String?account_id : Stringapi_url : Stringdownload_url : String?upload_url : String?event_source_url : String?state : String
fn Session::parse(String, String) -> Self raise Refused
suberror Refused
Malformed(at~ : String)NotAnObjectMissing(name~ : String)WrongType(name~ : String)BadUrl(url~ : String)Failed(call~ : String, kind~ : String, description~ : String)
08
The one that touches a socket
The same state machines over TCP and TLS, including the upgrade to STARTTLS mid-conversation.
struct Imap // private fields
async fn Imap::authenticate(Self, &@sasl.Mechanism) -> Unit
fn Imap::client(Self) -> @imap.Client
async fn Imap::connect(String, Security, port? : Int32, limits? : @imap.Limits, now? : Int64) -> Self
async fn Imap::login(Self, String, String) -> Unit
async fn Imap::logout(Self) -> Unit
async fn Imap::read(Self) -> Array[@imap.Event]
async fn Imap::run(Self) -> Array[@imap.Event]
async fn Imap::wait(Self, Int32) -> Reply
struct Pop3 // private fields
async fn Pop3::auth(Self, &@sasl.Mechanism) -> Unit
fn Pop3::client(Self) -> @pop3.Client
async fn Pop3::connect(String, Security, port? : Int32, limits? : @pop3.Limits, now? : Int64) -> Self
async fn Pop3::list(Self) -> Array[(Int32, Int64)]
async fn Pop3::login(Self, String, String) -> Unit
async fn Pop3::quit(Self) -> Unit
async fn Pop3::retr(Self, Int32) -> Bytes
async fn Pop3::run(Self) -> Array[@pop3.Event]
struct Reply
data : Array[@imap.Response]status : @imap.Statuscode : @imap.Code?text : String
enum Security { Tls(@tls.Client); StartTls(@tls.Client); Clear }
struct Smtp // private fields
async fn Smtp::auth(Self, &@sasl.Mechanism) -> Unit
fn Smtp::client(Self) -> @smtp.Client
async fn Smtp::connect(String, Security, port? : Int32, hello? : String, lmtp? : Bool, limits? : @smtp.Limits, now? : Int64) -> Self
async fn Smtp::quit(Self) -> Unit
async fn Smtp::run(Self) -> Array[@smtp.Event]
async fn Smtp::send(Self, String, Array[String], BytesView) -> Array[(String, @smtp.Reply)]
suberror Rejected
Reply(@smtp.Command, @smtp.Reply)Done(@imap.Status, @imap.Code?, String)Err(@pop3.Command, String)Sasl(@sasl.Refused)Closed