All docs
Docs · Reference
Email Out
Send email with EXEC txco://sendmail
You can send email from Thanks, Computer as well as receive it.
To send:
- Configure the
_sendmailcontract on the envelope EXEC "txco://sendmail"to request the email be sent.
WHEN .resolution == "credited"
SET ._sendmail.subject = "Your credit has been applied",
._sendmail.from = "billing@ops.example.com",
._sendmail.to = .customer.email,
._sendmail.body = "<p>Hi {{.name}}, your credit for invoice {{.invoice}} is in.</p>"
EXEC "txco://sendmail" The _sendmail contract
Required: subject, body (HTML), from.
| Field | Meaning |
|---|---|
to | One address, a list, or a list of {address, vars} objects for per-recipient personalization |
vars | Shared template variables; {{.name}}-style markers in subject/body render per recipient (missing keys render empty). Also exposed to the shell as {{.Vars.name}} — e.g. a per-send link in a custom templates.html |
text | Explicit plaintext part; omitted, it’s derived from the HTML body |
cc / bcc | Flat address lists added to every message (cc visible, bcc envelope-only) |
reply_to | Dedicated Reply-To field |
headers | Extra headers map — structural/signing/loop-guard headers are denylisted (use reply_to, not a raw header) |
envelope_from | MAIL FROM / Return-Path override. Defaults to from. Set "<>" for a null reverse-path — the RFC 3834 posture for auto-replies (no bounce loops) |
campaign | Label for rate-limit and audit grouping |
templates.html | A custom HTML template to wrap the body in, replacing the bundled default. Slots: {{.Subject}}, {{.Body}}, {{.Preheader}}, and {{.Vars.x}} for any vars. Omitted → the default template |
The HTML body is wrapped in a responsive, CSS-inlined shell — the bundled default, or a templates.html you supply (see below) — and messages are DKIM-signed.
Custom Template — templates.html
By default the body is dropped into a built-in responsive shell. To control the whole
email (your own header, footer, branding, responsive @media styles), pass a complete
HTML document as templates.html with the same {{.Body}} / {{.Subject}} / {{.Preheader}} slots the default uses (plus {{.Vars.x}} for any per-send vars,
e.g. a {{.Vars.nexturl}} button href):
# load a template shipped in the stack's FILES, then send with it
WITH files = &array(&object("path", "_email/drip.html", "as", "tmpl"))
EXEC "txco://read-file"
SET ._sendmail.to = .reader,
._sendmail.from = "news@ops.example.com",
._sendmail.subject = "This week",
._sendmail.body = .article_html,
._sendmail.templates.html = ._files.tmpl.content
EXEC "txco://sendmail" Variable substitutions are possible inside a html/template: {{.Subject}} is auto-escaped, {{.Body}} is your _sendmail.body.
The from domain must be a verified hostname of the sending tenant (routing) — a rule can’t send as a
domain its tenant doesn’t own. If a send fails with from_not_verified, add and
verify the hostname first (txco auth tenant hostnames add … && … verify).
What comes back
The EXEC of the operation merges a result under _sendmail.result:
- success:
{sent, skipped, failed, recipients: […]}— per-recipient outcomes; rate-limited recipients are skipped with reasonrate_limited - error:
{status: "error", reason, error}— reasons includeno_relay,missing_field,invalid_from,from_not_verified,no_recipients,too_many_recipients,invalid_template
Operator configuration
Sending is off until a relay is configured. The chassis is a submitter, not an MTA: it hands rendered messages to your edge SMTP.
| Flag | Default | Meaning |
|---|---|---|
--mail-relay-addr | (empty = disabled) | SMTP submission address (host:port) |
--mail-relay-tls | none | none (trusted private net) or starttls |
--mail-dial-timeout-ms | 5000 | Dial + submit deadline; a down relay fails fast |
--mail-max-recipients | 50 | Per-call cap; over it the op errors rather than truncating |
--mail-rate-limits | (empty = off) | Per-tenant caps, e.g. "100/2m,200/4h" — every rule must be under its cap. Per node, in memory: a runaway-loop valve, not fleet-wide accounting |