Skip to content

staff.mail ​

10 methods on staff.mail. Each returns the answer's data and throws NoviqaError on a failed answer.

list ​

GET/admin/v1/mail

List the own mailbox (staff)

One page of the caller's own folder: inbox (default), sent or draft. Inbox and sent come one row per thread, newest thread first: the row is the thread's newest message, thread_count is how many messages of the thread the folder holds and read is false while any of them is unread; drafts come one row each, newest first. total counts threads (drafts: drafts), unread counts unread messages. page is 1-based; limit defaults to 50, values outside 1-200 fall back to 50.

More details

With thread (a thread_id) the answer is instead a plain array of every inbox and sent message of that thread, oldest first, and all of them are marked read; folder, page and limit are ignored.

A trader account whose group has mail switched off gets an empty page with enabled false (an empty array for thread); staff always have a mailbox.

Rights: the staff member needs every one of: mailbox.

Parameters
folderstring · query
threadstring · query
one thread across inbox and sent, oldest first
pageinteger · query
limitinteger · query
Returns
Promise<Page>
The answer's data.
Errors
400invalid_input: invalid input
401auth.session_expired: session expired, sign in again
403auth.invalid_terminal: this terminal cannot be used here
403auth.otp_enrol_required: your group requires one-time codes: set them up to continue
403auth.password_change_required: you must change your password first
403forbidden: you do not have access to this action
500internal: something went wrong
ts
import { staff } from "@/lib/noviqa"

const data = await staff.mail.list({
  folder: "string",
  thread: "string",
  page: 1
})
bash
curl -X GET "$NOVIQA_API_URL/admin/v1/mail?folder=string&thread=string&page=1" \
  -H "Authorization: Bearer $NOVIQA_TOKEN"
json
{
  "success": true,
  "code": "ok",
  "message": "done",
  "data": {
    "enabled": true,
    "items": [
      {
        "attachments": [
          {}
        ],
        "body": "string",
        "created_at": "2026-09-25T10:15:00Z",
        "draft": {},
        "folder": "string",
        "from": 1,
        "from_name": "string",
        "id": 1,
        "message_id": "3f2a9c1e-8b7d-4e6f-a5c4-1b2d3e4f5a6b",
        "read": true,
        "recipients": "string",
        "subject": "string"
      }
    ],
    "page": 1,
    "total": 1,
    "unread": 1
  },
  "request_id": "5b7f2c1e"
}
ts
staff.mail.list(query?: { folder?, thread?, page?, limit? }, options?: RequestOptions): Promise<mail.Page>

create ​

POST/admin/v1/mail

Send mail to accounts

Delivers one message to trading accounts in the caller's group scope: either to_logins (1-1000 logins; one outside the scope is refused with forbidden, an unknown one with invalid_input) or to_group (a group mask up to 128 characters, * = every group in scope; reaches every account of the matching groups, at most 100 000) — not both. subject is required (1-256 characters, one line), body is plain text up to 64 KiB; both may use {{login}}, {{name}}, {{group}}, {{currency}} and the company placeholders, filled per recipient. Attachments: up to 5 files per message, each 1 byte to 1 MB, 2 MB together (files already uploaded onto the draft count too); the name must be a plain file name of up to 128 characters (no path, quotes or line breaks) ending in .png, .jpg, .jpeg, .bmp, .gif, .zip, .7z, .doc, .docx, .xls, .xlsx, .odt, .rtf, .csv, .txt, .log or .pdf. In JSON bodies content is the file base64-encoded; the whole request may be 4 MB at most. Accounts whose group has mail switched off are skipped (counted in skipped); if nobody is left the send is refused. email true also queues an e-mail per recipient with a valid address through the mail server named by server_id (0 or left out = the enabled default server, refused if there is none; a named server must exist and be enabled); they appear in GET /admin/v1/mail-outbox. reply_to (a mail of the caller's) joins its thread; draft_id sends from that draft: its uploaded files go along and the draft is removed.

