Namespaces
Give each of your customers a namespace with its own domains, contacts, and templates, a key that reaches only that namespace, and a usage readback to bill from.
A namespace is a sub-tenant inside your organization. If you run a platform that sends email on behalf of your own customers, create one namespace per customer and one API key per namespace. That customer's key reaches their domains, senders, contacts, and templates and nothing else, and you read each customer's message counts to charge them. Samva bills your organization; the per-customer split is yours to read and bill.
Set up a customer
This walkthrough creates a namespace, issues it a scoped key, and sends from it with that key. Run the first two steps with a key that can manage the organization.
import { createClient } from "samva";
const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
// 1. One namespace per customer. `metadata` is yours; Samva never reads it.
const namespace = await samva.namespaces.create({
name: "Acme Corp",
metadata: { customerId: "cus_123" },
});
// 2. A key that reaches only that namespace.
const created = await samva.apiKeys.create({
name: "Acme Corp",
access: {
mode: "restricted",
namespaceId: namespace.id,
resources: { domains: "write", contacts: "write", templates: "write", email: "write" },
},
});
// 3. Work as the customer. Resources created with this key land in the namespace.
const acme = createClient({ apiKey: created.key });
await acme.email.addDomain({ domain: "mail.acme.example" });
await acme.contacts.create({
name: "Ada Lovelace",
identities: [{ type: "email", identifier: "ada@example.com" }],
});Publish the DNS records the domain returns and wait for it to verify, as in Verify your sending domain. Then send with the customer's key:
const message = await acme.email.send({
from: { email: "hello@mail.acme.example" },
to: "ada@example.com",
subject: "Welcome to Acme",
html: "<p>Thanks for signing up.</p>",
});Read the customer's usage with your own key:
const usage = await samva.namespaces.getUsage({
id: namespace.id,
after: "2026-10-01T00:00:00Z",
before: "2026-11-01T00:00:00Z",
});
console.log(usage.emailCount, usage.totalMessages);curl "https://api.samva.dev/v1/namespaces/$NAMESPACE_ID/usage?after=2026-10-01T00:00:00Z&before=2026-11-01T00:00:00Z" \
-H "X-API-Key: $SAMVA_API_KEY"The same steps are available from the CLI (samva namespaces create, then --namespace on the
domains, senders, contacts, and templates commands), from the hosted MCP server as the namespaces_*
tools, and in the dashboard under Namespaces, where the API key form has a namespace picker.
What belongs to a namespace
Domains, senders, contacts, and templates each belong to one namespace or to the organization
itself. A resource outside every namespace is at organization level: its namespaceId is null.
- Create a resource inside a namespace by passing
namespaceIdon its create call. A key confined to a namespace always creates inside it. - List endpoints take
?namespaceId=to filter to one namespace. A confined key always lists only its own. - A sender on a domain belongs to the domain's namespace.
- Mailboxes, API keys, and webhook endpoints keep the namespace they were created with and do not move.
- Defaults are per scope. Setting a default sender or domain clears only the other default in the same namespace, or at organization level.
The same email address can be a contact in several namespaces and at organization level, once in each. A contact in one namespace is a different contact from the one with the same address in another.
Confine a key to a namespace
A key created with access.namespaceId reaches only that namespace's resources. It never reaches
organization-level resources, and a resource in another namespace or at organization level answers
404, the same as one that does not exist.
Such a key may hold grants only on mailboxes, messages, email, domains, contacts, and
templates. Endpoints that cannot confine to a namespace answer 403 for it: contact groups and
lists, custom fields, campaigns, webhooks, analytics, and organization settings. Manage those with a
key that is not confined, and use a webhook endpoint filtered to the namespace to receive its events
(Webhooks).
Sends
Everything a send uses must sit in one namespace: the sender or domain, the template, and any contacts you name by id.
- A confined key sends in its own namespace. Naming anything outside it is a
404. - A key that is not confined may combine organization-level resources with one namespace's, never
with two namespaces. Mixing two is a
422. - Without
from, the send uses the namespace's default sender. A key that is not confined falls back to the organization's default sender when the namespace has none. A confined key with no default in its namespace must passfrom. - Recipients you name by address become contacts in the send's namespace. A recipient you name by contact id must already be in it.
Move resources between namespaces
Assign an existing resource to a namespace, or return it to organization level, with a key that manages the organization:
curl -X POST https://api.samva.dev/v1/namespaces/$NAMESPACE_ID/resources \
-H "X-API-Key: $SAMVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "type": "domain", "resourceId": "dom_..." }'
curl -X DELETE https://api.samva.dev/v1/namespaces/$NAMESPACE_ID/resources/domain/dom_... \
-H "X-API-Key: $SAMVA_API_KEY"type is domain, sender, contact, or template. Some resources bring others with them:
- A domain brings its senders. A sender on a domain cannot move alone; move the domain.
- A contact brings its addresses.
- A template brings every template in its project.
A moved resource stops being a default. Keys confined to the old namespace stop reaching it at once. An address the target namespace already holds conflicts.
Usage
GET /v1/namespaces/{id}/usage counts the namespace's messages by channel in the window you pass
as after and before. It counts the same statuses as organization usage. Read it before you delete
the namespace: a deleted namespace has no usage to read.
Webhooks
A webhook endpoint created with namespaceId receives only that namespace's events: message,
mailbox, contact, and domain events. An endpoint without a filter receives every event in the
organization. Manage endpoints with a key that is not confined to a namespace.
Delete a namespace
A namespace that still holds anything cannot be deleted. The conflict names what remains and how many of each. Delete those resources, or return movable ones to organization level, then delete the empty namespace.
The endpoints are in the namespaces API reference.
Mailbox keys, grants, and mailbox webhooks work inside a namespace as described in Agents, people, and keys.