Message Optimizer
The messages.optimize endpoint - rewrite one outbound draft in the sender own voice, scored against their edit history, and returned with what changed
POST /messages/optimize takes one outbound draft and hands back a stronger version of it, in the voice of the person sending it. It scores your text, rewrites it up to three times, and returns whichever version graded best — which is sometimes the one you submitted.
This endpoint is not available in production yet. It is served by a separate component that runs in staging only. In every other environment the call succeeds with ok: false and reason: "optimizer_unconfigured" — an honest refusal, not an error and not a silent echo of your own draft. Check ok before reading message.
| Operation | REST | MCP | Scope | |
|---|---|---|---|---|
| Rewrite a draft | messages.optimize | POST /messages/optimize | Not exposed — see below | messages:execute |
/messages/optimizeauth requiredOne outbound draft in, the best-scoring version out, with a line on what changed and why.
What it is not
It does not check whether anything in your message is true.
The optimizer judges style, structure and reply-likelihood against how you write. It never reads your customer conversations and it has no opinion on whether a claim in the draft is one your buyers would recognise. That is a different question with a different instrument: prompt-and-message-eval grades a draft against your own recorded buyer quotes and tells you which claims are backed.
Reach for both and report both. A message can be well-written and assert something no customer ever said, and that is exactly the case a single blended score hides. If you are building an agent that uses both, keep the two verdicts separate in whatever it hands back.
A useful order: eval first, optimizer second. Get the claims grounded, then put the grounded version into the sender's voice. Running it the other way round means polishing text you are about to rewrite.
Request
curl -s -X POST "$AMDAHL_BASE/messages/optimize" \
-H "X-API-Key: $AMDAHL_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "Hi Jordan - saw you shipped SSO last month. Most teams your size end up rebuilding provisioning right after. Worth 15 minutes?",
"channel": "email"
}'| Field | Required | Notes |
|---|---|---|
message | yes | The draft, verbatim. Up to 10,000 characters — this is built for a message, not a document. |
channel | no | email, linkedin or whatsapp. Inferred from the draft when omitted. Only email blends in the reply-probability model; the others are scored on the style rubric alone. |
There is no field for who is sending. The sender is derived from the credential you called with, and cannot be supplied on the wire — see Whose voice it learns.
Response
{
"ok": true,
"message": "Hi Jordan - congrats on the SSO launch. Teams that ship SSO usually hit provisioning next, and it is a worse project than it looks. Worth 15 minutes to compare notes?",
"summary": "Rewrote for opening_specificity, hedging; rubric mean 3.2 -> 4.0 over 2 round(s).",
"unchanged": false,
"run_id": "3f2b...",
"extraction": { "recipient_first_name": "Jordan", "company": "Acme" }
}| Field | Notes |
|---|---|
ok | false means no rewrite was attempted. Read reason and stop. |
message | The best-scoring version. May be your own text, unchanged. |
summary | One line naming what the rewrite aimed at and what the score did. It names the thing you can argue with, rather than claiming "improved clarity". |
unchanged | true when nothing beat what you wrote. Do not present that as an improvement — it is a real and frequent outcome, and saying so is the point. |
extraction | Receiver facts read out of the draft. Absent fields are absent rather than guessed, so you can correct them. |
When ok is false
reason | What happened |
|---|---|
optimizer_unconfigured | The optimizer does not run in this environment. Nothing was attempted and nothing was charged. |
optimizer_timeout | It did not answer in time. Your draft is untouched; retry is safe but costs a fresh run. |
optimizer_error | It answered with an error. Your draft is untouched. |
The failure branch carries no message at all, so there is nothing to mistake for a rewritten draft.
Why it can return your own words
The loop scores your submitted text as round 0, then rewrites up to three times and keeps whichever version scored highest. Rewrites that score worse than what they replaced are a measured, ordinary outcome — so a loop that returned its last attempt would routinely hand back something its own judge rates below your draft. Returning the best round is what stops that, and unchanged: true is what tells you it happened.
A round the judge could not score is excluded from the comparison rather than treated as a zero. A message never wins by breaking the scorer, and never loses because the scorer was down.
Whose voice it learns
The optimizer keeps a per-sender ledger of tells — small, individually-checkable facts about how you write, like "opens with a question" or "never uses an em dash". It learns them from the edits you make to what it gives back, so its next rewrite is closer to your voice than its last.
Two things follow:
- The ledger is per workspace and per sender. The identity is composed server-side from your workspace and your user, and it is not something a request can name. One workspace's learned voice cannot reach another's.
- It has nothing to go on at first. A new sender gets rewrites scored on the base rubric alone. The ledger is what makes it yours, and it fills as you use it.
Latency
Budget for it: a three-round optimize measured a mean of roughly 32 seconds, and the call is allowed to run longer against a hosted judge. The operation declares this, so a client that reads expected_latency_ms off the operation catalog will already be waiting properly. Do not put it on a path where a person is watching a spinner with no explanation.
Why it is not on MCP
The component behind this endpoint has no authentication of its own — it trusts the sender identity it is handed. messages.optimize is what supplies that identity, from an authenticated context, which is the entire reason the endpoint exists rather than the component being exposed directly.
Putting it on a coarse MCP tool would widen that surface for a headless key, which is the same confused-deputy shape that keeps the computer-use verbs off MCP. It stays on REST and the in-app agent surface until the component carries its own auth.