Guides
Managing mailboxes, aliases and groups
The API that sends your email can also set up your company's mail: mailboxes, their alternate emails (aliases) and groups. This guide scripts the set-up of several domains and many mailboxes with the CLI or an SDK, in a way that can safely be run again.
Every change follows the console's rules, so a script can do exactly what its key's creator could do by hand. For a one-off import of a few users, the console's CSV upload does the same without a script. The endpoints are described in Mailboxes and Groups.
Before you start
- A full-access key. It acts with the console privileges of the person who created it: View, Create, Update and Suspend users, Reset user passwords, View and Manage groups. Deleting mailboxes and groups needs a key created by a company owner. See API keys.
- Verified domains. Mailboxes, alternate emails and groups can only use domains whose ownership the company has proven.
- Room in the plan. The plan sets the number of users and the largest mailbox. Mailbox sizes above
the plan's are lowered to it; a mailbox beyond the user limit is refused with
403 validation_error.
The examples read the key from TSMART_API_KEY:
npm install -g https://tsmart.ai/cli/tsmart-cli-1.1.0.tgz
export TSMART_API_KEY=ts_xxxxxxxx_… # or: tsmart login 1. Add and verify the domains
Add each domain, publish the ownership TXT record that the answer shows, then verify. Once ownership is proven
(status is no longer not_started), the domain is created on our mail server and can hold
mailboxes. A company can have at most 10 domains waiting for ownership verification at a time, so add a longer list
in rounds.
for d in acme.com acme.de acme.fr; do
tsmart domains create "$d" # prints the ownership record to publish
done
tsmart domains list # the ids
tsmart domains verify 6a0f3c1e-2b4d-4e8f-9a1b-3c5d7e9f1a2b With an SDK, verify every domain that is still waiting:
import { Tsmart } from "@tsmart/sdk";
const tsmart = new Tsmart();
const { data } = await tsmart.domains.list({ limit: 100 });
for (const d of data.data.filter((d) => d.status === "not_started")) {
await tsmart.domains.verify(d.id);
} Do not change the domains' MX records yet: incoming mail moves only when you switch MX, after the mailboxes exist (see step 5). Domains and DNS describes every record.
2. Create the mailboxes
Keep the users in a CSV file:
email,first_name,last_name,quota_mb
jane@acme.com,Jane,Doe,2048
sam@acme.com,Sam,Lee,2048
anna@acme.de,Anna,Schmidt,4096 Each new mailbox gets a 20-character temporary password, returned only in the answer that creates it. The user must choose a new password at the first sign-in. The scripts below write the passwords to a file that only you can read; give each user theirs over a safe channel, then delete the file. Keep the passwords out of logs and CI output.
// setup-mailboxes.mjs: node setup-mailboxes.mjs users.csv
import { appendFileSync, readFileSync } from "node:fs";
import { Tsmart } from "@tsmart/sdk";
const tsmart = new Tsmart(); // reads TSMART_API_KEY
const [, ...rows] = readFileSync(process.argv[2], "utf8").trim().split(/\r?\n/);
for (const row of rows) {
const [email, first_name, last_name, quota_mb] = row.split(",");
const { data, error } = await tsmart.mailboxes.create({ email, first_name, last_name, quota_mb: Number(quota_mb) });
if (error?.name === "already_exists") {
console.log("exists ", email); // created on an earlier run
} else if (error) {
console.error("failed ", email, error.name, error.message);
if (error.statusCode === 403) break; // missing privilege, plan limit or inactive subscription: stop
} else {
appendFileSync("passwords.csv", `${data.email},${data.temporary_password}\n`, { mode: 0o600 });
console.log("created", email);
}
} # setup_mailboxes.py: python setup_mailboxes.py users.csv
import csv
import os
import sys
from tsmart import Tsmart, TsmartError
client = Tsmart() # reads TSMART_API_KEY
fd = os.open("passwords.csv", os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
with open(sys.argv[1], newline="") as users, os.fdopen(fd, "a") as passwords:
for row in csv.DictReader(users):
try:
box = client.mailboxes.create({
"email": row["email"],
"first_name": row["first_name"],
"last_name": row["last_name"],
"quota_mb": int(row["quota_mb"]),
})
except TsmartError as e:
if e.name == "already_exists":
print("exists ", row["email"]) # created on an earlier run
continue
print("failed ", row["email"], e.name, e.message, file=sys.stderr)
if e.status_code == 403:
break # missing privilege, plan limit or inactive subscription: stop
continue
passwords.write("%s,%s\n" % (box["email"], box["temporary_password"]))
print("created", box["email"]) The same with the CLI, in a shell loop. It prints the temporary password on standard output only:
umask 077 # passwords.txt is readable by you only
tail -n +2 users.csv | while IFS=, read -r email first last quota; do
out=$(tsmart mailboxes create "$email" --first-name "$first" --last-name "$last" --quota-mb "$quota") || continue
echo "$email $(echo "$out" | sed -n 's/^temporary_password: *//p')" >> passwords.txt
done
Add org_unit_id (--org-unit-id) to place a mailbox in an organisational unit; it then takes
that unit's storage and mail app policies at once. Without it, the mailbox goes to the root unit. A
quota_mb (256 to 51200) above the plan's mailbox size is lowered to it; without one, the company's default
size applies.
3. Add alternate emails
An alternate email delivers to a mailbox, and the mailbox may also send as it. It can be in any of the company's verified domains, so one mailbox can receive mail for the same name in several domains.
tsmart mailboxes alias-add jane@acme.com j.doe@acme.com
tsmart mailboxes alias-add jane@acme.com jane@acme.de
tsmart mailboxes aliases jane@acme.com const aliases = { "jane@acme.com": ["j.doe@acme.com", "jane@acme.de"], "sam@acme.com": ["s.lee@acme.com"] };
for (const [mailbox, list] of Object.entries(aliases)) {
for (const address of list) {
const { error } = await tsmart.mailboxes.aliases.create(mailbox, { address });
if (error && error.name !== "already_exists") console.error(address, error.name, error.message);
}
} 4. Create the groups
A group delivers each message to all its members: up to 200 mailboxes of the company or outside addresses. With the CLI, members can come from a file with one address per line:
tsmart groups create team@acme.com --name "Team" --member jane@acme.com,sam@acme.com
tsmart groups create all@acme.com --name "All staff" --members-file staff.txt
tsmart groups update team@acme.com --add-member anna@acme.de --remove-member sam@acme.com
tsmart groups get team@acme.com members in an update is always the complete list. To make a script safe to run again, create the group
and, when it already exists, send the full list:
def ensure_group(address, name, members):
try:
return client.groups.create({"address": address, "name": name, "members": members})
except TsmartError as e:
if e.name != "already_exists":
raise
return client.groups.update(address, {"name": name, "members": members}) # bring it in line
ensure_group("team@acme.com", "Team", ["jane@acme.com", "anna@acme.de"]) already_exists also answers when the address belongs to a mailbox or an alternate email; the update then
answers 404 not_found. A group without members discards its mail.
Check the result
Mailboxes and groups are listed by address, and a page is continued with the last address as the cursor.
domain narrows a list to one domain.
tsmart mailboxes list --domain acme.com --limit 100
tsmart groups list --domain acme.com let after;
do {
const { data, error } = await tsmart.mailboxes.list({ domain: "acme.com", limit: 100, after });
if (error) throw new Error(error.message);
for (const m of data.data) console.log(m.email, m.quota_mb, m.aliases.join(" "));
after = data.has_more ? data.data.at(-1).email : undefined;
} while (after); 5. Switch incoming mail
When the mailboxes, alternate emails and groups are in place, point each domain's MX record to
mail.tsmart.ai (see MX). Mail for the domain then arrives in the new
mailboxes. Do it on the day you switch: changing MX moves all incoming mail of the domain.
Day-to-day changes
tsmart mailboxes update jane@acme.com --name "Jane Smith" --quota-mb 4096
tsmart mailboxes update sam@acme.com --active off # suspend: mail is still received, no sign-in
tsmart mailboxes update sam@acme.com --active on # restore
tsmart mailboxes reset-password jane@acme.com --yes # a new temporary password, shown once
tsmart mailboxes alias-remove jane@acme.com jane@acme.de
tsmart groups delete team@acme.com --yes # company owners only
tsmart mailboxes delete sam@acme.com --yes # with all its mail; company owners only Errors to expect
| Status | Name | Meaning and what to do |
|---|---|---|
| 409 | already_exists | The address is already a mailbox, a group or an alternate email. On a re-run, skip it. |
| 422 | validation_error | An address outside the company's verified domains, a part before @ that is not allowed, a size out of range, or a bad member list. Fix the input. |
| 403 | validation_error | The plan's user limit is reached. Stop, and change the plan in the console. |
| 403 | invalid_permission | The key's creator lacks the privilege, or the right for that organisational unit. Use a key of an admin who has it. |
| 403 | account_inactive | The company's subscription is not active. |
| 404 | not_found | The mailbox or group does not exist in the company's domains. |
| 429 | rate_limit_exceeded | More than 10 requests per second. The SDKs and the CLI wait and retry for you. |
| 502 | mail_server_error | The mail server refused the change; the message carries its error codes. Read the object again, then retry or contact support. |