mdsend

package module
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: MIT Imports: 29 Imported by: 0

README

Mdsend

Send markdown files as mail.

Project status: active development towads beta.

Features

  • Durable: mail queues are fault tolerant and atomic, brokered by <Watermill.io> over SQLite3.
  • Flexible: select the mailer backend based on highest deliverability. Swap it out later without changing anything in your letters or templates.
    • Supports recipient lists formats: CSV, JSON, YAML, TOML, and Cue.
  • Portable: runs on many kinds of systems. Will include an embeddable HTTP service and Posgres support in the future.

Installation

  • MacOS:
    brew tap dkotik/tap
    brew install mdsend
    
  • Build from source:
    go install github.com/dkotik/mdsend@latest
    

Letter

---
subject: "Test Email"
from: "Test Account <test@gmail.com>"
to: "Test Account <test@gmail.com>"
---

# Title of the Example Letter

Write text in **Markdown** notation.

Load Recipient Lists

Target address fields, to, cc, and bcc support a list of entries. Any entry can also point to a configuration file in a variety of common formats that will be merged into the parent list.

to: mailinglist.yaml
cc:
  - another_list.toml
  - jsonIsFine.json
  - cuelist.cue
bcc:
  - name: Named Entry
    email: test@test.com
    title: Mr.
    first_name: First

Each entry is a map, regardless of how it was loaded. Any value of the map is accessible to the template engine through the context.

Greeting {{ .Recipient.title }} {{ .Recipient.first_name }},

I am writing, because ...

Extend One Letter with Another

Merge the content and frontmatter fields into a letter from another file by using the extends field.

extends:
  # this letter will inherit the content
  # and configuration values of `template.md`
  - template.md
  # load a configuration file
  - config.yaml

Any Markdown content below the last horizontal rule is added as a footer to the current letter. You may nest the extensions deep as long as there are no circular references.

Development Roadmap

Tools

Similar Projects

Documentation

Index

Constants

View Source
const (
	FieldNameID                        = "id"
	FieldNameSeed                      = "seed"
	FieldNameExtends                   = "extends"
	FieldNameDatabase                  = "queue"
	FieldNameSubject                   = "subject"
	FieldNameFrom                      = "from"
	FieldNameReplyTo                   = "reply_to"
	FieldNameAttachments               = "attachments"
	FieldNameAttachmentName            = "name"
	FieldNameAttachmentLocation        = "location"
	FieldNameTemplates                 = "templates"
	FieldNameHeaders                   = "headers"
	FieldNameLanguage                  = "language"
	FieldNameMediaContraints           = "media_constraints"
	FieldNameMediaConstraintsQuality   = "quality"
	FieldNameMediaConstrainsResolution = "resolution"
	FieldNameMediaConstraintsWidth     = "width"
	FieldNameMediaConstrainsHeight     = "height"
	FieldNameSchedule                  = "schedule"
	FieldNameScheduleAfter             = "after"
	FieldNameScheduleDelay             = "delay"
	FieldNameScheduleStep              = "step"
	FieldNameScheduleExpire            = "expire"
	FieldNameScheduleFluctuate         = "fluctuate"
)
View Source
const Version = "dev"

Variables

This section is empty.

Functions

func EachAttachmentSourceRelativeWithPathPrefix added in v0.0.5

func EachAttachmentSourceRelativeWithPathPrefix(prefix string, each iter.Seq2[AttachmentSource, error]) iter.Seq2[AttachmentSource, error]

Types

type Attachment

type Attachment struct {
	LetterID string
	Name     string
	Source   string
	// Hash                     uint64 // for XXHash2
	Hash string

	// ContentID is the ID of the inline attachment to use in the message.
	// It must conform to RFC 2392 format, including the angle brackets:
	// <hash@domain.com>. Leave empty for standalone attachments that
	// are not referenced by other parts of the message.
	ContentID   string
	ContentType string
	Content     []byte
}

func NewAttachment

func NewAttachment(b []byte, constraints media.Constraints) (a Attachment, err error)

func NewAttachmentFromFile added in v0.0.5

func NewAttachmentFromFile(fs fs.FS, p string, constraints media.Constraints) (a Attachment, err error)

func (Attachment) AssertEqualityTo added in v0.0.5

func (a Attachment) AssertEqualityTo(b Attachment) error

func (Attachment) IsEqualTo added in v0.0.5

func (a Attachment) IsEqualTo(b Attachment) bool

func (Attachment) LogValue added in v0.0.5

func (a Attachment) LogValue() slog.Value

func (Attachment) Validate added in v0.0.5

func (a Attachment) Validate() (err error)

func (Attachment) WithUpdatedHash added in v0.0.5

func (a Attachment) WithUpdatedHash() Attachment

type AttachmentError added in v0.0.5

type AttachmentError uint8
const (
	ErrInvalidAttachment AttachmentError = iota
	ErrDuplicateAttachment
)

func (AttachmentError) Error added in v0.0.5

func (err AttachmentError) Error() string

type AttachmentReference added in v0.0.5

