Stalwart Alias Script
there is a longer write-up about this script available: see post
but this is the latest version of my script, which I try to update whenever possible
create_alias.py
import json
import os
import subprocess
import sys
import pyperclip
# --------------------
# stalwart server should be configured as mail server for DOMAIN
# and live on HOSTNAME, like mail.example.de should be reachable for jmap etc.
# and there must be a MX record for a.example.de (just let stalwart handle the details)
# and after setting it up add a new domain (a.example.de), set the MX record and it should be done
HOSTNAME = "mail.example.de"
DOMAIN = "a.example.de"
TOKEN = "API_AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" # your own API token <WEBINTERFACE>/account/Account/x:ApiKey
USERNAME = "user" # your username (like if you usually use me@example.de for your mails (example.de is your primary mail), then this field would be 'me')
ALIAS_POSTFIX = "" # e.g. -alias, or leave blank, I recommend setting this to set if you set the DOMAIN to your main domain
# which is also possible, in that case you won't need to add any records
CACHE_FILE = ".stalwart-alias-cache.json" # just to make it more efficient, as the stalwart-cli is somewhat slow otherwise handling the commands in succession
# --------------------
# Usage: uv run create_alias.py <alias_prefix>
# or uv run create_alias.py
# ------------
if not len(sys.argv) > 1:
new_alias_prefix = input("alias: ")
else:
new_alias_prefix = sys.argv[1]
user_id_cached = None
domain_id_cached = None
if os.path.isfile(CACHE_FILE):
with open(CACHE_FILE, "r") as f:
cache = json.load(f)
if "users" in cache and USERNAME in cache["users"]:
user_id_cached = cache["users"][USERNAME]
if "domains" in cache and DOMAIN in cache["domains"]:
domain_id_cached = cache["domains"][DOMAIN]
def run_stalwart_cmd(cmd, nojson=False):
env = os.environ.copy()
env["STALWART_URL"] = f"https://{HOSTNAME}"
env["STALWART_TOKEN"] = TOKEN
cmd = "stalwart-cli " + cmd
if not nojson:
cmd += " --json"
# print(cmd)
result = subprocess.check_output(
cmd,
env=env,
shell=True,
text=True,
)
if nojson:
return result
else:
return json.loads(result)
if not user_id_cached:
user_account = run_stalwart_cmd(
f"query Account --where name={USERNAME} --fields id,name,emailAddress"
)
user_id = user_account["id"]
print(f"[+] obtained user id: {user_id}")
else:
user_id = user_id_cached
print(f"[+] loaded user id from cache: {user_id}")
if not domain_id_cached:
domain_id = run_stalwart_cmd(
f"query Domain --fields id,name --where name={DOMAIN}"
)["id"]
print(f"[+] obtained domain id: {domain_id}")
else:
domain_id = domain_id_cached
print(f"[+] loaded domain id from cache: {domain_id}")
user_aliases = run_stalwart_cmd(f"get Account {user_id} --fields aliases")["aliases"]
if len(list(map(int, list(user_aliases)))) == 0:
new_alias_id = "0"
else:
new_alias_id = str(max(list(map(int, list(user_aliases)))) + 1)
alias_list = [user_aliases[a]["name"] for a in user_aliases]
print(f"[+] obtained next alias id: {new_alias_id}")
new_alias = f"{new_alias_prefix}{ALIAS_POSTFIX}"
if new_alias in alias_list:
print("[-] FAILURE: alias already exists! - exiting")
exit()
print(f"[+] alias can be created: {new_alias}@{DOMAIN}")
alias_patch = {
f"aliases/{new_alias_id}": {
"enabled": True,
"name": new_alias,
"domainId": domain_id,
"description": None,
}
}
result = run_stalwart_cmd(
"update Account " + user_id + " --json '" + json.dumps(alias_patch) + "'",
nojson=True,
)
if result.startswith("Updated"):
print("[+] created alias - copying to clipboard")
cache = {"users": {USERNAME: user_id}, "domains": {DOMAIN: domain_id}}
with open(CACHE_FILE, "w") as f:
json.dump(cache, f, indent=4)
pyperclip.copy(f"{new_alias}@{DOMAIN}")
else:
print(f"[-] FAILURE: {result}")