More details

Writes the sender's sent copy and one inbox copy per account, pushes a mail message to each recipient's live sockets and writes a journal line. Not idempotent: a repeat sends again.

Rights: the staff member needs every one of: mailbox.

Request body
attachmentsNewAttachment_input[]
bodystring
draft_idinteger
the draft this is sent from; it is removed
emailboolean
also send it by e-mail through the default mail server
reply_tointeger
a mail of the sender's this answers (0 = a new thread)
server_idinteger
which mail server; 0 = the default one
subjectstring
to_groupstring
to_loginsinteger[]
Returns
Promise<Sent>
The answer's data.
Errors
400invalid_input: invalid input
401auth.session_expired: session expired, sign in again
403auth.csrf_invalid: request refused: reload the page and try again
403auth.invalid_terminal: this terminal cannot be used here
403auth.otp_enrol_required: your group requires one-time codes: set them up to continue
403auth.password_change_required: you must change your password first
403forbidden: you do not have access to this action
404not_found: not found
500internal: something went wrong
ts
import { staff } from "@/lib/noviqa"

const data = await staff.mail.create({
  attachments: [
    {
      content: "JVBERi0xLjQK…",
      name: "hours.pdf"
    }
  ],
  body: "Dear {{name}}, markets close early on 24 December.",
  email: true,
  subject: "Holiday trading hours",
  to_group: "real\\*"
})
bash
curl -X POST "$NOVIQA_API_URL/admin/v1/mail" \
  -H "Authorization: Bearer $NOVIQA_TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{
  "attachments": [
    {
      "content": "JVBERi0xLjQK…",
      "name": "hours.pdf"
    }
  ],
  "body": "Dear {{name}}, markets close early on 24 December.",
  "email": true,
  "subject": "Holiday trading hours",
  "to_group": "real\\*"
}'
json
{
  "success": true,
  "code": "ok",
  "message": "done",
  "data": {
    "delivered": 1,
    "emailed": 1,
    "mail": {
      "attachments": [
        {}
      ],
      "body": "string",
      "created_at": "2026-09-25T10:15:00Z",
      "draft": {
        "email": "ann@example.com",
        "reply_to": 1,
        "to": 1,
        "to_group": "string",
        "to_logins": [
          1
        ]
      },
      "folder": "string",
      "from": 1,
      "from_name": "string",
      "id": 1,
      "message_id": "3f2a9c1e-8b7d-4e6f-a5c4-1b2d3e4f5a6b",
      "read": true,
      "recipients": "string",
      "subject": "string"
    },
    "skipped": 1
  },
  "request_id": "5b7f2c1e"
}
ts
staff.mail.create(body: mail.StaffMessage, options?: RequestOptions): Promise<mail.Sent>

drafts ​

POST/admin/v1/mail/drafts

Save a draft (staff)

Stores what the caller is writing in the draft folder and answers it as saved. id 0 (or left out) makes a new draft; an id of one of the caller's drafts rewrites it (same as PUT). Staff address a draft with to_logins (up to 1000) or to_group (a group mask, up to 128 characters) and may set email; a trader account uses to (a staff mailbox login from GET /trader/v1/mailboxes) and the staff fields are ignored. reply_to (a non-draft mail the caller holds) keeps the draft in that thread. Nothing is checked against scope or mailboxes here — sending does that — and the subject may still be empty; it must fit 256 characters on one line and the body 64 KiB. Upload files with POST …/mail/drafts/{id}/attachments. A trader whose group has mail switched off is refused.

More details

Rights: the staff member needs every one of: mailbox.

Request body
bodystring
emailboolean
idinteger
reply_tointeger
subjectstring
tointeger
Login is the numeric identity of every account, trader or staff.
to_groupstring
to_loginsinteger[]
Returns
Promise<Mail>
The answer's data.
Errors
400invalid_input: invalid input
401auth.session_expired: session expired, sign in again
403auth.csrf_invalid: request refused: reload the page and try again
403auth.invalid_terminal: this terminal cannot be used here
403auth.otp_enrol_required: your group requires one-time codes: set them up to continue
403auth.password_change_required: you must change your password first
403forbidden: you do not have access to this action
404not_found: not found
500internal: something went wrong
ts
import { staff } from "@/lib/noviqa"

const data = await staff.mail.drafts({
  body: "Dear {{name}}, …",
  email: true,
  subject: "Margin requirements update",
  to_logins: [
    10001,
    10002
  ]
})
bash
curl -X POST "$NOVIQA_API_URL/admin/v1/mail/drafts" \
  -H "Authorization: Bearer $NOVIQA_TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{
  "body": "Dear {{name}}, …",
  "email": true,
  "subject": "Margin requirements update",
  "to_logins": [
    10001,
    10002
  ]
}'
json
{
  "success": true,
  "code": "ok",
  "message": "done",
  "data": {
    "attachments": [
      {
        "id": 1,
        "name": "Ann Smith",
        "size": 1
      }
    ],
    "body": "string",
    "created_at": "2026-09-25T10:15:00Z",
    "draft": {
      "email": "ann@example.com",
      "reply_to": 1,
      "to": 1,
      "to_group": "string",
      "to_logins": [
        1
      ]
    },
    "folder": "string",
    "from": 1,
    "from_name": "string",
    "id": 1,
    "message_id": "3f2a9c1e-8b7d-4e6f-a5c4-1b2d3e4f5a6b",
    "read": true,
    "recipients": "string",
    "subject": "string"
  },
  "request_id": "5b7f2c1e"
}
ts
staff.mail.drafts(body: mail.Draft, options?: RequestOptions): Promise<models.Mail>

updateDrafts ​

PUT/admin/v1/mail/drafts/{id}

Rewrite a draft (staff)

Replaces the whole draft named in the URL (any id in the body is overridden by the URL). Same rules as POST …/mail/drafts; fields left out are cleared, not kept. Its uploaded files stay. Answers the draft as saved.

More details

Rights: the staff member needs every one of: mailbox.

Parameters
idinteger · pathrequired
Numeric id.
Request body
bodystring
emailboolean
idinteger
reply_tointeger
subjectstring
tointeger
Login is the numeric identity of every account, trader or staff.
to_groupstring
to_loginsinteger[]
Returns
Promise<Mail>
The answer's data.
Errors
400invalid_input: invalid input
401auth.session_expired: session expired, sign in again
403auth.csrf_invalid: request refused: reload the page and try again
403auth.invalid_terminal: this terminal cannot be used here
403auth.otp_enrol_required: your group requires one-time codes: set them up to continue
403auth.password_change_required: you must change your password first
403forbidden: you do not have access to this action
404not_found: not found
500internal: something went wrong
ts
import { staff } from "@/lib/noviqa"

const data = await staff.mail.updateDrafts(1, {
  body: "Dear {{name}}, …",
  subject: "Holiday trading hours",
  to_group: "real\\*"
})
bash
curl -X PUT "$NOVIQA_API_URL/admin/v1/mail/drafts/1" \
  -H "Authorization: Bearer $NOVIQA_TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{
  "body": "Dear {{name}}, …",
  "subject": "Holiday trading hours",
  "to_group": "real\\*"
}'
json
{
  "success": true,
  "code": "ok",
  "message": "done",
  "data": {
    "attachments": [
      {
        "id": 1,
        "name": "Ann Smith",
        "size": 1
      }
    ],
    "body": "string",
    "created_at": "2026-09-25T10:15:00Z",
    "draft": {
      "email": "ann@example.com",
      "reply_to": 1,
      "to": 1,
      "to_group": "string",
      "to_logins": [
        1
      ]
    },
    "folder": "string",
    "from": 1,
    "from_name": "string",
    "id": 1,
    "message_id": "3f2a9c1e-8b7d-4e6f-a5c4-1b2d3e4f5a6b",
    "read": true,
    "recipients": "string",
    "subject": "string"
  },
  "request_id": "5b7f2c1e"
}
ts
staff.mail.updateDrafts(id: number, body: mail.Draft, options?: RequestOptions): Promise<models.Mail>

