Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Thi
### Added

- `openmail inbox create` and `openmail init` accept `--domain <domain>` to create an inbox on a verified custom domain you own. When omitted, the inbox uses your account default domain.
- `openmail usage` — per-inbox usage (inbound/outbound counts, attachment bytes, stored bytes) plus account totals, with `--from`, `--to`, and `--group-by inbox|account` flags.

---

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ openmail inbox create [--mailbox-name <name>] [--display-name <sender name>] [--
openmail inbox get --id inb_xxx
openmail inbox delete --id inb_xxx

# Per-inbox usage (emails + storage) for metering
openmail usage [--from <ISO8601>] [--to <ISO8601>] [--group-by inbox|account]

# Send email (uses default inbox from setup/init, or pass --inbox-id)
openmail send --to hello@example.com --subject "Hi" --body "Hello"

Expand Down
42 changes: 42 additions & 0 deletions src/commands/__tests__/usage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, expect, it, vi } from "vitest";
import { parseArgs } from "../../lib/args";
import { runUsageCommand } from "../usage";
import type { OpenMailHttpClient } from "../../lib/http";

function makeClient() {
return {
get: vi.fn().mockResolvedValue({ groupBy: "inbox", inboxes: [], totals: {} }),
} as unknown as OpenMailHttpClient & { get: ReturnType<typeof vi.fn> };
}

describe("runUsageCommand", () => {
it("passes from/to/group-by as query params", async () => {
const client = makeClient();
const parsed = parseArgs([
"usage",
"--from",
"2026-05-01T00:00:00Z",
"--to",
"2026-06-01T00:00:00Z",
"--group-by",
"account",
]);

await runUsageCommand(client, parsed);

expect(client.get).toHaveBeenCalledWith("/v1/usage", {
from: "2026-05-01T00:00:00Z",
to: "2026-06-01T00:00:00Z",
group_by: "account",
});
});

it("sends no query params when no flags are given", async () => {
const client = makeClient();
const parsed = parseArgs(["usage"]);

await runUsageCommand(client, parsed);

expect(client.get).toHaveBeenCalledWith("/v1/usage", {});
});
});
15 changes: 15 additions & 0 deletions src/commands/usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { ParsedArgs } from "../lib/args";
import { getStringFlag } from "../lib/args";
import type { OpenMailHttpClient } from "../lib/http";

export async function runUsageCommand(client: OpenMailHttpClient, parsed: ParsedArgs) {
const from = getStringFlag(parsed.flags, "from");
const to = getStringFlag(parsed.flags, "to");
const groupBy = getStringFlag(parsed.flags, "group-by");

return client.get("/v1/usage", {
...(from ? { from } : {}),
...(to ? { to } : {}),
...(groupBy ? { group_by: groupBy } : {}),
});
}
20 changes: 20 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { getBooleanFlag, parseArgs } from "./lib/args";
import { version } from "../package.json";
import { runInboxCommand } from "./commands/inbox";
import { runUsageCommand } from "./commands/usage";
import { runMessagesCommand } from "./commands/messages";
import { runThreadsCommand } from "./commands/threads";
import { runSendCommand } from "./commands/send";
Expand Down Expand Up @@ -162,6 +163,8 @@ async function main() {
});
} else if (command === "inbox") {
output = await runInboxCommand(client, parsed);
} else if (command === "usage") {
output = await runUsageCommand(client, parsed);
} else if (command === "send") {
const inboxId = await resolveInboxIdWithFallback({
client,
Expand Down Expand Up @@ -261,6 +264,7 @@ function printHelp(topic?: string) {
" status Show current OpenMail/OpenClaw runtime status",
" init Create a new inbox and set as default",
" inbox Manage inboxes",
" usage Show per-inbox usage (emails + storage)",
" send Send an email",
" messages List messages for an inbox",
" threads List/get threads",
Expand Down Expand Up @@ -359,6 +363,22 @@ function printHelp(topic?: string) {
return;
}

if (usage === "usage") {
process.stdout.write(
[
"openmail usage",
"",
"Usage:",
" usage [--from <ISO8601>] [--to <ISO8601>] [--group-by inbox|account]",
"",
"Shows per-inbox usage (inbound/outbound counts, attachment bytes, stored bytes) plus account totals. Defaults to the last 30 days; max window is 92 days. Use --json for machine-readable output.",
"",
...globalFlags,
].join("\n"),
);
return;
}

if (usage === "send") {
process.stdout.write(
[
Expand Down