Jevmail for agents and developers
You draft email for an owner. Jevmail scores each draft against the owner’s questions, using the conversation and their sent mail as evidence, and keeps the drafts for one reply together for the owner to review. You never send; the owner does. For what jevmail is to the owner, see the docs.
The workflow
- Find the conversation.
find_conversationssays which message a reply to these people would answer, and itsmessageId. - Score one draft, or several.
score_draftscores text, or up to eight alternatives for the same reply, and stores each one. Scoring reads mail but never writes it. - Read a draft back.
get_scored_draftreturns a stored draft bycallId: its text, every answer, its overall score and its revisions. - Revise. Improve the weakest answers and call
score_draftagain, passing the lastcallIdaspreviousCallId. When the owner gives feedback that led to the revision or candidate set, include their actual words asownerFeedback(at most 4,000 UTF-8 bytes); never invent feedback from a score. Then tell the owner the draft is ready: they review and send it at jevmail.xyz/drafts.
Reply evidence stops at the email being answered: that email stays in the conversation, while other thread messages and sent examples must be earlier. A reply to older mail therefore uses older writing examples; fresh replies keep the examples already available then. New emails use the scoring time. The result records this millisecond boundary as context.cutoff; revisions with previousCallId reuse the stored parent’s boundary. Sent versions also exclude evidence at or after the send time.
When workflow.progress is not_comparable, workflow.progress_reason explains why: reply_target_changed, evidence_changed (the evidence ID changed or is missing), or model_changed. Comparable revisions omit the reason. MCP, CLI and API return the same fields.
An example. First, the conversation:
{
"to": [
"alex@example.com"
],
"subject": "Project timeline"
}{
"match": {
"messageId": "18c2f0a9b7d3e1f4",
"threadId": "18c2e7d1a0b9c8f7",
"subject": "Project timeline",
"from": "Alex <alex@example.com>",
"date": "2026-10-12T15:04:00.000Z",
"messages": 3
},
"alternatives": []
}Then a draft answering its message:
{
"messageId": "18c2f0a9b7d3e1f4",
"draft": {
"to": [
"alex@example.com"
],
"subject": "Re: Project timeline",
"text": "Thanks Alex. Friday works for me."
}
}{
"callId": "8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c",
"mailbox": "you@example.com",
"overall": 0.74,
"overallExcluded": [
"handling"
],
"questions": [
{
"id": "formality",
"question": "Does the draft's level of formality match how this user writes to these recipients?",
"type": "noul",
"noul": 0.81
}
],
"workflow": {
"status": "scored",
"next_action": "present_for_owner_review",
"review_id": "8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c",
"revision": 0,
"progress": "initial"
}
}Connecting
- MCP, for ChatGPT, Claude and other agents: add
https://jevmail.xyz/mcp. It signs you in with OAuth, or takes an agent key. - An agent key: create one in Settings, under Agent access.
- The CLI:
curl -fsSLo ~/.local/bin/jevmail https://jevmail.xyz/jevmail.mjs && chmod 755 ~/.local/bin/jevmail, then setJEVMAIL_AGENT_TOKENto the key.jevmail --helplists the operations. - HTTP:
POST https://jevmail.xyz/api/agent/<name>with the operation’s JSON arguments as the body andAuthorization: Bearer <agent key or OAuth token>.
Every operation has one name across the three, takes the same JSON arguments, and returns the same JSON result. Every result includes requestId, the id of that call. call_log reads the calls recorded for this account.
Errors
Every error has one shape, whichever way it was called: { "error": { "code", "message", "fix" } }. message says what went wrong, and fix what to do next. Follow the fix before retrying; when it says to wait, other fields such as retryAfterSeconds say how long. A name that isn’t an operation gets unknown_operation, with the current operations and where to get the current CLI.
Questions and scoring
Every draft is asked your saved questions. A question is one of three types:
- Yes/no (Noul): how likely the answer is yes, from 0 to 1.
- Choice: which of your named options fits, with how likely each is.
- Score: a position on your ordered levels, from the first to the last.
A draft’s overall score is the plain average of its answers that have an order: a yes/no answer as it is, a score as its share of the last level. Choice answers have no order, so they are left out, and the result names them in overallExcluded.
A working group is every draft that answers the same message. A reply’s drafts are compared within it, and its best (the highest overall, the newest on a tie) is the one shown first.
Reference
next
Pick the next inbox email. Return the highest scored eligible unhandled INBOX email across connected mailbox copies. Only the latest message in a Gmail thread is eligible: any later message, from the owner or anyone else, makes every earlier message in that thread ineligible, and a thread whose latest message is the owner's is not in the queue. That check is applied when next is read, including after a newer message syncs, and the importance view uses the same eligible set. One score is saved per thread and saved question during sync. Any new message in that thread, received or sent, makes the saved score stale until the thread is rescored; the previous score is not served. Every score uses the same context: that thread's copied headers and snippets, plus the latest message of at most five other open threads in the same mailbox from the last 14 days that share a human From, To, or Cc address. Participants on a message with List-Id or List-Unsubscribe, and no-reply-style addresses, do not count. A new stored message rescores each of those related open threads with that same context, and the previous score is not served once the related context changes. The returned message is the thread's latest eligible message, and its threadId is unchanged. next takes no arguments and never calls Gmail. Before returning a thread, next rescores that thread through the AI Gateway using the same context as every score, including the current time. If it is still the top serving thread, next returns it with the fresh score. If not, next rescores whatever is now top and repeats in that order until a thread survives its rescore. Each new score is saved as that thread's serving score, and each rescore is logged with this call's request id. A model failure returns an error and does not return a stale score. next never sends, never alters drafts, and never changes Gmail. reason.importance_score is that thread score; reason.confidence is null because the individual score has no choice confidence. waiting counts threads in the waiting state; next excludes them until a newer inbound message or undismiss reopens them. unscored counts eligible threads without a fresh serving score; they are retried on later syncs. rescore_in_progress means the saved question changed and the disclosed ranking_question still serves until all replacement scores are ready. The saved question in Settings is the only question. The importance view uses the same persisted order, which stays stable when an email is dismissed. A mailbox may have no selected email; that does not mean it is disconnected. mailboxes and named warnings describe copy coverage, failures and last sync times; incomplete or unscored mail never produces a false empty inbox. After handling a pick, call dismiss with its mailbox and messageId; confirmed Jevmail replies are marked handled automatically. Gmail archiving remains separate; ask before archiving matches from same_sender_in_inbox.
No arguments.
{
"method": "tools/call",
"params": {
"name": "next",
"arguments": {}
}
}echo '{}' | jevmail next --json -curl -X POST https://jevmail.xyz/api/agent/next \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"empty": false,
"mailboxes": [
{
"email": "you@example.com",
"checked": true
}
],
"message": {
"mailbox": "you@example.com",
"messageId": "abc123",
"subject": "Re: Friday",
"text": "Friday works?"
},
"question": "Which email would Sam be most happy to have responded to?",
"unscored": 0,
"rescore_in_progress": false,
"waiting": 0,
"reason": {
"importance_score": 0.8,
"confidence": null
},
"next_step": "Handle this email, call dismiss with its mailbox and messageId, then use same_sender_in_inbox in the same mailbox and ask before archiving other mail; call next again for another pick."
}dismiss
Mark an inbox thread handled. Immediately mark a copied thread dismissed in Jevmail so next excludes it without waiting for sync. Provide its connected mailbox and exactly one of messageId or threadId. Already handled threads stay unchanged. No Gmail or model call; Gmail labels are unchanged. A newer inbound message reopens the thread. undismiss clears that local dismissal. Logged like every operation.
| Field | Type | Description |
|---|---|---|
mailbox (required) | string | A bare email address, without a display name. |
messageId | string | |
threadId | string |
{
"method": "tools/call",
"params": {
"name": "dismiss",
"arguments": {
"mailbox": "you@example.com",
"messageId": "abc123"
}
}
}echo '{"mailbox":"you@example.com","messageId":"abc123"}' | jevmail dismiss --json -curl -X POST https://jevmail.xyz/api/agent/dismiss \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mailbox":"you@example.com","messageId":"abc123"}'{
"mailbox": "you@example.com",
"threadId": "abc456",
"handled": "dismissed",
"changed": true
}undismiss
Clear an inbox thread dismissal. Immediately clear a copied thread's dismissal or wait in Jevmail so its latest message can return in next. Provide its connected mailbox and exactly one of messageId or threadId. A thread that is neither dismissed nor waiting stays unchanged. No Gmail or model call; Gmail labels and message bodies are unchanged. Only the latest message in the thread can be offered, and a thread whose latest message is the owner's stays out of next. Logged like every operation.
| Field | Type | Description |
|---|---|---|
mailbox (required) | string | A bare email address, without a display name. |
messageId | string | |
threadId | string |
{
"method": "tools/call",
"params": {
"name": "undismiss",
"arguments": {
"mailbox": "you@example.com",
"messageId": "abc123"
}
}
}echo '{"mailbox":"you@example.com","messageId":"abc123"}' | jevmail undismiss --json -curl -X POST https://jevmail.xyz/api/agent/undismiss \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mailbox":"you@example.com","messageId":"abc123"}'{
"mailbox": "you@example.com",
"threadId": "abc456",
"handled": "unhandled",
"changed": true
}wait
Set an inbox thread to waiting. Mark a copied thread waiting so next skips it without dismissing it. Provide its connected mailbox, a nonempty note of at most 4,000 characters, and exactly one of messageId or threadId. The note is appended to the thread's log with the time and author agent; entries are never edited or deleted. An unhandled thread becomes waiting. A thread already waiting stays waiting, keeps its original waiting time, and gains the new note. A replied or dismissed thread is left unchanged and the note is not added. A newer inbound message reopens the thread, the same way it reopens a dismissal. undismiss also reopens it. No Gmail or model call; Gmail labels and message bodies are unchanged. Logged like every operation.
| Field | Type | Description |
|---|---|---|
mailbox (required) | string | A bare email address, without a display name. |
messageId | string | |
threadId | string | |
note (required) | string | The note to append. Nonempty, at most 4,000 characters, stored as given. |
{
"method": "tools/call",
"params": {
"name": "wait",
"arguments": {
"mailbox": "you@example.com",
"messageId": "abc123",
"note": "Waiting on competing quotes."
}
}
}echo '{"mailbox":"you@example.com","messageId":"abc123","note":"Waiting on competing quotes."}' | jevmail wait --json -curl -X POST https://jevmail.xyz/api/agent/wait \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mailbox":"you@example.com","messageId":"abc123","note":"Waiting on competing quotes."}'{
"mailbox": "you@example.com",
"threadId": "abc456",
"handled": "waiting",
"changed": true,
"notes": [
{
"at": 1792000000000,
"author": "agent",
"text": "Waiting on competing quotes."
}
]
}append_note
Append a note to an inbox thread. Append one timestamped note to a copied thread without changing whether it is waiting, dismissed, or unhandled. Provide its connected mailbox, a nonempty note of at most 4,000 characters, and exactly one of messageId or threadId. The response notes are the whole log, oldest first. author is agent for this operation. Entries are never edited or deleted; there is no operation that does either. No Gmail or model call; Gmail labels and message bodies are unchanged. Logged like every operation.
| Field | Type | Description |
|---|---|---|
mailbox (required) | string | A bare email address, without a display name. |
messageId | string | |
threadId | string | |
note (required) | string | The note to append. Nonempty, at most 4,000 characters, stored as given. |
{
"method": "tools/call",
"params": {
"name": "append_note",
"arguments": {
"mailbox": "you@example.com",
"threadId": "abc456",
"note": "Asked for the second quote."
}
}
}echo '{"mailbox":"you@example.com","threadId":"abc456","note":"Asked for the second quote."}' | jevmail append_note --json -curl -X POST https://jevmail.xyz/api/agent/append_note \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mailbox":"you@example.com","threadId":"abc456","note":"Asked for the second quote."}'{
"mailbox": "you@example.com",
"threadId": "abc456",
"handled": "waiting",
"notes": [
{
"at": 1792000000000,
"author": "agent",
"text": "Waiting on competing quotes."
},
{
"at": 1792000001000,
"author": "agent",
"text": "Asked for the second quote."
}
]
}waiting
List waiting inbox threads. List this account's waiting threads, oldest wait first. Each entry has its mailbox, threadId, since (when the wait started, in epoch milliseconds), waitingMs (how long it has been waiting at this read), and notes, oldest first. A note has at, author (agent or owner), and text. Takes no arguments. Never calls Gmail or a model, and does not include message bodies. Logged like every operation.
No arguments.
{
"method": "tools/call",
"params": {
"name": "waiting",
"arguments": {}
}
}echo '{}' | jevmail waiting --json -curl -X POST https://jevmail.xyz/api/agent/waiting \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"threads": [
{
"mailbox": "you@example.com",
"threadId": "abc456",
"since": 1792000000000,
"waitingMs": 3600000,
"notes": [
{
"at": 1792000000000,
"author": "agent",
"text": "Waiting on competing quotes."
}
]
}
]
}same_sender_in_inbox
Find inbox mail from the same sender or list. Find INBOX mail from the same exact sender address or List-Id in the given connected mailbox’s synced copy, without a Gmail call. Provide mailbox plus either messageId to read a copied source’s From and List-Id headers (the source need not still be in INBOX), or from and/or listId directly. The source is excluded; no other mailbox is searched. Returns mailbox, named warnings, partial, up to 20 newest matches (messageId, from, subject, date), and capped when more matches remain. An incomplete, failed or lapsed copy returns its warning with the last confirmed sync time and partial:true, without falling back to another mailbox. Makes no Gmail writes or Jev calls. Ask the owner before archiving any matches using separately authorized Gmail access.
| Field | Type | Description |
|---|---|---|
mailbox (required) | string | A bare email address, without a display name. |
messageId | string | |
from | string | A bare email address, without a display name. |
listId | string | List-Id header value, with or without its display name. |
{
"method": "tools/call",
"params": {
"name": "same_sender_in_inbox",
"arguments": {
"mailbox": "you@example.com",
"from": "sam@example.com"
}
}
}echo '{"mailbox":"you@example.com","from":"sam@example.com"}' | jevmail same_sender_in_inbox --json -curl -X POST https://jevmail.xyz/api/agent/same_sender_in_inbox \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mailbox":"you@example.com","from":"sam@example.com"}'{
"mailbox": "you@example.com",
"warnings": [],
"partial": false,
"matches": [
{
"messageId": "abc123",
"from": "Sam <sam@example.com>",
"subject": "Friday plans",
"date": "2026-10-20T10:00:00.000Z"
}
],
"capped": false,
"next_step": "Ask the owner whether to archive these; archive only if they say yes. Jevmail archives none of them."
}score_draft
Score and store a draft in Jevmail. The default scoring tool. Send up to eight alternatives in one call using shared draft metadata without draft.text and candidates: [{text, subject?}], with messageId or inReplyTo for replies, newEmail: true for new emails, or previousCallId to inherit a stored target. Each is scored sequentially in one shared roundId; new emails follow the existing recipient-and-subject grouping rules; the response contains candidates ranked by overall (newest input on a tie), each with callId, answers and draftReviewUrl, plus bestCallId. A failure stops the batch and reports completed candidates and failedCandidateIndex. Single draft.text requests keep their existing response. Score plain text against the owner's questions and sent-mail evidence, and retain the encrypted draft, answers and revision chain in Jevmail. Never creates, edits or reads a Gmail draft; requires only Gmail read access for evidence. Supply messageId or inReplyTo for a reply, or newEmail: true. When the owner gives feedback that leads to this revision or set of drafts, pass their actual words as ownerFeedback; never invent feedback or infer it from a score. On a revision pass the new draft text and previousCallId=this callId; its mailbox and reply target or newEmail are inherited when omitted. No Gmail tools or requestId needed. Returns per-question native answers; overall, the plain average from 0 to 1 of the ordered answers (a Score level as its share of the last level), with the Choice questions it leaves out listed in overallExcluded; callId, mailbox and workflow. Follow workflow.next_action; a score is not permission to send. Twelve reviews a minute per account, shared with legacy tools. Retrieve by callId with get_scored_draft; save_draft_to_gmail is a separate explicit export.
| Field | Type | Description |
|---|---|---|
draft (required) | object | |
messageId | string | |
inReplyTo | string | RFC Message-ID header; use this or messageId. |
newEmail | true | |
previousCallId | string | |
ownerFeedback | string | The owner's actual feedback that led to this draft or candidate set, relative to previousCallId. Optional; nonempty, at most 4,000 UTF-8 bytes. Never invent feedback. Stored with the call, not used as a scoring instruction. |
candidates | array of object | Alternative bodies sharing draft metadata and one roundId; use messageId or inReplyTo for replies, newEmail: true for new emails, or previousCallId to inherit the target. Omit draft.text. |
{
"method": "tools/call",
"params": {
"name": "score_draft",
"arguments": {
"messageId": "18c2f0a9b7d3e1f4",
"draft": {
"to": [
"alex@example.com"
],
"subject": "Re: Project timeline",
"text": "Thanks Alex. Friday works for me."
}
}
}
}echo '{"messageId":"18c2f0a9b7d3e1f4","draft":{"to":["alex@example.com"],"subject":"Re: Project timeline","text":"Thanks Alex. Friday works for me."}}' | jevmail score_draft --json -curl -X POST https://jevmail.xyz/api/agent/score_draft \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"messageId":"18c2f0a9b7d3e1f4","draft":{"to":["alex@example.com"],"subject":"Re: Project timeline","text":"Thanks Alex. Friday works for me."}}'{
"callId": "8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c",
"mailbox": "you@example.com",
"overall": 0.74,
"overallExcluded": [
"handling"
],
"questions": [
{
"id": "formality",
"question": "Does the draft's level of formality match how this user writes to these recipients?",
"type": "noul",
"noul": 0.81
}
],
"workflow": {
"status": "scored",
"next_action": "present_for_owner_review",
"review_id": "8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c",
"revision": 0,
"progress": "initial"
}
}get_scored_draft
Read a stored scored draft. Retrieve the account's immutable Jevmail draft by callId: text, recipients, mailbox, reply target, per-question answers, workflow, previousCallId, ownerFeedback when supplied, revision IDs and review URL. Reads Jevmail only; no Gmail access or writes.
| Field | Type | Description |
|---|---|---|
callId (required) | string |
{
"method": "tools/call",
"params": {
"name": "get_scored_draft",
"arguments": {
"callId": "8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c"
}
}
}echo '{"callId":"8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c"}' | jevmail get_scored_draft --json -curl -X POST https://jevmail.xyz/api/agent/get_scored_draft \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"callId":"8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c"}'{
"callId": "8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c",
"draft": {
"to": [
"alex@example.com"
],
"cc": [],
"subject": "Re: Project timeline",
"text": "Thanks Alex. Friday works for me."
},
"mailbox": "you@example.com",
"previousCallId": null,
"dismissed": false,
"sent": null,
"reviewUrl": "https://jevmail.xyz/drafts/8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c?version=draft",
"revisions": [
{
"callId": "8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c",
"status": "completed",
"at": 1792000000000
}
]
}save_draft_to_gmail
Explicitly save a scored draft to Gmail. An explicit Gmail WRITE. Use only when the owner asks to save to Gmail. Save the Jevmail-held snapshot named by callId. The first save creates one Gmail draft; later revisions in the previousCallId chain update that same Gmail draft. Never create a separate Gmail draft for a revision. Stored scored versions remain unchanged. Never sends or scores. Reuse callId on retries; older versions cannot overwrite a newer export, and uncertain writes never create another copy. External Gmail edits or deletion require owner attention. Returns gmailDraftId, gmailMessageId and mailbox. Requires Gmail compose permission and a text review from score_draft. Gmail edits do not update its scores: paste revised text into score_draft.
| Field | Type | Description |
|---|---|---|
callId (required) | string |
{
"method": "tools/call",
"params": {
"name": "save_draft_to_gmail",
"arguments": {
"callId": "8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c"
}
}
}echo '{"callId":"8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c"}' | jevmail save_draft_to_gmail --json -curl -X POST https://jevmail.xyz/api/agent/save_draft_to_gmail \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"callId":"8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c"}'{
"callId": "8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c",
"savedToGmail": true,
"gmailDraftId": "r-4815162342",
"gmailMessageId": "18c2f1b0c4e5a6d7",
"mailbox": "you@example.com"
}find_conversations
Find conversations. Which conversation a reply to these people would answer, from Gmail metadata only. Without a subject: up to ten of the past year's conversations with every address as a participant, most recently active first, each with the messageId to pass to score_draft, subject, sender, date and message count. With a subject: the conversation to choose explicitly for score_draft (the most recently active one with the same subject once reply prefixes are removed, replying to its latest message from someone else) and up to three alternatives. Thirty lookups a minute per account.
| Field | Type | Description |
|---|---|---|
to (required) | array of string | The recipients of the reply: one to ten bare addresses. |
subject | string | The draft's subject, to see which conversation it would answer. |
{
"method": "tools/call",
"params": {
"name": "find_conversations",
"arguments": {
"to": [
"alex@example.com"
],
"subject": "Project timeline"
}
}
}echo '{"to":["alex@example.com"],"subject":"Project timeline"}' | jevmail find_conversations --json -curl -X POST https://jevmail.xyz/api/agent/find_conversations \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"to":["alex@example.com"],"subject":"Project timeline"}'{
"match": {
"messageId": "18c2f0a9b7d3e1f4",
"threadId": "18c2e7d1a0b9c8f7",
"subject": "Project timeline",
"from": "Alex <alex@example.com>",
"date": "2026-10-12T15:04:00.000Z",
"messages": 3
},
"alternatives": []
}list_draft_questions
List draft questions. The saved questions every draft is asked, with their IDs, types and criteria. Only the owner can edit them on the Questions page; agents have read-only access to the saved set. There are fourteen by default: twelve Noul questions, one categorical Choice and one Score. At most thirty-two.
No arguments.
{
"method": "tools/call",
"params": {
"name": "list_draft_questions",
"arguments": {}
}
}echo '{}' | jevmail list_draft_questions --json -curl -X POST https://jevmail.xyz/api/agent/list_draft_questions \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"questions": [
{
"id": "formality",
"question": "Does the draft's level of formality match how this user writes to these recipients?",
"type": "noul"
}
]
}get_draft_test_set
Get the draft test set. The test set for trying question wording: the owner's twenty most recent sent emails, each prepared as if it were a proposed draft with the evidence scoring would retrieve, plus up to ten recent agent drafts from the audit. Returns each case's id, kind (sent or agent_draft), subject, recipients and date. Prepared on first use and kept for six hours; refresh rebuilds it. Pass case ids to try_draft_questions, six at a time.
| Field | Type | Description |
|---|---|---|
refresh | boolean | true to rebuild the set from current mail. |
{
"method": "tools/call",
"params": {
"name": "get_draft_test_set",
"arguments": {
"refresh": false
}
}
}echo '{"refresh":false}' | jevmail get_draft_test_set --json -curl -X POST https://jevmail.xyz/api/agent/get_draft_test_set \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"refresh":false}'{
"preparedAt": 1792000000000,
"cases": [
{
"id": "18c2d4e5f6a7b8c9",
"kind": "sent",
"subject": "Re: Lunch",
"to": [
"sam@example.com"
],
"date": "2026-10-10T12:00:00.000Z"
}
]
}get_draft_test_case
Get one test case. One test case in full: the draft that is scored and every evidence email the model sees, exactly as prepared. Use it to read why a case scored as it did.
| Field | Type | Description |
|---|---|---|
caseId (required) | string | A case id from get_draft_test_set. |
{
"method": "tools/call",
"params": {
"name": "get_draft_test_case",
"arguments": {
"caseId": "18c2d4e5f6a7b8c9"
}
}
}echo '{"caseId":"18c2d4e5f6a7b8c9"}' | jevmail get_draft_test_case --json -curl -X POST https://jevmail.xyz/api/agent/get_draft_test_case \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"caseId":"18c2d4e5f6a7b8c9"}'{
"case": {
"id": "18c2d4e5f6a7b8c9",
"kind": "sent",
"subject": "Re: Lunch",
"state": {
"world": {
"thread": [
"18c2d3a1b2c3d4e5"
]
},
"emails": {}
}
}
}try_draft_questions
Try draft questions. Score proposed questions on one to six cases of the test set without saving anything. drafts optionally gives a case substitute draft text, scored in place of its own with the same evidence from before the original send; such results say draftOverridden and are never cached. Returns each case's typed answers with distributions and confidence where provided. The legacy scores map contains only yes probabilities; categories and rubric positions have null there. The owner wrote every sent case, so a question about resemblance to their writing should mostly score high on sent cases; a low score is a reason to read that case with get_draft_test_case. Call once per six cases to cover the set. Shares score_draft's limit of twelve a minute.
| Field | Type | Description |
|---|---|---|
questions (required) | array of string or object | The wording to try: question objects with id, question, optional type (noul, choice, score) and criteria, or plain strings, which get ids trial_1, trial_2 and so on. |
caseIds (required) | array of string | One to six case ids from get_draft_test_set. |
drafts | object | Optional substitute draft text by case id, for cases in caseIds: each is scored on this text in place of its own draft, with the same evidence from before the original send. Read-only: nothing is saved or cached. |
{
"method": "tools/call",
"params": {
"name": "try_draft_questions",
"arguments": {
"questions": [
"Would I sign off like this?"
],
"caseIds": [
"18c2d4e5f6a7b8c9"
]
}
}
}echo '{"questions":["Would I sign off like this?"],"caseIds":["18c2d4e5f6a7b8c9"]}' | jevmail try_draft_questions --json -curl -X POST https://jevmail.xyz/api/agent/try_draft_questions \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"questions":["Would I sign off like this?"],"caseIds":["18c2d4e5f6a7b8c9"]}'{
"results": [
{
"id": "18c2d4e5f6a7b8c9",
"scores": {
"trial_1": 0.88
},
"answers": {
"trial_1": {
"type": "noul",
"noul": 0.88
}
}
}
]
}list_calls
List this account's jevmail calls. The account's call log, newest first: every call the owner or their agents made to jevmail, over MCP, the agent key or the site (not requests the site makes on its own to draw a page). Each line has id, at, call, tool, status, httpStatus, durationMs, gmailRequests (Gmail API requests jevmail made answering it) and draftCallId for a scoring call. limit is 1–100 (default 20); pass nextBefore as before for the next page. Read one with get_call. Reads Jevmail only.
| Field | Type | Description |
|---|---|---|
limit | integer | Lines to return, 1–100 (default 20). |
before | string | The nextBefore of the previous page. |
{
"method": "tools/call",
"params": {
"name": "list_calls",
"arguments": {
"limit": 1
}
}
}echo '{"limit":1}' | jevmail list_calls --json -curl -X POST https://jevmail.xyz/api/agent/list_calls \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"limit":1}'{
"calls": [
{
"id": "c0ffee00-1111-4222-8333-444455556666",
"at": "2026-10-13T20:31:00.000Z",
"call": "agent_draft_assess",
"tool": "score_draft",
"status": "ok",
"httpStatus": 200,
"durationMs": 5400,
"gmailRequests": 6,
"draftCallId": "8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c"
}
],
"nextBefore": "c0ffee00-1111-4222-8333-444455556666",
"logUrl": "https://jevmail.xyz/log"
}get_call
Read one jevmail call. One call from list_calls by id: what it was asked (input), what jevmail answered (output), each side as JSON when it was whole JSON and marked when cut at 64 KB, the Gmail API requests jevmail made answering it (method, result, time, pacing wait, quota units; never IDs or content), and models: each Jev model request exactly as sent and Jev's response exactly as received (kept whole up to 256 KB a side). Read top to bottom: input, gmail, models, output. Kept 90 days. Reads Jevmail only.
| Field | Type | Description |
|---|---|---|
id (required) | string | A call id from list_calls. |
{
"method": "tools/call",
"params": {
"name": "get_call",
"arguments": {
"id": "c0ffee00-1111-4222-8333-444455556666"
}
}
}echo '{"id":"c0ffee00-1111-4222-8333-444455556666"}' | jevmail get_call --json -curl -X POST https://jevmail.xyz/api/agent/get_call \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":"c0ffee00-1111-4222-8333-444455556666"}'{
"id": "c0ffee00-1111-4222-8333-444455556666",
"tool": "score_draft",
"status": "ok",
"input": {
"newEmail": true
},
"output": {
"callId": "8f0c2a7e-3b1d-4c5e-9f6a-1d2e3f4a5b6c"
},
"gmail": [
{
"method": "messages.get",
"outcome": "success",
"status": 200,
"durationMs": 80
}
],
"models": [],
"url": "https://jevmail.xyz/log/c0ffee00-1111-4222-8333-444455556666"
}get_profile
Whose account this is. The account the token speaks for: a stable id, the Gmail address and the owner's name.
No arguments.
{
"method": "tools/call",
"params": {
"name": "get_profile",
"arguments": {}
}
}echo '{}' | jevmail get_profile --json -curl -X POST https://jevmail.xyz/api/agent/get_profile \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "u_7f3a",
"email": "you@example.com",
"name": "Your Name"
}call_log
Read operation request ids. Read this account's operation calls. Pass requestId alone for one call, or omit it to list recent calls. A list can filter by operation, threadId, messageId, and an inclusive UTC since and until; limit is 1–100 (default 50) and the rows come back oldest first. Each row has the request id, time, operation, caller, mailbox, the message and thread ids the call was given, the ids it returned, and ok or an error code. A next call read by request id also includes rescores, oldest first: the request id, time, mailbox, thread id and message id of each thread next rescored. Rows contain no subjects, bodies or sender addresses, and only the account that made the call can read them. Reads Jevmail only.
| Field | Type | Description |
|---|---|---|
requestId | string | One call's request id, as that call returned it. Pass it alone. |
operation | string | Only calls of this operation. |
threadId | string | Calls whose given or returned ids include this thread. |
messageId | string | Calls whose given or returned ids include this message. |
since | string | Inclusive start, UTC, such as 2026-10-27T19:00:00.000Z. |
until | string | Inclusive end, UTC. |
limit | integer | How many of the newest matches to return, 1–100 (default 50), oldest first. |
{
"method": "tools/call",
"params": {
"name": "call_log",
"arguments": {
"requestId": "req_BBBBBBBBBBBBBBBBBBBBBB"
}
}
}echo '{"requestId":"req_BBBBBBBBBBBBBBBBBBBBBB"}' | jevmail call_log --json -curl -X POST https://jevmail.xyz/api/agent/call_log \
-H "Authorization: Bearer $JEVMAIL_AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"requestId":"req_BBBBBBBBBBBBBBBBBBBBBB"}'{
"requestId": "req_AAAAAAAAAAAAAAAAAAAAAA",
"call": {
"requestId": "req_BBBBBBBBBBBBBBBBBBBBBB",
"at": "2026-10-27T19:00:00.000Z",
"operation": "next",
"caller": "owner",
"mailbox": "you@example.com",
"inputMessageIds": [],
"inputThreadIds": [],
"outcome": "ok",
"outputMessageIds": [
"abc123"
],
"outputThreadIds": [
"abc456"
]
},
"rescores": [
{
"requestId": "req_BBBBBBBBBBBBBBBBBBBBBB",
"at": "2026-10-27T19:00:00.000Z",
"mailbox": "you@example.com",
"threadId": "abc456",
"messageId": "abc123"
}
]
}