draftsAttachments ​

POST/admin/v1/mail/drafts/{id}/attachments

Upload a file onto a draft (staff)

Uploads one file as multipart/form-data field file (the only field read; the part's file name becomes the attachment name, path stripped). The file must be 1 byte to 1 MB; with the files already on the draft at most 5 files and 2 MB together; the name must be a plain file name of up to 128 characters with an allowed extension (.png, .jpg, .jpeg, .bmp, .gif, .zip, .7z, .doc, .docx, .xls, .xlsx, .odt, .rtf, .csv, .txt, .log, .pdf). Sending the draft (with draft_id) sends its files. Answers the draft with its file list. A trader whose group has mail switched off is refused.

More details

Rights: the staff member needs every one of: mailbox.

Parameters
idinteger · pathrequired
Numeric id.
Returns
Promise<Mail>
The answer's data.
Errors
400invalid_input: invalid input
401auth.session_expired: session expired, sign in again
403auth.csrf_invalid: request refused: reload the page and try again
403auth.invalid_terminal: this terminal cannot be used here
403auth.otp_enrol_required: your group requires one-time codes: set them up to continue
403auth.password_change_required: you must change your password first
403forbidden: you do not have access to this action
404not_found: not found
500internal: something went wrong
ts
import { staff } from "@/lib/noviqa"

const data = await staff.mail.draftsAttachments(1)
bash
curl -X POST "$NOVIQA_API_URL/admin/v1/mail/drafts/1/attachments" \
  -H "Authorization: Bearer $NOVIQA_TOKEN"
json
{
  "success": true,
  "code": "ok",
  "message": "done",
  "data": {
    "attachments": [
      {
        "id": 1,
        "name": "Ann Smith",
        "size": 1
      }
    ],
    "body": "string",
    "created_at": "2026-09-25T10:15:00Z",
    "draft": {
      "email": "ann@example.com",
      "reply_to": 1,
      "to": 1,
      "to_group": "string",
      "to_logins": [
        1
      ]
    },
    "folder": "string",
    "from": 1,
    "from_name": "string",
    "id": 1,
    "message_id": "3f2a9c1e-8b7d-4e6f-a5c4-1b2d3e4f5a6b",
    "read": true,
    "recipients": "string",
    "subject": "string"
  },
  "request_id": "5b7f2c1e"
}
ts
staff.mail.draftsAttachments(id: number, options?: RequestOptions): Promise<models.Mail>

deleteDraftsAttachments ​

DELETE/admin/v1/mail/drafts/{id}/attachments/{file}

Remove a file from a draft (staff)

Deletes one uploaded file from the caller's draft and answers the draft with its remaining files.

More details

Rights: the staff member needs every one of: mailbox.

Parameters
idinteger · pathrequired
Numeric id.
fileinteger · pathrequired
Attachment id, from the draft's attachments.
Returns
Promise<Mail>
The answer's data.
Errors
400invalid_input: invalid input
401auth.session_expired: session expired, sign in again
403auth.csrf_invalid: request refused: reload the page and try again
403auth.invalid_terminal: this terminal cannot be used here
403auth.otp_enrol_required: your group requires one-time codes: set them up to continue
403auth.password_change_required: you must change your password first
403forbidden: you do not have access to this action
404not_found: not found
500internal: something went wrong
ts
import { staff } from "@/lib/noviqa"

const data = await staff.mail.deleteDraftsAttachments(1, 1)
bash
curl -X DELETE "$NOVIQA_API_URL/admin/v1/mail/drafts/1/attachments/1" \
  -H "Authorization: Bearer $NOVIQA_TOKEN"
json
{
  "success": true,
  "code": "ok",
  "message": "done",
  "data": {
    "attachments": [
      {
        "id": 1,
        "name": "Ann Smith",
        "size": 1
      }
    ],
    "body": "string",
    "created_at": "2026-09-25T10:15:00Z",
    "draft": {
      "email": "ann@example.com",
      "reply_to": 1,
      "to": 1,
      "to_group": "string",
      "to_logins": [
        1
      ]
    },
    "folder": "string",
    "from": 1,
    "from_name": "string",
    "id": 1,
    "message_id": "3f2a9c1e-8b7d-4e6f-a5c4-1b2d3e4f5a6b",
    "read": true,
    "recipients": "string",
    "subject": "string"
  },
  "request_id": "5b7f2c1e"
}
ts
staff.mail.deleteDraftsAttachments(id: number, file: number, options?: RequestOptions): Promise<models.Mail>

preview ​

POST/admin/v1/mail/preview

Preview a mail before sending

Takes the same body as POST /admin/v1/mail and runs the same checks and addressing in the caller's scope, without sending or storing anything: how many accounts it reaches, how many are skipped (mail switched off) and how many have an e-mail address, plus the subject and body as the first reached account reads them (placeholders filled). draft_id, reply_to and email are accepted but not used.

More details

Rights: the staff member needs every one of: mailbox.

Request body
attachmentsNewAttachment_input[]
bodystring
draft_idinteger
the draft this is sent from; it is removed
emailboolean
also send it by e-mail through the default mail server
reply_tointeger
a mail of the sender's this answers (0 = a new thread)
server_idinteger
which mail server; 0 = the default one
subjectstring
to_groupstring
to_loginsinteger[]
Returns
Promise<Preview>
The answer's data.
Errors
400invalid_input: invalid input
401auth.session_expired: session expired, sign in again
403auth.csrf_invalid: request refused: reload the page and try again
403auth.invalid_terminal: this terminal cannot be used here
403auth.otp_enrol_required: your group requires one-time codes: set them up to continue
403auth.password_change_required: you must change your password first
403forbidden: you do not have access to this action
500internal: something went wrong
ts
import { staff } from "@/lib/noviqa"

const data = await staff.mail.preview({
  body: "Dear {{name}}, …",
  subject: "Your account {{login}}",
  to_logins: [
    10001
  ]
})
bash
curl -X POST "$NOVIQA_API_URL/admin/v1/mail/preview" \
  -H "Authorization: Bearer $NOVIQA_TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{
  "body": "Dear {{name}}, …",
  "subject": "Your account {{login}}",
  "to_logins": [
    10001
  ]
}'
json
{
  "success": true,
  "code": "ok",
  "message": "done",
  "data": {
    "body": "string",
    "emails": 1,
    "login": 1001,
    "recipients": 1,
    "skipped": 1,
    "subject": "string"
  },
  "request_id": "5b7f2c1e"
}
ts
staff.mail.preview(body: mail.StaffMessage, options?: RequestOptions): Promise<mail.Preview>

