Compose and send
Send new messages, reply or forward in context, and save drafts while tracking approvals and delivery waits.
Start with an existing mailbox and a client allowed to send from it. A mailbox-scoped key needs
send; saving or editing drafts also needs update. The mailbox chooses the sending address
through its default From identity.
Agents and keys explains how to limit access and require approval.
Send a new message
Supply recipients, a subject, and at least one of text or html. Choose a stable idempotency key
for this intentional send and keep it if you retry the same request.
import { createClient } from "samva";
const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const id = "mbx_...";
const receipt = await samva.mailboxes.sendMessage({
id,
to: ["customer@example.com"],
subject: "Your support request",
text: "We are looking into your request.",
"idempotency-key": "support-request-123-first-response",
});
console.log(receipt.status, receipt.actionId, receipt.waitReason);Optional cc, bcc, and replyTo address lists let you include other recipients or choose where
answers return. See send a message for the request.
Reply or forward in context
Use a message ID from reading the conversation.
reply addresses the message's Reply-To, or its sender when Reply-To is absent, and sets the
subject and threading headers. replyAll also includes the other recipients and excludes the
mailbox's own addresses.
const messageId = "msg_...";
await samva.mailboxes.reply({
id,
messageId,
text: "Thanks, we have the details we need.",
"idempotency-key": "support-request-123-reply",
});
await samva.mailboxes.forward({
id,
messageId,
to: ["specialist@example.com"],
text: "Could you help with this request?",
"idempotency-key": "support-request-123-forward",
});Forwarding quotes the original message and carries its attachments; any attachments you supply are added to them. See reply, reply all, and forward for the precise inputs.
Save a draft before sending
Drafts belong to their author. Listing drafts returns summaries without bodies; getDraft reads
the full draft. A new-message draft can be incomplete while you work.
const draft = await samva.mailboxes.createDraft({
id,
to: ["customer@example.com"],
subject: "Your support request",
text: "We are checking the details.",
});
await samva.mailboxes.updateDraft({
id,
draftId: draft.id,
text: "The details are confirmed. Your request is complete.",
});
const draftReceipt = await samva.mailboxes.sendDraft({
id,
draftId: draft.id,
"idempotency-key": "support-request-123-final-response",
});
console.log(draftReceipt.status, draftReceipt.actionId);To save a reply draft, pass inReplyToMessageId to createDraft; it must name a message in that
mailbox. The reply stays in its conversation. A draft without a reply target sends as a new
conversation. Sending requires recipients, a subject, and a body, including for reply drafts.
Each field in updateDraft replaces that field; omitted fields stay. null clears an optional
field, and an empty to list removes recipients. See
create a draft and
update a draft.
A saved draft is editable and deletable. An approvalPending draft is held unchanged for the
reviewer: editing returns 409, and it cannot be deleted or sent again. An authorized reviewer
can read the held draft. Denying the send returns it to its author as an editable draft; changing
and sending it requires a fresh approval. A sent draft cannot be edited or resent. Follow
Approve sends for the review workflow.
Attach a file
Send and draft requests accept attachments. Each attachment has a filename, contentType,
size, and exactly one source: base64 content or the mediaId of a ready upload belonging to
your organization. A mailbox-only key can complete or attach only uploads created with that same
key. An upload created with a setup key or another agent's key returns 404; create a new upload
with the sending key instead. Declared size and content type must match the uploaded file. A
message's attachment ID or download URL is not an outbound media ID.
await samva.mailboxes.sendMessage({
id,
to: ["customer@example.com"],
subject: "Your receipt",
text: "Your receipt is attached.",
attachments: [
{ filename: "receipt.txt", content: "SGVsbG8=", contentType: "text/plain", size: 5 },
],
"idempotency-key": "support-request-123-receipt",
});For larger files, upload and complete organization-owned media before attaching its ID. The email attachments guide owns size limits and upload instructions.
Send receipts
New messages, replies, forwards, and draft sends return the same receipt synchronously. Read
status before deciding what happens next:
| Status | Meaning | Next step |
|---|---|---|
sent | Samva accepted the message for sending. Delivery can still be waiting. | Inspect waitReason and follow delivery events. |
pending_approval | The principal's send mode or a mailbox policy holds the send for a person's decision. | Follow actionId through the approval workflow. |
sent does not confirm arrival in the recipient's inbox. waitReason is null when the receipt
has no reported delivery wait; a non-null value explains a wait, such as rate, daily-quota,
domain-health, or content-review. reviewExpectedAt is a nullable estimate for a content
review decision, present only while waitReason is content-review, and is not a promised
delivery time. See email delivery for delivery outcomes.
The receipt also carries actionId, threadId, createdAt, and nullable messageId.
messageId is null while approval is pending; use it to inspect the accepted message when it is
present. skippedRecipients lists excluded addresses with their reason; an empty list
means no exclusions are reported. Check it even when status is sent, because acceptance does
not mean every requested address is included. The
send response reference defines the complete shape.
Retry without creating another send
Mailbox sends have a 24-hour idempotency window. Within that window, repeat the same operation
with the same body and Idempotency-Key after a lost response. After retention expires, the same
request can create another send; check the action and message history before retrying an old
request. Reusing a key with a different body returns 409. An in-progress conflict asks you to repeat the same
request later; a held send replays its current approval receipt. Do not submit another send to
resolve an approval hold or delivery wait.
A recorded failed action returns a conflict instructing a new idempotency key. Treat that as a new intentional submission: inspect the action and message history before proceeding if the outcome is unclear. Follow Approve sends to approve or deny held work and Mailbox events to observe the result.