Files
smtp/README.md
T

129 lines
3.6 KiB
Markdown
Raw Permalink Normal View History

# einherjar/smtp
[![version](https://img.shields.io/badge/version-v1.3.0-5C4EE5?style=flat-square)](https://code.nochebuena.dev/einherjar/smtp)
[![license](https://img.shields.io/badge/license-AGPL--3.0-22863A?style=flat-square)](LICENSE)
[![go](https://img.shields.io/badge/Go-1.26+-00ADD8?style=flat-square&logo=go&logoColor=white)](https://go.dev)
[![health](https://img.shields.io/badge/health-degraded-E36209?style=flat-square)]()
> A raven sent from Valhalla reaches its destination. The sender does not wait at the window.
`code.nochebuena.dev/einherjar/smtp` is the email sender component of the Einherjar framework. It is built entirely on the Go standard library (`net/smtp`, `mime/multipart`, `html/template`) with no external dependencies. When `EINHERJAR_SMTP_HOST` is empty, `New` returns a silent no-op — email absence never blocks a transaction, user registration, or order placement. Health priority is `LevelDegraded`.
---
## Usage
### Setup
```go
import "code.nochebuena.dev/einherjar/smtp"
mailer := smtp.New(logger, smtp.DefaultConfig())
lc.Append(mailer) // OnInit verifies credentials; OnStop is a no-op
// mailer is observability.Checkable (dial check, LevelDegraded):
srv.Get("/health", health.NewHandler(logger, mailer).ServeHTTP)
```
When `cfg.Host` is empty, `New` returns a no-op that logs every `Send` call at debug level and always returns nil. No code changes are needed between environments.
### Sending a plain-text email
```go
err := mailer.Send(ctx, smtp.Message{
To: []string{"user@example.com"},
Subject: "Welcome to the service",
Body: "Your account is ready.",
})
```
### Sending HTML with attachments
```go
err := mailer.Send(ctx, smtp.Message{
To: []string{"user@example.com"},
CC: []string{"support@example.com"},
BCC: []string{"audit@example.com"}, // envelope only — never in headers
Subject: "Your invoice",
Body: renderedHTML,
ContentType: "text/html",
Attachments: []smtp.Attachment{
{
Name: "invoice.pdf",
ContentType: "application/pdf",
Data: pdfReader, // consumed once; do not reuse
},
},
})
```
### Template rendering
Template rendering is intentionally separate from `Send`. Render to a string first, then assign to `Message.Body`. This keeps the transport and rendering concerns independent.
```go
tmpl, err := smtp.ParseFS(os.DirFS("templates"), "*.html")
if err != nil {
return err
}
body, err := tmpl.Render("welcome.html", map[string]any{
"Name": user.Name,
"URL": activationURL,
})
if err != nil {
return err
}
err = mailer.Send(ctx, smtp.Message{
To: []string{user.Email},
Subject: "Activate your account",
Body: body,
ContentType: "text/html",
})
```
---
## Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
| `EINHERJAR_SMTP_HOST` | No | `""` | SMTP host. Empty → no-op sender |
| `EINHERJAR_SMTP_PORT` | No | `587` | SMTP port |
| `EINHERJAR_SMTP_USER` | No | `""` | Auth username |
| `EINHERJAR_SMTP_PASSWORD` | No | `""` | Auth password |
| `EINHERJAR_SMTP_FROM` | No | `""` | Default From address |
---
## Dependency graph
```
contracts (zero dependencies)
core
smtp (contracts, core, stdlib only)
your app
```
No external dependencies beyond the Go standard library.
---
## Verification
```bash
cd smtp/
go build ./...
go vet ./...
go test ./...
gofmt -l .
```
---
> *The message matters. The raven is only the path.*
> *Make the path reliable. Do not let it stop the battle.*