Email for MoonBit: the message model, MIME and DKIM, and SMTP, LMTP, IMAP and POP3.
let message = @moonmail.Message::parse(raw[:])
message.subject() // encoded words already decoded
message.from() // the mailboxes, display names decoded
message.text() // the body a reader would show, by its charset
message.attachments() // name, type, octets, inline and Content-ID
let reply = @moonmail.Message::new(
from=@moonmail.Mailbox::new("alice@example.com", name="Alice"),
to=[Mailbox(@moonmail.Mailbox::new("bob@example.com"))],
subject="Re: lunch",
text="Noon works.",
attachments=[@moonmail.Attachment::new("map.png", png)],
)
let wire = reply.encode() // CRLF, folded, encoded words; reads back the samemoon run src/examples/tourlet pop = @pop3.Client::new()
pop.user("alice")
pop.pass("secret")
pop.retr(1)
// ... write pop.next_write(), feed the server's octets
for event in pop.feed(octets) {
if event is Reply(Retr(_), Content(message)) {
println(message.length())
}
}let smtp = @smtp.Client::new()
smtp.ehlo("client.example")
// ... write smtp.next_write(), feed the server's octets until the EHLO reply
smtp.send("alice@example.com", ["bob@example.com"], raw[:])
smtp.quit()
for event in smtp.feed(octets) {
match event {
Reply(Rcpt(to), reply) if !reply.is_ok() => println("refused: " + to)
_ => ()
}
}let server = @smtp.Server::new("mx.example", auth=["PLAIN"])
server.feed(octets)
while server.next() is Some(request) {
match request {
Rcpt(to, _) if !known(to) => server.reply(550, "5.1.1 No such user")
Data(message) => { store(message); server.accept() }
Rset | Quit => ()
_ => server.accept()
}
}
// write server.next_write() back to the clientlet request = @jmap.Request::new()
let tag = request.mailbox_get(account_id) // Mailbox/get
let body = request.to_string() // POST this to session.api_url
let answer = @jmap.Response::parse(reply)
for box in @jmap.mailboxes(answer, tag) {
println("\{box.name}: \{box.unread} unread of \{box.total}")
}flowchart LR
page["writing desk<br/>(a page, no socket)"]
desk["moonmail<br/>composes the MIME"]
jmap["JMAP server<br/>HTTPS · no helper"]
office["local post office<br/>127.0.0.1:12000"]
smtp["SMTP · IMAP · POP3"]
page --> desk
desk -->|"jmap.request"| jmap
desk -->|"compose"| office
office -->|"@moonmail/net"| smtplet smtp = @net.Smtp::connect("smtp.example.com", StartTls(tls))
smtp.auth(@sasl.Plain::new("alice", password))
let refused = smtp.send("alice@example.com", ["bob@example.com"], raw[:])
smtp.quit()
let imap = @net.Imap::connect("imap.example.com", Tls(tls))
imap.login("alice", password)
let inbox = imap.wait(imap.client().select("INBOX"))
let pop = @net.Pop3::connect("pop.example.com", Tls(tls))
pop.login("alice", password)
let first = pop.retr(1)// SCRAM never sends the password, and refuses a server that cannot prove it
// holds the salted key. The nonce is fresh random printable ASCII each time.
smtp.auth(@sasl.ScramSha256::new("alice", "secret", nonce~))
imap.authenticate(@sasl.OAuthBearer::new(token, username="alice@example.com"))let imap = @imap.Client::new()
let _ = imap.login("alice", "secret")
let _ = imap.select("INBOX")
let fetch = imap.fetch("1:*", "(FLAGS BODY.PEEK[HEADER])", uid=true)
// ... write imap.next_write(), feed the server's octets
for event in imap.feed(octets) {
match event {
Data(Fetch(n, items)) => println(n.to_string() + " " + items.length().to_string())
Done(tag, No, _, text) if tag == fetch => println("fetch failed: " + text)
_ => ()
}
}let options = @dkim.SignOptions::new(
domain="example.com",
selector="sel",
headers=["From", "To", "Subject", "Date"],
)
let signature = @dkim.sign(raw[:], private_key, options)
// "DKIM-Signature: " + signature is the header to add; verify() checks it back.// IMAP: ask about a mailbox without selecting it, wait for changes, and
// store flags only where nothing changed them since (CONDSTORE).
let _ = imap.status("INBOX", ["MESSAGES", "UIDNEXT", "UIDVALIDITY"])
let _ = imap.idle() // changes arrive as Data until done()
imap.done()
let _ = imap.store("1:5", "+FLAGS (\\Seen)", uid=true, unchangedsince=modseq)
// SMTP: chunk the body instead of using DATA, and ask for delivery status.
smtp.mail("alice@example.com", size=1234, body=Binary)
smtp.rcpt("bob@example.com", notify=[Success, Failure])
smtp.bdat(chunk[:], last=true) // LAST ends the transactionmoon clean && moon fmt && moon check --target all --deny-warn \
&& moon build --target all && moon test --target allfn Attachment::new(name : String, data : Bytes, media_type? : String, inline? : Bool, id? : String) -> AttachmentInstall
Download zipEmail for MoonBit: the message model, MIME and DKIM, and SMTP, LMTP, IMAP and POP3.