retrieve ​

GET/admin/v1/mail/{id}

Read one mail (staff)

One message the caller holds (any folder), with its file list. Reading an unread inbox message marks it read. Download a file with GET …/mail/{id}/attachments/{file}.

More details

Rights: the staff member needs every one of: mailbox.

Parameters
idinteger · pathrequired
Numeric id.
Returns
Promise<Mail>
The answer's data.
Errors
400invalid_input: invalid input
401auth.session_expired: session expired, sign in again
403auth.invalid_terminal: this terminal cannot be used here
403auth.otp_enrol_required: your group requires one-time codes: set them up to continue
403auth.password_change_required: you must change your password first
403forbidden: you do not have access to this action
404not_found: not found
500internal: something went wrong
ts
import { staff } from "@/lib/noviqa"

const data = await staff.mail.retrieve(1)
bash
curl -X GET "$NOVIQA_API_URL/admin/v1/mail/1" \
  -H "Authorization: Bearer $NOVIQA_TOKEN"
json
{
  "success": true,
  "code": "ok",
  "message": "done",
  "data": {
    "attachments": [
      {
        "id": 1,
        "name": "Ann Smith",
        "size": 1
      }
    ],
    "body": "string",
    "created_at": "2026-09-25T10:15:00Z",
    "draft": {
      "email": "ann@example.com",
      "reply_to": 1,
      "to": 1,
      "to_group": "string",
      "to_logins": [
        1
      ]
    },
    "folder": "string",
    "from": 1,
    "from_name": "string",
    "id": 1,
    "message_id": "3f2a9c1e-8b7d-4e6f-a5c4-1b2d3e4f5a6b",
    "read": true,
    "recipients": "string",
    "subject": "string"
  },
  "request_id": "5b7f2c1e"
}
ts
staff.mail.retrieve(id: number, options?: RequestOptions): Promise<models.Mail>

delete ​

DELETE/admin/v1/mail/{id}

Delete one mail (staff)

Removes the caller's own copy (inbox, sent or draft); other people's copies stay. The files go when the last copy of the message is gone.

More details

Rights: the staff member needs every one of: mailbox.

Parameters
idinteger · pathrequired
Numeric id.
Returns
Promise<null>
The answer's data.
Errors
400invalid_input: invalid input
401auth.session_expired: session expired, sign in again
403auth.csrf_invalid: request refused: reload the page and try again
403auth.invalid_terminal: this terminal cannot be used here
403auth.otp_enrol_required: your group requires one-time codes: set them up to continue
403auth.password_change_required: you must change your password first
403forbidden: you do not have access to this action
404not_found: not found
500internal: something went wrong
ts
import { staff } from "@/lib/noviqa"

const data = await staff.mail.delete(1)
bash
curl -X DELETE "$NOVIQA_API_URL/admin/v1/mail/1" \
  -H "Authorization: Bearer $NOVIQA_TOKEN"
json
{
  "success": true,
  "code": "ok",
  "message": "done",
  "data": null,
  "request_id": "5b7f2c1e"
}
ts
staff.mail.delete(id: number, options?: RequestOptions): Promise<null>

retrieveAttachments ​

GET/admin/v1/mail/{id}/attachments/{file}

Download a mail attachment (staff)

The bytes of one file of a message the caller holds a copy of, sent as application/octet-stream with Content-Disposition: attachment; filename=… and X-Content-Type-Options: nosniff: save it, never show it inline. The file ids are in the mail's attachments.

More details

Rights: the staff member needs every one of: mailbox.

Parameters
idinteger · pathrequired
Numeric id.
fileinteger · pathrequired
Attachment id, from the mail's attachments.
Returns
Promise<Response>
The answer's data.
Errors
400invalid_input: invalid input
401auth.session_expired: session expired, sign in again
403auth.invalid_terminal: this terminal cannot be used here
403auth.otp_enrol_required: your group requires one-time codes: set them up to continue
403auth.password_change_required: you must change your password first
403forbidden: you do not have access to this action
404not_found: not found
500internal: something went wrong
ts
import { staff } from "@/lib/noviqa"

const data = await staff.mail.retrieveAttachments(1, 1)
bash
curl -X GET "$NOVIQA_API_URL/admin/v1/mail/1/attachments/1" \
  -H "Authorization: Bearer $NOVIQA_TOKEN"
ts
// application/octet-stream file: the SDK returns the raw Response
ts
staff.mail.retrieveAttachments(id: number, file: number, options?: RequestOptions): Promise<Response>