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 ¶
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`.
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 ¶
DeletePassword wipes the password file. Idempotent.
func LoadPassword ¶
LoadPassword reads the password back. Returns ErrNoPassword when the file isn't there so callers can prompt for `inbox auth`.
func SavePassword ¶
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.
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 ¶
LoadState reads State from path. Returns an empty (zero-value) State if the file doesn't exist — first run is normal.
func (*State) AlreadyProcessed ¶
AlreadyProcessed reports whether messageID has been recorded.
func (*State) MarkProcessed ¶
MarkProcessed records messageID as already-handled. No-op for empty IDs (some servers / spam don't include a Message-ID).