Karl.Cli
1.3.2
dotnet tool install --global Karl.Cli --version 1.3.2
dotnet new tool-manifest
dotnet tool install --local Karl.Cli --version 1.3.2
#tool dotnet:?package=Karl.Cli&version=1.3.2
nuke :add-package Karl.Cli --version 1.3.2
Karl.Cli
Karl includes a standalone CLI tool for:
- Sending test emails
- Rendering templates
- Local development workflows
- CI/CD pipelines
- Administrative scripts
Installing Karl.Cli
dotnet tool install --global Karl.Cli
Or locally:
dotnet new tool-manifest
dotnet tool install Karl.Cli
Basic Usage
Send an Email
karl send \
--from noreply@example.com \
--smtp-host smtp.example.com \
--to user@example.com \
--subject "Hello" \
--body "# Hello World"
CLI Configuration Sources
Karl.Cli reads configuration from:
--json <path>when provided explicitly- Local config files in current directory (first match wins):
.karl,karl,karl.json - User config directory:
- Windows:
%USERPROFILE%/.karl - Linux/macOS:
$XDG_CONFIG_HOME/karlor~/.config/karl
- Windows:
- Environment variables prefixed with
KARL_
Use __ in environment variable names for nested keys, for example:
KARL_Karl__Smtp__Host=smtp.example.com
KARL_Karl__Smtp__Port=587
KARL_Karl__Smtp__SecurityMode=StartTlsRequired
Configuration Precedence
For Karl:Smtp:*/Karl:File:* settings, a command-line flag always wins over a config file value,
and a config file value always wins over Karl.Cli's own built-in defaults:
| Setting | CLI flag | Config key | Built-in default |
|---|---|---|---|
| SMTP host | --smtp-host |
Karl:Smtp:Host |
none — see below |
| SMTP port | --smtp-port |
Karl:Smtp:Port |
587 |
| SMTP username | --username |
Karl:Smtp:Username |
(none) |
| SMTP password | --password |
Karl:Smtp:Password |
(none) |
| STARTTLS mode | --tls |
Karl:Smtp:SecurityMode |
StartTlsRequired |
| File output directory | --output |
Karl:File:DirectoryPath |
emails |
| File name prefix | (none) | Karl:File:FileNamePrefix |
email |
send requires an SMTP host to be specified somewhere — via --smtp-host, Karl:Smtp:Host in a
config file, or KARL_Karl__Smtp__Host — and fails fast with a clear error if none of them supply
one, rather than silently attempting to connect to a default host.
Send Using a Template
karl send \
--from noreply@example.com \
--smtp-host smtp.example.com \
--to user@example.com \
--subject "Welcome {{name}}" \
--markdown ./welcome.md \
--model ./model.json
SMTP Send Example
karl send \
--from noreply@example.com \
--to user@example.com \
--subject "SMTP test" \
--body "Sent via Karl CLI" \
--smtp-host smtp.example.com \
--smtp-port 587 \
--username username \
--password password \
--tls StartTlsRequired
Mass Email Using a CSV File
send, file, and preview all accept --csv to send one personalized email
per row of a CSV file instead of a single message. Every CSV value is a string
that can be substituted into --subject/--body/--markdown templates — this
is token replacement, not typed data binding.
karl send \
--from noreply@example.com \
--smtp-host smtp.example.com \
--csv contacts.csv \
--to-column Email \
--name-column Name \
--subject "Welcome {{FirstName}}" \
--markdown ./welcome.md
Email,FirstName,Name
alice@example.com,Alice,Alice Smith
bob@example.com,Bob,Bob Jones
--to-column(required with--csv) names the CSV column holding the recipient's email address.--name-column(optional) names the CSV column holding the recipient's display name. If omitted, or blank for a row, the email address is used with no display name.--toand--modelcannot be combined with--csv— the recipient and the per-row template values both come from the CSV.- A row with a blank
--to-columnvalue is skipped, not sent. Failed sends are logged and counted, and the batch continues to the next row. A summary line reports the outcome:Sent 48 of 50 emails from contacts.csv (1 skipped, 1 failed).
Token names are case-sensitive and must match the CSV header (or --model
JSON property) exactly — a column named FirstName only satisfies
{{FirstName}} in a template, not {{firstname}} or {{first_name}}.
Attachments
send, file, and preview all accept a repeatable --attach/-a option to attach one or more
local files:
karl send \
--from noreply@example.com \
--smtp-host smtp.example.com \
--to user@example.com \
--subject "Your invoice" \
--markdown ./invoice.md \
--attach ./invoice.pdf \
-a ./terms.pdf
- The display name and content type are inferred from each path (
invoice.pdfbecomesapplication/pdf); unrecognized extensions fall back toapplication/octet-stream. - A missing
--attachpath fails the whole command before anything is sent or rendered, the same as a missing--markdown/--model/--csvpath. --attachcomposes with--csv: the same set of attached files is sent with every row's personalized message.fileandpreviewonly list attachment names and content types in their output — they don't copy the attached bytes anywhere. Usesendagainst a test SMTP server to verify the actual file contents.
File Output Mode
karl file \
--from noreply@example.com \
--to user@example.com \
--subject "File output test" \
--body "This message is written to disk." \
--output ./mail-output
Written files are named with an email prefix by default (e.g. email_20260810_123456_789.txt).
There is no --file-name-prefix flag; set Karl:File:FileNamePrefix in a config file to change it.
StdOut Mode
karl preview \
--from noreply@example.com \
--to user@example.com \
--subject "Preview test" \
--body "This message is printed to stdout."
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.