Files
openmail/spike/mimecheck/main.go
T
karti-ai 551739baa3 Milestone 1 scaffold: binary skeleton, schema, deploy, Mox spike
- Confirmed core bet via spike/mimecheck: mox/message parses a messy multipart
  message standalone (envelope, MIME tree, attachment, body) with only an
  io.ReaderAt — no mox store/config. Option B is viable.
- cmd/openmail single binary: serve|migrate|smtpd|sender|version subcommands.
- internal/api: chi router, constant-time bearer auth, /healthz, stubbed v0
  AgentMail-shaped routes (501 until core services land).
- internal/store: pgxpool + embedded, idempotent, tracked SQL migrations.
- internal/store/migrations/0001_init.sql: full native schema (pods, inboxes,
  threads, messages, attachments, drafts, api_keys, webhooks, outbox, events,
  domains) with FTS + GIN indexes. Validated end-to-end against Postgres 16.
- deploy/: docker-compose (postgres+minio+openmail+caddy), Caddyfile, DNS.md
  (MX/SPF/DKIM/DMARC/DANE/MTA-STS). Makefile, .gitignore.
- Verified: go vet clean; build OK; health/auth smoke tests; migrate idempotent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 12:16:51 -07:00

124 lines
3.8 KiB
Go

// Command mimecheck is a throwaway feasibility spike for OpenMail's core
// architectural bet (see ARCHITECTURE.md §0, §9): can Mox's `message` package
// parse a raw RFC 5322 message standalone — outside Mox's store/config/global
// state — given only an io.ReaderAt?
//
// If this builds and runs without dragging in mox-/store/config, "embed Mox as
// a library" (Option B) is viable. Run: go run ./spike/mimecheck
package main
import (
"bytes"
"fmt"
"log/slog"
"os"
"strings"
"github.com/mjl-/mox/message"
)
// A deliberately messy real-world-ish message: multipart/alternative (text+html)
// with a reply quote, threading headers, and an attachment part.
const sampleEML = "From: Alice <alice@example.com>\r\n" +
"To: agent@openmail.test\r\n" +
"Subject: Re: invoice #42\r\n" +
"Message-ID: <reply-2@example.com>\r\n" +
"In-Reply-To: <orig-1@openmail.test>\r\n" +
"References: <orig-1@openmail.test>\r\n" +
"Date: Mon, 21 Jun 2026 12:00:00 +0000\r\n" +
"MIME-Version: 1.0\r\n" +
"Content-Type: multipart/mixed; boundary=\"OUTER\"\r\n" +
"\r\n" +
"--OUTER\r\n" +
"Content-Type: multipart/alternative; boundary=\"INNER\"\r\n" +
"\r\n" +
"--INNER\r\n" +
"Content-Type: text/plain; charset=utf-8\r\n" +
"\r\n" +
"Thanks, looks good to me.\r\n" +
"\r\n" +
"On Mon, Alice wrote:\r\n" +
"> here is the invoice\r\n" +
"--INNER\r\n" +
"Content-Type: text/html; charset=utf-8\r\n" +
"\r\n" +
"<p>Thanks, looks good to me.</p>\r\n" +
"--INNER--\r\n" +
"--OUTER\r\n" +
"Content-Type: application/pdf; name=\"invoice.pdf\"\r\n" +
"Content-Disposition: attachment; filename=\"invoice.pdf\"\r\n" +
"Content-Transfer-Encoding: base64\r\n" +
"\r\n" +
"JVBERi0xLjQK\r\n" +
"--OUTER--\r\n"
func main() {
log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelWarn}))
r := bytes.NewReader([]byte(sampleEML))
// EnsurePart fully parses header + walks the MIME tree given the size.
p, err := message.EnsurePart(log, false, r, int64(len(sampleEML)))
if err != nil {
fmt.Fprintf(os.Stderr, "FAIL: parse error: %v\n", err)
os.Exit(1)
}
fmt.Println("=== mox/message standalone parse OK ===")
if p.Envelope != nil {
e := p.Envelope
fmt.Printf("Subject: %s\n", e.Subject)
fmt.Printf("MessageID: %s\n", e.MessageID)
fmt.Printf("InReplyTo: %s\n", e.InReplyTo)
if len(e.From) > 0 {
fmt.Printf("From: %s@%s\n", e.From[0].User, e.From[0].Host)
}
}
fmt.Printf("Top type: %s/%s\n", p.MediaType, p.MediaSubType)
// Walk the tree, summarizing each leaf — proves multipart traversal works.
var textBody string
var attachments int
var walk func(parts []message.Part, depth int)
walk = func(parts []message.Part, depth int) {
for i := range parts {
sp := &parts[i]
indent := strings.Repeat(" ", depth)
disp := ""
if sp.ContentDisposition != nil {
disp = " [" + *sp.ContentDisposition + "]"
}
fmt.Printf("%s- %s/%s%s\n", indent, sp.MediaType, sp.MediaSubType, disp)
if sp.ContentDisposition != nil && strings.EqualFold(*sp.ContentDisposition, "attachment") {
attachments++
}
if sp.MediaType == "TEXT" && sp.MediaSubType == "PLAIN" && textBody == "" {
if buf, rerr := readPart(sp); rerr == nil {
textBody = string(buf)
}
}
walk(sp.Parts, depth+1)
}
}
fmt.Println("Structure:")
walk(p.Parts, 1)
fmt.Printf("\nAttachments found: %d\n", attachments)
fmt.Printf("text/plain body:\n%s\n", indentBlock(textBody))
fmt.Println("=== SPIKE PASSED: Mox message parsing is usable standalone ===")
}
func readPart(p *message.Part) ([]byte, error) {
rd := p.Reader()
var b bytes.Buffer
_, err := b.ReadFrom(rd)
return b.Bytes(), err
}
func indentBlock(s string) string {
out := []string{}
for _, line := range strings.Split(strings.TrimRight(s, "\r\n"), "\n") {
out = append(out, " | "+strings.TrimRight(line, "\r"))
}
return strings.Join(out, "\n")
}