devmail

package
v0.3.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package devmail is the Dev Portal's mail catcher (ADR-0074): an SMTP server inside orb dev that keeps every message the app sends under .orb/portal/mail, parsed for the inbox screen. It speaks the SMTP the app's sender needs in development (no TLS, no authentication) and nothing more; it must never listen beyond the loopback interface.

Index

Constants

View Source
const (
	// MaxMessages is how many messages the inbox keeps; older ones are
	// deleted first.
	MaxMessages = 500
	// Dir is where the messages live, inside the app.
	Dir = ".orb/portal/mail"
)

Limits of the store.

View Source
const (
	// MaxMessageBytes is the largest message accepted.
	MaxMessageBytes = 16 << 20
)

Limits of a session.

Variables

View Source
var ErrNotFound = errors.New("no such message")

ErrNotFound reports a message ID the store doesn't have.

Functions

func Parse

func Parse(raw []byte) (Summary, Detail, error)

Parse reads a message: the summary for the list and the detail with its bodies, links and attachments.

Types

type Address

type Address struct {
	Name  string `json:"name,omitempty"`
	Email string `json:"email"`
}

Address is a parsed address.

type Attachment

type Attachment struct {
	Name        string `json:"name"`
	ContentType string `json:"content_type"`
	Size        int    `json:"size"`
}

Attachment describes a part with a file name.

type Detail

type Detail struct {
	Summary
	ReplyTo     []Address         `json:"reply_to,omitempty"`
	CC          []Address         `json:"cc,omitempty"`
	Headers     map[string]string `json:"headers"`
	Text        string            `json:"text"`
	HTML        string            `json:"html"`
	Links       []Link            `json:"links"`
	Attachment  []Attachment      `json:"attachment_list"`
	MessageID   string            `json:"message_id,omitempty"`
	Envelope    Envelope          `json:"envelope"`
	SourceBytes int64             `json:"source_bytes"`
}

Detail is a message with its bodies.

type Envelope

type Envelope struct {
	From string   `json:"from"`
	To   []string `json:"to"`
}

Envelope is what SMTP said, as opposed to the headers.

type Link struct {
	URL  string `json:"url"`
	Text string `json:"text,omitempty"`
}

Link is a URL found in the message.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server receives messages into a Store.

func NewServer

func NewServer(store *Store) *Server

NewServer returns a server storing into store.

func (*Server) Addr

func (s *Server) Addr() string

Addr is the listening address.

func (*Server) Close

func (s *Server) Close() error

Close stops listening and ends every session.

func (*Server) Listen

func (s *Server) Listen(addr string) error

Listen starts accepting on addr, which must be a loopback address. It returns once the listener is open; sessions run until Close.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store keeps messages as .eml files with a JSON summary next to each, and serves them to the portal. It is safe for concurrent use.

func Open

func Open(dir string) (*Store, error)

Open opens the store under the app directory dir, creating it, and reads the summaries it has.

func (*Store) Add

func (s *Store) Add(_ context.Context, from string, rcpts []string, raw []byte) (Summary, error)

Add stores a message received from from for rcpts.

func (*Store) Clear

func (s *Store) Clear() error

Clear removes every message.

func (*Store) Count

func (s *Store) Count() int

Count is how many messages the store keeps.

func (*Store) Delete

func (s *Store) Delete(id string) error

Delete removes one message.

func (*Store) Get

func (s *Store) Get(id string) (Detail, error)

Get returns a message with its bodies, marking it read.

func (*Store) List

func (s *Store) List(q string, limit int) ([]Summary, int)

List returns the messages newest first, those matching q (a case-insensitive substring of the subject, addresses or snippet) when q isn't empty, at most limit (default 100).

func (*Store) Source

func (s *Store) Source(id string) ([]byte, error)

Source returns the message as received.

func (*Store) Subscribe

func (s *Store) Subscribe() (<-chan Summary, func())

Subscribe returns a channel of new messages; stop with the returned function.

type Summary

type Summary struct {
	ID       string    `json:"id"`
	Time     time.Time `json:"time"`
	From     Address   `json:"from"`
	To       []Address `json:"to"`
	Subject  string    `json:"subject"`
	Snippet  string    `json:"snippet"`
	Size     int64     `json:"size"`
	HasHTML  bool      `json:"has_html"`
	HasText  bool      `json:"has_text"`
	Codes    []string  `json:"codes"`
	Category string    `json:"category,omitempty"`
	// Attachments counts them; the detail lists them.
	Attachments int  `json:"attachments"`
	Read        bool `json:"read"`
}

Summary is a message in the list.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL