Examples

Create a domain and mailbox

Complete production workflow for creating a Hosting domain and its first mailbox.

This production example uses the Management API to create a Hosting domain, verify domain ownership with DNS, wait for activation, create the first mailbox, and update its quota.

Workflow and API calls

StepAPI callWhat happens
1POST /api/domainsCreate the Hosting domain and postmaster account.
2GET /api/domains/:codeRead the domain status and DNS ownership data.
3Manual DNS changePublish the ownership DNS record in the domain DNS zone.
4PUT /api/domains/:code/dns_ownership_checkAsk Qboxmail to verify DNS ownership and queue activation.
5GET /api/domains/:codePoll until the domain is enabled with status_detail: ok.
6POST /api/domains/:code/email_accountsCreate the first mailbox on the active domain.
7GET /api/domains/:code/email_accounts/:email_account_codePoll until the mailbox is active.
8PUT /api/domains/:code/email_accounts/:email_account_codeOptionally update mailbox settings, such as quota.

Before you start

  • Use a production Management API token in the X-Api-Token header.
  • Make sure the customer can create paid Hosting resources. If required, complete the initial order in the Qboxmail panel before provisioning by API.
  • Have access to the domain DNS zone. Production uses real DNS ownership verification; do not use the sandbox-only skip ownership endpoint.
  • If your token belongs to an admin, set CUSTOMER_CODE. If it belongs directly to the customer, leave it empty.
Use a disposable domain while testing

This example creates real production resources. Use a test domain first, then run the same workflow for customer domains when your integration is ready.

Full TypeScript example

import { stdin as input, stdout as output } from "node:process";
import { createInterface } from "node:readline/promises";

type Json = Record<string, unknown>;

type CreatedResource = {
  message: string;
  resource_code: string;
};

type ResourcesResponse<T> = {
  resources: T[];
  pagination?: Record<string, unknown>;
};

type StatusResource = {
  status: string;
  status_detail: string;
};

type Domain = StatusResource & {
  code: string;
  name: string;
  customer_code?: string;
  ownership_verification_random_code?: string;
  possession_a_record?: string;
};

type EmailAccount = StatusResource & {
  code: string;
  name: string;
  email_address: string;
  max_email_quota: number;
};

const API_BASE = (process.env.QBOXMAIL_API_BASE ?? "https://api.qboxmail.com/api").replace(/\/$/, "");
const API_TOKEN = requiredEnv("QBOXMAIL_API_TOKEN");
const DOMAIN_NAME = requiredEnv("DOMAIN_NAME");
const POSTMASTER_PASSWORD = requiredEnv("POSTMASTER_PASSWORD");
const MAILBOX_PASSWORD = requiredEnv("MAILBOX_PASSWORD");

const CUSTOMER_CODE = process.env.CUSTOMER_CODE;
const DOMAIN_PLAN = process.env.DOMAIN_PLAN ?? "basic";
const MAILBOX_NAME = process.env.MAILBOX_NAME ?? "hello";
const UPDATED_MAILBOX_QUOTA_BYTES = Number(process.env.UPDATED_MAILBOX_QUOTA_BYTES ?? 2_147_483_648);

function requiredEnv(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`Missing env var: ${name}`);
  return value;
}

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function parseBody(text: string): unknown {
  try {
    return JSON.parse(text);
  } catch {
    return text;
  }
}

async function api<T>(path: string, options: { method?: string; body?: Json } = {}): Promise<T> {
  const method = options.method ?? "GET";
  const response = await fetch(`${API_BASE}${path}`, {
    method,
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      "X-Api-Token": API_TOKEN,
    },
    body: options.body === undefined ? undefined : JSON.stringify(options.body),
  });

  if (response.status === 204) return undefined as T;

  const text = await response.text();
  const payload = text ? parseBody(text) : undefined;

  if (!response.ok) {
    throw new Error(`${method} ${path} failed: ${response.status} ${JSON.stringify(payload)}`);
  }

  return payload as T;
}

function firstResource<T>(response: ResourcesResponse<T>): T {
  const resource = response.resources[0];
  if (!resource) throw new Error("API returned no resources");
  return resource;
}

async function waitFor<T extends StatusResource>(label: string, load: () => Promise<T>, ready: (resource: T) => boolean) {
  const deadline = Date.now() + 30 * 60_000;

  while (Date.now() < deadline) {
    const resource = await load();

    if (ready(resource)) return resource;

    console.log(`${label} not ready yet: ${resource.status}/${resource.status_detail}`);
    await sleep(15_000);
  }

  throw new Error(`Timed out waiting for ${label}`);
}

async function waitForEnter(message: string) {
  const rl = createInterface({ input, output });
  await rl.question(message);
  rl.close();
}

async function createDomain(): Promise<string> {
  const created = await api<CreatedResource>("/domains", {
    method: "POST",
    body: {
      name: DOMAIN_NAME,
      plan: DOMAIN_PLAN,
      ...(CUSTOMER_CODE ? { customer_code: CUSTOMER_CODE } : {}),
      postmaster_password: POSTMASTER_PASSWORD,
      postmaster_password_confirmation: POSTMASTER_PASSWORD,
    },
  });

  console.log(`Domain created: ${DOMAIN_NAME} (${created.resource_code})`);
  return created.resource_code;
}

