inbox

package
v0.7.1 Latest Latest
Warning

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

Go to latest
Published: May 19, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package inbox watches an IMAP mailbox for job-application updates (confirmations, interview invites, rejections, offers) and proposes status changes the user confirms before they land. We never write to the mailbox — no flag changes, no deletes, no replies — so JobForge can't accidentally affect what the user sees in their normal mail client.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoPassword = errors.New("no IMAP password stored; run `jobforge inbox auth`")

ErrNoPassword signals the password file is missing. The CLI uses this to point users at `jobforge inbox auth`.

View Source
var ErrNotConfigured = errors.New("imap.host and imap.username are not set in config.json")

ErrNotConfigured signals config.json has no imap.host. The CLI uses this to point users at `jobforge inbox configure`.

Functions

func DeletePassword

func DeletePassword(path string) error

DeletePassword wipes the password file. Idempotent.

func LoadPassword

func LoadPassword(path string) (string, error)

LoadPassword reads the password back. Returns ErrNoPassword when the file isn't there so callers can prompt for `inbox auth`.

func SavePassword

func SavePassword(path, password string) error

SavePassword writes the IMAP password to path with restrictive permissions: 0o600 on Unix (meaningful), best-effort icacls inheritance-drop + grant-to-current-user on Windows. We never log or pretty-print the password.

func SaveState

func SaveState(path string, s State) error

SaveState writes State to path atomically. We use temp-and-rename like the rest of the storage so a crashed run never leaves the file in a partial state.

Types

type Client

type Client interface {
	Connect(ctx context.Context) error
	Close() error
	FetchSince(ctx context.Context, folder string, since uint32) ([]Message, error)
}

Client is the IMAP-level abstraction. Implementations open a connection in Connect and tear it down in Close. FetchSince returns every message with UID > since in the given folder; pass 0 to fetch all unseen.

func NewIMAPClient

func NewIMAPClient(settings config.IMAPSettings, password string) (Client, error)

NewIMAPClient builds an unconnected Client. Call Connect before any fetch. Settings.Host and Username must be non-empty.

type Message

type Message struct {
	UID       uint32
	From      string    // "Jane Doe <jane@acme.example>"
	Subject   string    // already MIME-decoded
	Date      time.Time // sent date, UTC
	BodyText  string    // up to ~8KB; longer bodies are truncated
	MessageID string    // RFC 5322 Message-ID for dedupe across polls
}

Message is one mail we plucked off the server. BodyText is the plain-text content after MIME decoding — HTML-only mails get best-effort plain-text extraction.

type State

type State struct {
	// LastUIDByFolder records the highest UID we've already fetched
	// per folder name. Server-assigned UIDs are monotonic within a
	// folder (UIDVALIDITY changes invalidate that — we don't handle
	// UIDVALIDITY rotation yet; on rotation, just delete this file).
	LastUIDByFolder map[string]uint32 `json:"last_uid_by_folder"`

	// ProcessedMessageIDs is a small set of RFC 5322 Message-IDs the
	// user has already approved/declined an action for. Belt-and-
	// braces dedupe in case server UIDs and our local state desync.
	// We cap the set at processedIDCap to keep the file small.
	ProcessedMessageIDs []string `json:"processed_message_ids,omitempty"`

	// LastPolledAt is informational, surfaced by `inbox status`.
	LastPolledAt time.Time `json:"last_polled_at,omitempty"`
}

State tracks where we left off so consecutive `inbox poll` runs don't re-process the same mail. Stored as JSON at Paths.InboxStateFile — small enough to hand-edit if needed.

func LoadState

func LoadState(path string) (State, error)

LoadState reads State from path. Returns an empty (zero-value) State if the file doesn't exist — first run is normal.

func (*State) AlreadyProcessed

func (s *State) AlreadyProcessed(messageID string) bool

AlreadyProcessed reports whether messageID has been recorded.

func (*State) MarkProcessed

func (s *State) MarkProcessed(messageID string)

MarkProcessed records messageID as already-handled. No-op for empty IDs (some servers / spam don't include a Message-ID).

Jump to

Keyboard shortcuts

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