intake

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package intake is the inbound adapter for content that arrives from outside without a person behind it: the jumble's doors, the webhook intake and the mail intake. An intake differs from the REST API in who is asking - a bridge with a token, not an account with a role - and in nothing else: everything it stores goes through the same application services and the same bounds.

Index

Constants

View Source
const (
	DefaultMaxMailParts          = 100
	DefaultMaxMailDepth          = 4
	DefaultMaxMailAttachments    = 20
	DefaultMaxMailAttachmentSize = 10 << 20
	DefaultMaxMailTextBytes      = 64 << 10
)

The documented defaults. Mail-shaped rather than generous: the numbers are what an ordinary message needs with room to spare, and the point of a bound is that the extraordinary one is refused rather than served.

View Source
const (
	CodeMailUnparseable      = "mail.unparseable"
	CodeMailTooManyParts     = "mail.too_many_parts"
	CodeMailTooDeep          = "mail.too_deeply_nested"
	CodeMailTooManyAttached  = "mail.too_many_attachments"
	CodeMailAttachmentTooBig = "mail.attachment_too_large"
)

The refusals. Codes rather than sentences (rule 8), and one per bound, because "too big" and "too many" send whoever is looking to two different places.

View Source
const HTMLAlternativeFileName = "message.html"

HTMLAlternativeFileName is what the kept HTML alternative is called. Stable, because a person looking at an entry with two attachments has to be able to tell which one is the mail.

Variables

View Source
var ErrUnparseable = shared.ErrValidation.WithDetail(CodeMailUnparseable)

ErrUnparseable is a payload that is not a message this parser can walk. The intake answers it by storing the raw bytes rather than by refusing the delivery - see MailIntake.

Functions

This section is empty.

Types

type Mail

type Mail struct {
	// Sender is the address the message claims to come from, decoded and normalised. Data, never
	// an identity: a From header authenticates nothing (security.md §4), and every consumer of
	// this field treats it as text somebody typed.
	Sender string
	// Subject is the decoded subject line, RFC 2047 words included.
	Subject string
	// Text is what a person reads: the plain part where there is one, and the HTML part's source
	// as text where there is not. Never rendered markup - an HTML mail read here is somebody
	// reading tags, which is the honest thing for an inbox that must not run them.
	Text string
	// Attachments are the files the mail carried, decoded and bounded. The HTML alternative of a
	// mail that also had a plain part is one of them, carried as text: keeping it is what "stored
	// alongside" means, and keeping it out of Text is what stops a second copy of the same message
	// from being the message.
	Attachments []MailAttachment
}

Mail is one message, parsed into what a jumble entry is made of.

func ParseMail

func ParseMail(raw []byte, limits MailLimits) (Mail, error)

ParseMail walks one message.

The bounds are refusals with their own codes, and they are not the same answer as ErrUnparseable: a mail with two hundred parts is a mail this installation declines to take, and one whose shape the parser cannot follow is a mail it takes as bytes. Telling an operator which of the two happened is the difference between "raise the bound" and "look at the entry".

type MailAttachment

type MailAttachment struct {
	FileName    string
	ContentType string
	Content     []byte
}

MailAttachment is one file out of a message.

type MailDeliverer

type MailDeliverer interface {
	Execute(ctx context.Context, delivery jumbleservice.MailDelivery) (domain.Entry, error)
}

MailDeliverer is the use case this hands a parsed message to.

type MailIntake

type MailIntake struct {
	Deliveries MailDeliverer
	// Limits are the parser's bounds. Zero fields take the documented defaults, so an installation
	// that configures nothing is bounded rather than unbounded.
	Limits MailLimits
}

MailIntake turns one posted message into a jumble entry.

The seam between the two halves of the mail intake, and it is deliberately thin: the parser above it knows MIME and knows nothing about this system, the use case below it knows the inbox and knows nothing about MIME, and what happens here is that one becomes the other. That is what makes the transport replaceable - an IMAP poll, when AM-1 is decided, produces bytes and calls this same function (automation.md §5).

It authenticates nothing itself. The token travels through to the use case, which resolves the tenant from it before a byte is stored, and every reason not to serve answers the same not-found - a malformed token included, because "that is not the right shape" tells somebody guessing that the shape is what to fix (T-21).

func (MailIntake) Deliver

func (i MailIntake) Deliver(
	ctx context.Context, presented string, raw []byte,
) (domain.Entry, error)

Deliver parses the payload and stores what it found.

A payload the parser cannot read is not a refusal: the delivery goes on with the raw bytes, and the entry carries them. A payload that breaks one of the parser's *bounds* is a refusal, with the code that says which one - the difference between "look at the entry" and "raise the bound" is the difference between a message this installation could not read and one it declines to take.

type MailLimits

type MailLimits struct {
	// MaxParts is how many MIME parts one message may have, counted across the whole tree.
	MaxParts int
	// MaxDepth is how deeply parts may nest. A multipart/mixed holding a multipart/alternative is
	// depth two, which is what an ordinary mail with a text, an HTML alternative and a file looks
	// like; four leaves room for the unordinary one.
	MaxDepth int
	// MaxAttachments is how many files one mail may carry into the jumble.
	MaxAttachments int
	// MaxAttachmentBytes bounds one attachment, decoded.
	MaxAttachmentBytes int64
	// MaxTextBytes bounds the text this becomes, decoded. The jumble's own bound on a body is the
	// domain's (jumble.MaxBodyBytes); this one is the parser's, so an over-long part is cut here
	// rather than refused three layers later.
	MaxTextBytes int64
}

MailLimits bounds what one parse may cost. Zero means the default rather than "no limit": an unbounded parse is exactly the thing this file exists to prevent, so forgetting to set a bound gives the documented one.

type WebhookIntake

type WebhookIntake struct {
	Deliveries jumbleservice.IntakeJumbleEntry
}

WebhookIntake turns one posted delivery into a jumble entry (automation.md §1.1's credential discipline applied to the inbox).

It authenticates the tenant, never a person: the token is the whole credential, and every reason not to serve answers the same not-found - a malformed token included, because "that is not the right shape" tells somebody guessing that the shape is what to fix (T-21).

func (WebhookIntake) Deliver

func (i WebhookIntake) Deliver(
	ctx context.Context, presented, sender, subject, body string,
) (domain.Entry, error)

Deliver parses the credential and stores the delivery.

Jump to

Keyboard shortcuts

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