type AttachmentReference struct {
	ID          string
	Name        string
	ContentID   string
	ContentType string
}

type AttachmentSource added in v0.0.5

type AttachmentSource struct {
	Name     string
	Location string
}

type FieldComparisonMismatchError added in v0.0.5

type FieldComparisonMismatchError struct {
	FieldName     string
	ExpectedValue any
	ActualValue   any
}

func (FieldComparisonMismatchError) Error added in v0.0.5

type Letter added in v0.0.5

type Letter struct {
	ID string
	// Frontmatter orderedmap.OrderedMap[string, any]
	Frontmatter map[string]any
	Templates   []Attachment
	Content     string
	CreatedAt   time.Time
	SentAt      time.Time
}

func NewLetter added in v0.0.5

func NewLetter(b []byte) (letter Letter, err error)

func NewLetterFromFile added in v0.0.5

func NewLetterFromFile(
	ctx context.Context,
	fs fs.FS,
	p string,
) (letter Letter, err error)

func (Letter) AssertEqualityTo added in v0.0.5

func (l Letter) AssertEqualityTo(b Letter) error

func (Letter) EachAttachmentSource added in v0.0.5

func (l Letter) EachAttachmentSource() iter.Seq2[AttachmentSource, error]

func (Letter) EachWarning added in v0.0.5

func (l Letter) EachWarning(fs fs.FS) iter.Seq2[*i18n.LocalizeConfig, error]

func (Letter) GetDatabase added in v0.0.5

func (l Letter) GetDatabase() string

func (Letter) GetFrom added in v0.0.5

func (l Letter) GetFrom() (mail.Address, error)

GetFrom returns the FieldNameFrom address from the frontmatter. There must be only one valid address. Mutliple from addresses can disrupt delivery.

func (Letter) GetHeaders added in v0.0.5

func (l Letter) GetHeaders() (headers []header.Header, err error)

func (Letter) GetLanguage added in v0.0.5

func (l Letter) GetLanguage() (lang language.Tag, err error)

func (Letter) GetMediaConstraints added in v0.0.5

func (l Letter) GetMediaConstraints() (m media.Constraints, err error)

func (Letter) GetSchedule added in v0.0.5

func (l Letter) GetSchedule() (s Schedule, err error)

func (Letter) GetSeed added in v0.0.5

func (l Letter) GetSeed() (string, error)

func (Letter) GetSubject added in v0.0.5

func (l Letter) GetSubject() (string, error)

func (Letter) IsEqualTo added in v0.0.5

func (l Letter) IsEqualTo(b Letter) bool

func (Letter) IsValid added in v0.0.5

func (l Letter) IsValid(cxt context.Context, logger *slog.Logger) (ok bool)

func (Letter) LogValue added in v0.0.5

func (l Letter) LogValue() slog.Value

func (Letter) Validate added in v0.0.5

func (l Letter) Validate() (err error)

type LetterError added in v0.0.5

type LetterError uint
const (
	ErrNoSubject LetterError = iota + 1
	// ErrNoQueue
	ErrDuplicateLetter
	ErrNoFromAddress
	ErrNoContent
	ErrLetterNotFound
)

func (LetterError) Error added in v0.0.5

func (l LetterError) Error() string

type Mailer

type Mailer interface {
	SendMail(context.Context, Message) (string, error)
}

type Message

type Message struct {
	ID            string
	LetterID      string
	SeedKey       string
	Headers       []header.Header
	From          mail.Address
	To            mail.Address
	Subject       string
	Text          string
	HTML          string
	ScheduleAfter time.Time
	ScheduledAt   time.Time
	SentAt        time.Time
}

Message is an intent to delivery a copy of a letter to a particular recipient.

func (Message) AssertEqualityTo added in v0.0.5

func (m Message) AssertEqualityTo(b Message) error

func (Message) IsEqualTo added in v0.0.5

func (m Message) IsEqualTo(b Message) bool

func (Message) LogValue added in v0.0.5

func (m Message) LogValue() slog.Value

func (Message) Validate added in v0.0.5

func (m Message) Validate() (err error)

type MessageError added in v0.0.5

type MessageError uint8
const (
	ErrInvalidMessage MessageError = iota
	ErrDuplicateMessage
	ErrMessageNotFound
)

func (MessageError) Error added in v0.0.5

func (err MessageError) Error() string

type Schedule added in v0.0.5

type Schedule struct {
	After     time.Time     `json:"after"`
	Step      time.Duration `json:"step"`
	Fluctuate time.Duration `json:"fluctuate"`
	Expire    time.Duration `json:"expire"`
}

func (Schedule) Next added in v0.0.5

func (s Schedule) Next() Schedule

Directories

Path Synopsis
cmd
mdsend command
mime
Package mime encodes electronic mail parts for delivery.
Package mime encodes electronic mail parts for delivery.
smtp
Package smtp sends MIME-formatted emails via SMTP.
Package smtp sends MIME-formatted emails via SMTP.

Jump to

Keyboard shortcuts

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