I have a rule I keep breaking: never give a website my real email if I'm not sure I want a relationship with them. Five minutes later I'm on some forum, signup form open, and I'm typing me@gmail.com again because firing up a password manager, generating an alias, copying it, pasting it - it's just enough friction that I default to the lazy thing.
So this weekend I built alias - a single shell command that spits out a fresh forwarding address I can paste anywhere. Took about an hour. The whole thing is curl, jq, and one POST request.
I'm using anon.li as the backend because (a) it has a clean REST API, (b) it's open source, and (c) the free tier covers personal use. But the structure here works against any forwarding service that exposes an API - Addy, SimpleLogin, your own self-hosted thing.
The endpoint
The Alias API has a Bitwarden-compatible "just generate me one" endpoint. From the docs:
POST https://anon.li/api/v1/alias?generate=true
Authorization: Bearer ak_...
You can pass an optional domain in the body if you've added a custom one. With no body, it returns a random alias on anon.li. The response is the canonical alias object - id, email, timestamps, the works.
That's literally the entire API surface for what I want. One call, one address.
Step 1: get a key
Sign up, go to Dashboard → API Keys, generate one. It'll look like ak_ followed by 32 hex characters. Copy it once, because anon.li only shows the SHA-256 hash on their side after that - if you lose it, you rotate.
I stash mine in ~/.config/anon/key:
mkdir -p ~/.config/anon
echo "ak_yourkeyhere" > ~/.config/anon/key
chmod 600 ~/.config/anon/key
If you're on a multi-user machine or just paranoid, swap this for pass, 1Password CLI, gpg --decrypt, whatever. Don't put it in .bashrc in plaintext.
Step 2: the function
Drop this into ~/.bashrc (or ~/.zshrc):
alias() {
local key
key=$(cat ~/.config/anon/key 2>/dev/null) || {
echo "no api key at ~/.config/anon/key" >&2
return 1
}
local domain="${1:-anon.li}"
local response
response=$(curl -sS -X POST \
"https://anon.li/api/v1/alias?generate=true" \
-H "Authorization: Bearer $key" \
-H "Content-Type: application/json" \
-d "{\"domain\":\"$domain\"}")
local email
email=$(echo "$response" | jq -r '.data.email // empty')
if [[ -z "$email" ]]; then
echo "alias creation failed:" >&2
echo "$response" >&2
return 1
fi
echo -n "$email" | (command -v pbcopy >/dev/null && pbcopy \
|| command -v wl-copy >/dev/null && wl-copy \
|| command -v xclip >/dev/null && xclip -selection clipboard)
echo "$email (copied)"
}
Reload your shell. Now:
$ alias
x7k9m2@anon.li (copied)
That's it. Forty-something lines of Bash, one network call, address on my clipboard. I haven't typed gmail.com into a signup form in six weeks.
Heads up:
aliasis a Bash builtin (the thing you use foralias ll='ls -la'). Defining a function with the same name shadows it inside your own shell, which is fine for personal use, but if you script anything that depends onaliasthe builtin, name yoursakaorallior whatever. I personally don't care.
Why not just use the dashboard?
I do, for ones I want to label and track. But for a throwaway - "I just want to read this gated article" - I don't need to think about what to call it. I want it on my clipboard before I've finished alt-tabbing back to the browser. The CLI wins on that exact use case.
The dashboard is also where I go later to disable the alias when the site inevitably starts spamming me. Toggling active: false is one PATCH request, but I rarely bother scripting that part - I'm already in the dashboard reading the spam.
Bonus: domain switcher
If you've added a custom domain (say, mail.example.dev), you can pass it as an argument:
$ alias mail.example.dev
random-suffix@mail.example.dev (copied)
I use this for my "real but disposable" identity - the one I give to recruiters and conference signups. Random-looking but on a domain I own, so it doesn't trip the "is this a burner?" filters that some sites run against known alias domains.
Bonus 2: skip the CLI, use Bitwarden
If you live in Bitwarden anyway, anon.li implements the Addy.io-compatible flow. In Settings → Options → Username Generator, pick Forwarded Email Alias → addy.io, paste your API key, and set the URL to https://anon.li/api/v1. Now the username field on every "create new login" sheet has a generate button that talks to the same endpoint. That's the fully no-friction version.
The CLI still wins for terminal flows - quick test signups, registering API sandboxes, anywhere I'm not in the password manager UI.
What's actually happening behind the curtain
The Alias backend isn't a hosted mailbox. There's no inbox. anon.li's mail server takes the inbound message, optionally encrypts the forwarded copy with your recipient's PGP key, signs it with DKIM, rewrites the envelope sender (SRS) so SPF still aligns, and ships it to your real address. The only state stored per alias is aggregate counters: received, blocked, last-seen-at.
That trust model is what makes me comfortable using disposable aliases for anything moderately sensitive - receipts, account confirmations, the dentist. Even if anon.li got fully owned tomorrow, there's no historical archive of my mail to leak. Just the routing config.
It's not a replacement for E2EE messaging. SMTP is what it is. But it raises the floor by a meaningful amount, and the API makes it easy enough that I actually use it instead of giving up and pasting my real address.
The whole script
For copy-paste convenience:
# ~/.bashrc
alias() {
local key
key=$(cat ~/.config/anon/key 2>/dev/null) || {
echo "no api key at ~/.config/anon/key" >&2
return 1
}
local domain="${1:-anon.li}"
local response
response=$(curl -sS -X POST \
"https://anon.li/api/v1/alias?generate=true" \
-H "Authorization: Bearer $key" \
-H "Content-Type: application/json" \
-d "{\"domain\":\"$domain\"}")
local email
email=$(echo "$response" | jq -r '.data.email // empty')
if [[ -z "$email" ]]; then
echo "alias creation failed:" >&2
echo "$response" >&2
return 1
fi
echo -n "$email" | (command -v pbcopy >/dev/null && pbcopy \
|| command -v wl-copy >/dev/null && wl-copy \
|| command -v xclip >/dev/null && xclip -selection clipboard)
echo "$email (copied)"
}
Friction is the enemy of good security habits. The smaller you can make the gap between "I want to do the right thing" and "the right thing is done," the more often you'll do it. A single shell function won me back the habit. Try it.
Top comments (0)