Email (SMTP)
Send-only over any SMTP relay — subject auto-splits from the first line, body flows naturally into compliance and archival pipelines.
🟢 Runtime-ready — wraps
nodemailer(MIT). No streaming — email is atomic once sent. The sharedcreateStreamSinkhelper buffers incremental deltas and dispatches a single message at finalize time.
What you get
- Works with any SMTP relay — Gmail, Office 365, SES, Postmark, Mailgun, or self-hosted
- Subject auto-split: if the message text starts with a single short line (≤ 120 chars, no newline), that line becomes the subject and the rest is the body. Otherwise the subject is "Minara alert". This lets the LLM write natural alerts without learning a two-field tool
- Per-message recipient override via
channel— useful for routing critical alerts to a pager address
Setup
1. Pick a relay
Pick the path of least resistance:
| Relay | Host | Port | Notes |
|---|---|---|---|
| Gmail (personal) | smtp.gmail.com | 587 | Needs an app password, not your main login |
| Office 365 | smtp.office365.com | 587 | OAuth only for production; SMTP basic auth is on its way out |
| AWS SES | email-smtp.{region}.amazonaws.com | 587 | Account must be out of sandbox to send to arbitrary recipients |
| Postmark | smtp.postmarkapp.com | 587 | Server API token = SMTP username = SMTP password |
| Self-hosted | your own host | 587 or 465 | secure: true auto-enabled for port 465 |
2. Configure Minara
Easy way — ask the agent:
"configure email alerts — I'll give you the SMTP settings"
Manual way:
minara auth messaging add emailOr directly in your project .env file:
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
[email protected]
SMTP_PASSWORD=your-app-password
[email protected]
[email protected]SMTP_USER and SMTP_PASSWORD are optional — open relays without
auth (e.g. an internal SMTP gateway) skip both.
3. Test
minara auth messaging test emailCheck EMAIL_TO for a message with subject "Minara alert — test ping".
Subject and body layout
Given this text:
Position liquidated — BTC long @ $61 800
Reason: 3σ drawdown on 15m chart; watching for re-entry.Minara sends the email as:
Subject: Position liquidated — BTC long @ $61 800
Body: Reason: 3σ drawdown on 15m chart; watching for re-entry.If the first line is > 120 chars or the text doesn't contain a newline-to-paragraph break, the whole text becomes the body and the subject falls back to "Minara alert".
Overriding the recipient
send_message({
provider: "email",
channel: "[email protected]",
text: "Critical: position liquidation imminent",
})Use this for pager/compliance routing without reconfiguring EMAIL_TO.
Troubleshooting
Gmail: "Username and Password not accepted"
- You're using your main password, not an app password. Go to
App passwords, generate
one for "Mail", and paste it as
SMTP_PASSWORD. Requires 2FA on the Google account
Office 365: "5.7.57 SMTP client was not authenticated"
- SMTP basic auth is disabled on the tenant. Use SES / Postmark / Mailgun for reliable programmatic send, or contact your admin to enable SMTP AUTH on the mailbox (Microsoft has been deprecating it)
SES: "554 Message rejected (still in sandbox)"
- Your AWS account hasn't been granted production SES access yet. Request it in the SES console — takes ~1 day
Message arrives in spam
- Set SPF / DKIM / DMARC on the
EMAIL_FROMdomain. Without them gateways like Gmail aggressively mark programmatic mail as spam
"Stream doesn't appear live"
- Expected: email has no streaming surface. Minara buffers the full response and sends once at finalize time
Reference
- Env vars:
SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASSWORD,EMAIL_FROM,EMAIL_TO - Source:
apps/agent/src/messaging/email.ts - Transport: nodemailer.com