Summary#
A mail message is BYTES — RFC 5322 text with MIME parts inside. These calls turn those bytes into data an app can read, data back into bytes a provider sends, and move them to and from a mail host:
MailMessage mail = JsonSerializer.Deserialize<MailMessage>(Mime.Parse(raw)); // what Osysharp.Mail's ParseMail does
byte[] raw = Mime.Build(JsonSerializer.Serialize(mail)); // …and RawOfMost apps never write them: Osysharp.Mail wraps the first two as ParseMail / RawOf, and
Osysharp.Mail.Imap's ImapMailbox is built on the other two. They are here for the kit you write next.
Signature#
string Mime.Parse(byte[] raw) // → JSON: From, To, Cc, Bcc, ReplyTo, Subject, Date, MessageId, InReplyTo,
// References, Text, Html, Headers, Attachments[{FileName, Content (base64),
// ContentType, ContentId}], FailedMessageIds (a bounce report only)
byte[] Mime.Build(string json) // the same shape (any letter case) → RFC 5322 bytes
// `use Osysharp.Http;` — outbound network, so the same opt-in as Http.*
string Imap.Fetch(string host, int port, string username, string credential, bool oauth,
string folder, string cursor, int max)
string Smtp.Send(string host, int port, string username, string credential, bool oauth, byte[] message)Description#
Parse reads everything a mail carries#
Mime.Parse answers the message in MailMessage's own shape, so JsonSerializer.Deserialize<MailMessage> is the
whole job. The bodies come both ways a client sends them (Text, Html); Headers keeps every header's first value;
each attachment carries its bytes as base64 and its ContentId when the HTML places it inline. Date is null when the
message has none — never "now", which would be a fact the sender did not state.
A bounce says which message failed. A delivery-status report (RFC 3464) names the message it returns in its own
attached part, which is the one place a bounce reliably carries it — many carry no In-Reply-To. FailedMessageIds
holds those ids, for a FAILURE report only, so a receiver can mark exactly that delivery failed.
Build writes what a provider sends#
Mime.Build takes the same JSON and answers the bytes: multipart when there are attachments or both bodies, every
header the message carries, its Message-ID and threading headers as given. It stamps no Date of its own — a build
is deterministic, so the same message builds to the same bytes, and a durable retry sends what the first try would have.
Imap.Fetch and Smtp.Send move bytes, guarded#
Both run over the host's mail transport, which refuses a private or loopback address exactly as Http.* does, requires
TLS (993/465) or upgrades with STARTTLS, and never logs a credential. The credential is an app password, or — with
oauth: true — an OAuth access token (XOAUTH2); either way it is a secret sink: pass a Secret.X or a setting
filled from one, never a literal. A host that refuses the login, is down or is not allowed is an
ExternalServiceException naming the host, never the credential.
Imap.Fetchanswers JSON{ "Cursor": "…", "Notice": "…", "Messages": [{ "Uid": 42, "Raw": "<base64>" }] }: the messages aftercursorinfolder, oldest first, at mostmax, read with PEEK (nothing is marked read). An EMPTY cursor answers no mail and the cursor of NOW — a mailbox connected today does not import its history. A cursor carries the folder'sUIDVALIDITY; when the server re-creates the folder, the fetch starts again from now andNoticesays so in a sentence an admin can read.Smtp.Sendsubmits the bytes as they are — theirFrom, recipients andMessage-ID— and answers the server's reply.
A test stubs either like any effect: Imap.Fetch.Stub((host, port, user, credential, oauth, folder, cursor, max) => …).
Examples#
Read a received message's subject and whether it is a bounce:
class Seen {
public string Subject = "";
public List<string> FailedMessageIds = [];
}
string SubjectOf(byte[] raw) {
return JsonSerializer.Deserialize<Seen>(Mime.Parse(raw)).Subject;
}
bool IsBounce(byte[] raw) {
return JsonSerializer.Deserialize<Seen>(Mime.Parse(raw)).FailedMessageIds.Count > 0;
}Build a plain message's bytes:
class Outgoing {
public string From = "";
public List<string> To = [];
public string Subject = "";
public string Text = "";
}
byte[] Hello(string to) {
return Mime.Build(JsonSerializer.Serialize(new Outgoing {
From = "Acme <[email protected]>", To = [to], Subject = "Hello", Text = "Welcome aboard.",
}));
}See also#
- The Mail kit — send a message, keep a record, pick a sender —
MailMessage, the outbox,ParseMailandRawOf - Receiving mail — watched mailboxes (Gmail, Microsoft 365, IMAP) — watched mailboxes;
ImapMailboxis built onImap.FetchandSmtp.Send - Http.* — the same egress guard, for HTTP
- Zip.List — what an archive holds — what an attached archive holds