Documentation
¶
Overview ¶
Package mail holds mailbox business logic that is independent of HTTP: the send guardrail (external recipients fall back to a draft for human review) and construction of Graph message payloads.
Index ¶
- func BuildInlineAttachment(img InlineImage) ([]byte, error)
- func BuildMessage(m Message) ([]byte, error)
- func BuildReplyBodyPatch(body Body, quoted string) ([]byte, error)
- func BuildReplyComment(body Body) ([]byte, error)
- func BuildSendMail(m Message) ([]byte, error)
- func DecodeAttachment(attachmentJSON []byte) (string, []byte, error)
- func LooksLikeHTML(body string) bool
- func ReplyRecipients(messageJSON []byte, replyAll bool) ([]string, error)
- func SpliceIntoQuoted(reply, quoted string) string
- func TextToHTML(text string) string
- type Body
- type InlineImage
- type Message
- type SendAction
- type SendPlan
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuildInlineAttachment ¶ added in v0.2.2
func BuildInlineAttachment(img InlineImage) ([]byte, error)
BuildInlineAttachment renders the Graph fileAttachment payload that adds an inline image to an existing draft (POST /messages/{id}/attachments).
func BuildMessage ¶
BuildMessage renders a Graph message object (used to create a draft via POST /messages).
func BuildReplyBodyPatch ¶ added in v0.2.1
BuildReplyBodyPatch renders the PATCH payload that fills a reply draft created via createReply / createReplyAll. That draft is an HTML message (it carries the quoted original), so the body is always written as HTML.
quoted is the body the draft already has. The reply is spliced in above it instead of replacing it — that quote IS the thread, including its inline images. Pass "" only when there is demonstrably nothing to keep.
func BuildReplyComment ¶
BuildReplyComment renders the {comment} payload for reply / replyAll. Graph splices the comment into the HTML body of the reply, so a plain-text body is converted first — otherwise every line break is lost on the way out.
func BuildSendMail ¶
BuildSendMail renders a Graph sendMail payload ({message, saveToSentItems}).
func DecodeAttachment ¶
DecodeAttachment extracts the file name and decoded bytes from a Graph fileAttachment. It errors for attachments without contentBytes (item or reference attachments cannot be downloaded this way).
func LooksLikeHTML ¶ added in v0.2.1
LooksLikeHTML reports whether a body was written as HTML. Only the opening of the body decides: prose never *starts* with a tag, so this cannot misfire on a mail that merely mentions one ("der Platzhalter <name> …").
It is a safety net, not the interface — `--html` states the intent explicitly. But a body that opens with `<html><body>` has exactly one plausible reading, and sending it as text/plain shows the recipient the tags.
func ReplyRecipients ¶
ReplyRecipients extracts the addresses a reply would go to: the sender for a plain reply, plus the original To/Cc (deduplicated) for reply-all. These feed the send guardrail so replies are subject to the same send_allow policy.
func SpliceIntoQuoted ¶ added in v0.2.2
SpliceIntoQuoted puts a reply body at the TOP of the quoted original that a createReply / createReplyAll draft already carries — the shape an Outlook reply has: the new text first, the original thread below it, with its inline images, signatures and formatting untouched.
Why this exists: the draft Graph hands back is not empty. It already contains the quoted original. Writing the reply as the *whole* body — what this CLI did until 2026-08-05 — REPLACES that quote, so the thread the reader expects under the answer is gone, and every inline image with it. Reported by Gerald Katterbauer as "mal ohne Thread, mal ohne Bilder" (GC 975b01a9).
The quote is an HTML document, so the reply cannot simply be prepended: anything before <html> lands outside the rendered body. The insertion point is therefore the first <body …> tag. A bare fragment without <body> is concatenated, which renders the same.
func TextToHTML ¶ added in v0.2.1
TextToHTML renders a plain-text mail body as HTML that keeps the author's layout: a blank line starts a new paragraph, a single newline becomes a line break. Everything else is escaped, so text that happens to contain angle brackets stays literal.
Why this exists: Graph's reply/replyAll `comment` is injected into the HTML body of the reply, and a reply draft handed back by createReply is an HTML message too. Plain text placed there renders as one run-on paragraph — the newlines are simply insignificant in HTML.
Types ¶
type Body ¶ added in v0.2.1
Body is an outgoing mail body plus how its author wrote it. It travels through the backend seam unchanged, because whether plain text needs converting depends on the sink: Graph's reply path is HTML-only, EWS types its bodies explicitly and keeps plain text as plain text.
type InlineImage ¶ added in v0.2.2
type InlineImage struct {
Name string // file name shown in the attachment list
ContentID string // the cid: the body references
ContentType string // image/png, image/jpeg, …
Data []byte
}
InlineImage is a picture that travels inside the message body, referenced from the HTML as <img src="cid:CONTENT-ID">. Signature logos are the reason this exists: the quoted original brings its own images along, but the block the agent writes on top has none unless they are attached explicitly.
func NewInlineImage ¶ added in v0.2.2
func NewInlineImage(contentID, path string, data []byte) (InlineImage, error)
NewInlineImage builds an InlineImage from a content id and the file's bytes, deriving name and MIME type from the path.
type SendAction ¶
type SendAction int
SendAction is the outcome of the send guardrail.
const ( // SendDirect: every recipient is permitted, send immediately. SendDirect SendAction = iota // DraftOnly: at least one recipient is not in send_allow — create a draft // for human review instead of sending. DraftOnly )
func (SendAction) String ¶
func (a SendAction) String() string
type SendPlan ¶
type SendPlan struct {
Action SendAction
Blocked []string // recipients that forced a draft (empty for SendDirect)
}
SendPlan is the guardrail decision for a set of recipients.