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, each with printable ASCII only.
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 while the runner remembers the key. On the platform it remembers the app's last 10 000 keys within 24 hours, until it restarts, and aicoded dev until it restarts; a retry after that sends the message again.
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: