Email yourself from any shell or AI agent — one tiny command over Resend.
Project description
selfmail
Email yourself from any shell or AI agent — one tiny command, over Resend.
uvx selfmail "the build is green"
selfmail sends an email to a pre-configured "me" address with no ceremony. It runs
anywhere — your laptop, a cron job, a cloud runner, or an AI agent — with no GUI and no
local mail server. After a one-time selfmail init, sending is a single command.
Install
selfmail is a single pure-Python package on PyPI. The zero-install path is best:
uvx selfmail "hello" # run without installing (recommended)
uv tool install selfmail # install the `selfmail` command on PATH
pipx install selfmail # same, via pipx
For the MCP server, pull the optional extra:
uvx --from 'selfmail[mcp]' selfmail mcp
uv tool install 'selfmail[mcp]'
Requires Python 3.11+.
Quick start
selfmail init # paste a Resend API key once — it emails you a test message
selfmail "done" # send yourself a note
init deep-links you to the Resend create-key page, captures the key without echoing
it, and proves the whole pipeline by emailing you on the spot — no DNS setup needed
(Resend's shared sender delivers to your own account address).
Commands
selfmail [body] send an email (this is the default command)
selfmail init configure: paste a Resend key, get a test email
selfmail doctor check configuration without sending
selfmail mcp run an MCP server exposing a send_email tool
selfmail --version print the version
Run selfmail <command> --help for a command's options.
selfmail [body] — send
The default command. With no subcommand, selfmail sends an email.
selfmail [-s SUBJECT] [-a FILE]... [--html] [--to TO] [--from SENDER] [-q] [body]
| Argument / flag | Description |
|---|---|
body |
The message body. Optional — if omitted and stdin is not a TTY, the body is read from stdin. If neither is given, the body is (no body). |
-s, --subject TEXT |
Subject line. Default: a timestamp like selfmail 2026-06-16 14:05. |
-a, --attach FILE |
Attach a file. Repeatable: -a a.pdf -a b.png. |
--html |
Treat the body as HTML (sets the HTML part instead of plain text). |
--to ADDR |
Override the configured recipient for this send. |
--from ADDR |
Override the configured sender for this send. |
-q, --quiet |
Suppress the sent → … success line. |
On success it prints sent → you@example.com (id <message-id>) and exits 0.
Examples
selfmail -s "build finished" "all green on main" # subject + inline body
echo "$(tail -50 build.log)" | selfmail -s "log tail" # body from stdin (pipe)
some-command 2>&1 | selfmail -s "cron output" # capture a command's output
selfmail -s "the report" -a report.html "see attached" # with an attachment
selfmail --html "<h1>Shipped</h1><p>v1.2 is live.</p>" # HTML body
selfmail --to ops@example.com "heads up" # one-off different recipient
selfmail -q "silent ping" # no stdout on success
selfmail init — configure
Interactive first-run setup. Resend has no OAuth or programmatic key provisioning, so
the key is minted in your dashboard and pasted in — init makes that one step painless.
selfmail init [--force] [--domain]
| Flag | Description |
|---|---|
--force |
Reconfigure even if a working config already exists. |
--domain |
Also print the steps to verify your own sending domain. |
What it does:
- If you're already configured and the key authenticates, it says so and exits
(unless
--force). - Deep-links you to
https://resend.com/api-keys?new=true(opens the create-key dialog directly), and tells you to create a key with Sending access. - Reads the key with no echo (never shown, never stored in shell history).
- Asks for your email — where
selfmailshould send. - Sends a real test email from the shared sender to that address (works with no domain), which both validates the key and proves delivery. A bad key re-prompts; nothing is saved until a send succeeds.
- Asks which address to send from. There is no silent default: you either accept
the shared
onboarding@resend.dev(self-only) or enter your own verified-domain sender. - Writes the config file with
0600permissions.
selfmail doctor — check configuration
Validates the resolved configuration without sending. Prints a summary and exits
0 if usable, 2 otherwise. Never prints the secret.
$ selfmail doctor
backend: resend
from: onboarding@resend.dev
to: you@example.com
api key: present
auth: ok
config: /Users/you/.config/selfmail/config.toml
selfmail mcp — MCP server
Runs a Model Context Protocol server over stdio,
exposing a single tool so AI agents can email you directly. Requires the mcp extra
(selfmail[mcp]); without it, the command exits 2 with an install hint.
uvx --from 'selfmail[mcp]' selfmail mcp
Tool:
send_email(subject: str, body: str, to?: str, html?: bool) -> {"id": str, "to": str}
It uses the same configuration as the CLI — no separate setup.
Configuration
Values are resolved in this order (highest priority first):
command-line flag → environment variable → config file → built-in default
Config file
Written by selfmail init to ~/.config/selfmail/config.toml (or
$XDG_CONFIG_HOME/selfmail/config.toml), with mode 600:
to = "you@example.com"
from = "onboarding@resend.dev"
[resend]
api_key = "re_xxx"
Environment variables
For CI, cron, or cloud boxes, skip the file entirely and set:
| Variable | Maps to |
|---|---|
RESEND_API_KEY |
the Resend API key |
SELFMAIL_TO |
the recipient ("me") |
SELFMAIL_FROM |
the sender — required (there is no built-in default; init sets it) |
SELFMAIL_CONFIG |
full path to an alternate config file |
export RESEND_API_KEY=re_...
export SELFMAIL_TO=you@example.com
selfmail "from CI"
Sending to addresses other than yourself
Resend's shared sender, onboarding@resend.dev, only delivers to your own Resend
account address — which is all "email myself" needs, and what init offers by default.
To send from your own domain (and to anyone), verify a domain in Resend and set from
accordingly. If your DNS is on
Cloudflare, selfmail init --domain points you at Resend's one-click Cloudflare
integration, which auto-creates the MX/SPF/DKIM records.
Exit codes
| Code | Meaning |
|---|---|
0 |
Success. |
1 |
The send failed (network error, or Resend rejected the message). |
2 |
Configuration or usage error (no key, bad flag, missing recipient, unreadable attachment). |
For agents
selfmail is built to be driven by AI agents two ways:
- As a shell command — any agent that can run a shell can
selfmail -s "…" "…". - As an MCP server —
selfmail mcpexposes thesend_emailtool over stdio.
Because it reads config from the environment, an agent in a headless sandbox just needs
RESEND_API_KEY and SELFMAIL_TO set to start emailing you.
Development
uv sync # install deps (incl. dev group)
uv run ruff check . # lint
uv run ruff format . # format
uv run ty check # type-check
uv run pytest # tests (unit tests mock the Resend SDK boundary)
There's an opt-in end-to-end test that sends a real email — skipped unless you provide real credentials:
SELFMAIL_LIVE_TEST=1 RESEND_API_KEY=re_... SELFMAIL_LIVE_TO=you@example.com \
SELFMAIL_FROM=you@yourdomain.com uv run pytest -k live
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file selfmail-0.1.1.tar.gz.
File metadata
- Download URL: selfmail-0.1.1.tar.gz
- Upload date:
- Size: 72.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f4677f6f9cc6aa8b536301dec088521fd92f359070d1d0d90f99b97bf6c31ee
|
|
| MD5 |
6fb49400808e0a36b86a17544bae1184
|
|
| BLAKE2b-256 |
49dc597ebbd79de719b98b97dce57d8ac612d0140601374b2315d876d6061ba9
|
Provenance
The following attestation bundles were made for selfmail-0.1.1.tar.gz:
Publisher:
release.yml on alltuner/selfmail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
selfmail-0.1.1.tar.gz -
Subject digest:
7f4677f6f9cc6aa8b536301dec088521fd92f359070d1d0d90f99b97bf6c31ee - Sigstore transparency entry: 1839889386
- Sigstore integration time:
-
Permalink:
alltuner/selfmail@eec5a61b9964c625dcf249bac7677124053b4cc7 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/alltuner
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eec5a61b9964c625dcf249bac7677124053b4cc7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file selfmail-0.1.1-py3-none-any.whl.
File metadata
- Download URL: selfmail-0.1.1-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fccf0efe65e44e152ddb38ea64965912ec0cebf57f8e598408023efecde25827
|
|
| MD5 |
512566a199d5662d0324efaa214debe6
|
|
| BLAKE2b-256 |
46b81e37485abcc0af67ebb2f9443a9910017820a9486aa9f26fe07f6916b527
|
Provenance
The following attestation bundles were made for selfmail-0.1.1-py3-none-any.whl:
Publisher:
release.yml on alltuner/selfmail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
selfmail-0.1.1-py3-none-any.whl -
Subject digest:
fccf0efe65e44e152ddb38ea64965912ec0cebf57f8e598408023efecde25827 - Sigstore transparency entry: 1839889399
- Sigstore integration time:
-
Permalink:
alltuner/selfmail@eec5a61b9964c625dcf249bac7677124053b4cc7 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/alltuner
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eec5a61b9964c625dcf249bac7677124053b4cc7 -
Trigger Event:
push
-
Statement type: