Documentation
¶
Overview ¶
Package mailer sends the app's mail and reads its mailbox. The app sends from its one address, email.from in the permission list, and only to the domains in email.to_domains; the runner enforces both:
email: from: ops@acme.example to_domains: [acme.example]
Rules for Send:
- Every Message has an IdempotencyKey that names what the mail is about, such as invoice-42-reminder. Sending the same key again sends nothing and returns the first message's id, so a retried request never mails twice.
- The domain of every recipient, and of ReplyTo unless it is the app's own address, equals one of email.to_domains (E-MAIL-003).
- Headers may set only In-Reply-To, References, List-Id, List-Unsubscribe, List-Unsubscribe-Post, Auto-Submitted and Precedence.
- Send after the data the mail reports is committed, never before.
The app's mailbox has the folders Inbox, Archive and Trash. List reads a page of a folder, newest first, Get reads a message, Move moves it to another folder and Delete removes it. Without an email section in the permission list, every call fails with E-MAN-012. In aicoded dev, mail is caught and shown on the dev UI, never sent.
Read more in the guide docs/guides/mail.md and the task docs/tasks/email-after-a-write.md, which aicoded explain and the MCP tool howto print as guides/mail and tasks/email-after-a-write.
Index ¶
Examples ¶
Constants ¶
const ( Inbox = "inbox" Archive = "archive" Trash = "trash" )
Folders of the app's mailbox.
Variables ¶
var ErrNotFound = errors.New("mailer: no such message")
ErrNotFound is returned for a message id the mailbox does not hold.
Functions ¶
func Send ¶
Send checks msg against the mail rules and hands it to the runner, and returns its id.
Example ¶
Send a mail after the data it reports is committed, here in a form's Process hook. The idempotency key names what the mail is about, so a retried request sends it once.
package main
import (
"context"
"aicoded.dev/framework/mailer"
)
func main() {
process := func(ctx context.Context) error {
_, err := mailer.Send(ctx, mailer.Message{
IdempotencyKey: "invoice-42-reminder",
To: []mailer.Address{{Name: "Accounts", Address: "accounts@acme.example"}},
Subject: "Invoice 42 is due tomorrow",
Text: "Invoice 42 is due tomorrow. Its details are in the app.",
})
return err
}
_ = process
}
Output:
Types ¶
type Address ¶
type Address struct{ Name, Address string }
Address is one mailbox: a plain address and the name shown with it.
type Attachment ¶
Attachment is a file sent with or received in a message.
type Mail ¶
type Mail struct {
Summary
Cc []Address
ReplyTo *Address
Text, HTML string
Attachments []Attachment
Headers []Header
}
Mail is a whole message from the mailbox.
type Message ¶
type Message struct {
IdempotencyKey string
FromName string
To, Cc, Bcc []Address
ReplyTo *Address
Subject string
Text, HTML string
Attachments []Attachment
// Headers may set only In-Reply-To, References, List-Id, List-Unsubscribe,
// List-Unsubscribe-Post, Auto-Submitted and Precedence.
Headers []Header
}
Message is one outbound email. The sender is always the app's address; FromName sets the name shown with it. IdempotencyKey is required: sending the same key again returns the first message's id and sends nothing, so a retried request never mails twice.
type Summary ¶
type Summary struct {
ID string
From Address
To []Address
Subject string
Date time.Time
Size int64
Folder string
}
Summary describes a message in the mailbox.
func List ¶
List returns a page of folder, newest first, and the number of messages in the folder. The page skips offset messages and holds up to limit, and never more than 100. A negative offset counts as 0, and a limit of 0 or less as 50.
Example ¶
List reads a page of a folder of the app's mailbox, newest first, here in a page's Data.
package main
import (
"context"
"fmt"
"time"
"aicoded.dev/framework/mailer"
)
func main() {
data := func(ctx context.Context) error {
page, total, err := mailer.List(ctx, mailer.Inbox, 0, 20)
if err != nil {
return err
}
fmt.Printf("%d of %d messages\n", len(page), total)
for _, m := range page {
fmt.Println(m.Date.Format(time.DateOnly), m.Subject)
}
return nil
}
_ = data
}
Output: