Mail
The Mail component gives a generated App one portable message API with local and production delivery drivers.
Use it for account email, invitations, receipts, reports, and other outbound messages. Application code composes message intent; generated providers choose the transport.
Generated Ownership
Generated mail integration lives in:
internal/mail/
app/wire/inject_managers.gointernal/mail owns the generated manager, driver construction, named accessors, auth delivery integration, and observability hooks. App services should use that manager instead of importing SMTP or provider SDKs.
The underlying portable message API comes from github.com/goforj/mail.
Local-First Configuration
Without Docker, the generated fallback is the log driver:
MAIL_DRIVER=log
MAIL_SUPPORTED_DRIVERS=log
MAIL_FROM_ADDRESS=no-reply@example.com
MAIL_FROM_NAME=Example App
MAIL_LOG_BODIES=falseThe log driver writes delivery metadata to application output. Message bodies stay hidden unless MAIL_LOG_BODIES=true.
When local Docker support is selected, generated Apps can use SMTP with Mailpit:
MAIL_DRIVER=smtp
MAIL_SUPPORTED_DRIVERS=smtp
MAIL_SMTP_HOST=mailpit
MAIL_SMTP_PORT=1025
MAILPIT_HTTP_PORT=8025Mailpit provides a local inbox at http://localhost:8025 without delivering messages to real recipients.
Send a Message
Inject the generated manager into the service that owns the workflow:
package notifications
import (
"context"
"your/module/internal/mail"
)
// WelcomeService owns welcome-message delivery for new users.
type WelcomeService struct {
mail *mail.Manager
}
// NewWelcomeService creates a welcome-message service.
func NewWelcomeService(mailManager *mail.Manager) *WelcomeService {
return &WelcomeService{mail: mailManager}
}
// Send delivers one welcome message through the default mailer.
func (s *WelcomeService) Send(ctx context.Context, email, name string) error {
return s.mail.Default().
Message().
To(email, name).
Subject("Welcome").
Text("Your account is ready.").
HTML("<p>Your account is ready.</p>").
Send(ctx)
}Add notifications.NewWelcomeService to the owning App's service provider set, then run:
forj buildNamed Mailers
Use a named mailer when one App needs distinct senders or providers:
MAIL_TRANSACTIONAL_DRIVER=resend
MAIL_TRANSACTIONAL_FROM_ADDRESS=transactions@example.com
MAIL_TRANSACTIONAL_FROM_NAME=Example Transactions
MAIL_TRANSACTIONAL_RESEND_API_KEY=secretAfter generation, use the typed accessor:
err := manager.Transactional().
Message().
To("alice@example.com", "Alice").
Subject("Receipt ready").
Text("Your receipt is ready.").
Send(ctx)manager.Named("transactional") is available for dynamic operator-oriented lookup. Business code should prefer generated typed accessors when the mailer name is known at compile time.
Auth Integration
Auth implies Mail. Generated password-reset and email-verification delivery uses the default mailer automatically.
Keep auth message construction in the generated auth and mail integration. Changing the transport should be an environment and supported-driver change, not a rewrite of auth workflows.
Driver Selection
MAIL_SUPPORTED_DRIVERS controls which transports are compiled into the App. MAIL_DRIVER and MAIL_<NAME>_DRIVER select among those compiled drivers at runtime.
Supported framework drivers include:
logsmtpresendpostmarkmailgunsendgridses
Use the Mail library page for transport capabilities and Environment Reference for every generated provider setting.
Testing
For generated App integration, use the log driver and capture output or exercise the owning service with a local manager.
For isolated service tests, place a narrow mail boundary around the workflow and use the library's mailfake driver. Assert recipient, subject, body, and send count without contacting a provider.
Keep live provider credentials out of normal unit and integration suites.
Operations
Generated mail sends emit observer events used by logs, metrics, and inspects when those components are enabled.
In production:
- compile only the providers the deployment uses
- inject credentials through the deployment environment
- verify sender domains and provider authentication
- keep message bodies and recipient data out of default logs
- monitor send failures by bounded mailer and driver names
- use provider dashboards for delivery, bounce, and complaint state
Common Mistakes
Common mistakes
- Do not import provider SDKs into application services.
- Do not enable real delivery for local tests.
- Do not log message bodies or provider credentials by default.
- Do not add a named mailer without regenerating typed accessors.
- Do not treat successful provider submission as proof of inbox delivery.
Next Steps
- Mail Library covers message composition and transport APIs.
- Environment Reference lists sender, driver, and provider settings.
- Named Resources explains generated accessors.
- Driver Selection explains compile-time support and runtime selection.
- Auth explains generated account email flows.