async function getDomain(domainCode: string): Promise<Domain> {
  return firstResource(await api<ResourcesResponse<Domain>>(`/domains/${encodeURIComponent(domainCode)}`));
}

async function waitForDnsOwnershipData(domainCode: string): Promise<Domain> {
  return waitFor("DNS ownership data", () => getDomain(domainCode), (domain) => Boolean(domain.possession_a_record));
}

async function verifyDomainOwnership(domainCode: string): Promise<void> {
  await api<void>(`/domains/${encodeURIComponent(domainCode)}/dns_ownership_check`, { method: "PUT" });
  console.log("DNS ownership check queued. Waiting for domain activation.");
}

async function waitForDomainReady(domainCode: string): Promise<Domain> {
  return waitFor(
    "domain activation",
    () => getDomain(domainCode),
    (domain) => domain.status === "enabled" && domain.status_detail === "ok",
  );
}

async function createFirstEmailAccount(domainCode: string): Promise<string> {
  const created = await api<CreatedResource>(`/domains/${encodeURIComponent(domainCode)}/email_accounts`, {
    method: "POST",
    body: {
      name: MAILBOX_NAME,
      firstname: "Example",
      lastname: "User",
      password: MAILBOX_PASSWORD,
      password_confirmation: MAILBOX_PASSWORD,
    },
  });

  console.log(`Mailbox creation queued: ${MAILBOX_NAME}@${DOMAIN_NAME} (${created.resource_code})`);
  return created.resource_code;
}

async function getEmailAccount(domainCode: string, emailCode: string): Promise<EmailAccount> {
  return firstResource(
    await api<ResourcesResponse<EmailAccount>>(
      `/domains/${encodeURIComponent(domainCode)}/email_accounts/${encodeURIComponent(emailCode)}`,
    ),
  );
}

async function waitForEmailAccountReady(domainCode: string, emailCode: string): Promise<EmailAccount> {
  return waitFor(
    "mailbox activation",
    () => getEmailAccount(domainCode, emailCode),
    (email) => email.status === "enabled" && email.status_detail === "ok",
  );
}

async function updateEmailAccountQuota(domainCode: string, emailCode: string, quotaBytes: number): Promise<void> {
  await api<void>(`/domains/${encodeURIComponent(domainCode)}/email_accounts/${encodeURIComponent(emailCode)}`, {
    method: "PUT",
    body: { max_email_quota: quotaBytes },
  });

  console.log(`Mailbox quota update queued: ${quotaBytes} bytes`);
}

async function waitForEmailAccountQuotaUpdate(
  domainCode: string,
  emailCode: string,
  quotaBytes: number,
): Promise<EmailAccount> {
  return waitFor(
    "mailbox quota update",
    () => getEmailAccount(domainCode, emailCode),
    (email) =>
      email.status === "enabled" && email.status_detail === "ok" && email.max_email_quota === quotaBytes,
  );
}

async function main() {
  const domainCode = await createDomain();
  const domainForDns = await waitForDnsOwnershipData(domainCode);

  console.log("Publish the DNS ownership record requested by Qboxmail before continuing.");
  console.log({
    domain: domainForDns.name,
    ownershipCode: domainForDns.ownership_verification_random_code,
    expectedARecord: domainForDns.possession_a_record,
  });

  await waitForEnter("Press Enter after the DNS ownership record resolves...");
  await verifyDomainOwnership(domainCode);

  const domain = await waitForDomainReady(domainCode);
  const emailCode = await createFirstEmailAccount(domain.code);
  const email = await waitForEmailAccountReady(domain.code, emailCode);

  await updateEmailAccountQuota(domain.code, email.code, UPDATED_MAILBOX_QUOTA_BYTES);
  const updatedEmail = await waitForEmailAccountQuotaUpdate(domain.code, email.code, UPDATED_MAILBOX_QUOTA_BYTES);

  console.log({
    domainCode: domain.code,
    emailCode: updatedEmail.code,
    emailAddress: updatedEmail.email_address,
    quotaBytes: updatedEmail.max_email_quota,
  });
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

Run it

Save the code above as create-domain-and-mailbox.ts, then run:

export QBOXMAIL_API_TOKEN="<MANAGEMENT_API_TOKEN>"
export DOMAIN_NAME="example.com"
export POSTMASTER_PASSWORD="<temporary alphanumeric password>"
export MAILBOX_PASSWORD="<temporary alphanumeric password>"

# Optional
export CUSTOMER_CODE="<CUSTOMER_CODE>"
export DOMAIN_PLAN="basic"
export MAILBOX_NAME="hello"
export UPDATED_MAILBOX_QUOTA_BYTES="2147483648"

npx tsx create-domain-and-mailbox.ts

The script pauses after domain creation so you can publish the DNS ownership record. After the DNS record resolves, press Enter. The script then continues with ownership verification, activation polling, mailbox creation, and quota update.

On this page