← Back to docs

Mail Support Assistant

Language: EN | EN | SV

Mail Support Assistant

The Mail Support Assistant is an admin-configured support-mail workflow for Tools and the standalone PHP client stored under public/tornevall-tools-mail-assistant.

What it does

  • stores IMAP mailbox settings in Tools admin
  • stores rule-based matching on From, To, Subject, and optional body text
  • allows per-rule static autoreplies or Tools/OpenAI-assisted replies
  • supports per-rule reply sender, BCC, footer, responder/persona/mood/custom instruction fields, selected AI model, and reasoning effort
  • supports mailbox-level unmatched-mail AI triage with ordered add-row If... + Instructions... rules plus one stricter mailbox-owned last fallback
  • lets the standalone client decide whether a handled message should be moved to a target folder or deleted after reply
  • can now sync processed mail threads back into Tools as tracked support cases, with a Tools-admin threaded view and a public case link that can be shared with the recipient
  • centralized Tools cases can now also keep additive full inbound/outbound body fields plus source-instance metadata, so admin can review the actual message text even when the polling cronjob ran on another server
  • can optionally send one operator report mail after a run listing the messages that were not answered
  • keeps unmatched mail untouched
  • keeps the standalone runtime framework-free (plain PHP, no Laravel runtime required)

Requirements

Before the Mail Support Assistant can actually work in production, you need these prerequisites in place:

Tools / account requirements

  • a real Tools / ToolsAPI account on the same Tools host that will serve the config/API requests
  • access to the admin UI at /admin/mail-support-assistant
  • at least one configured mailbox in Tools admin
  • a personal standalone client token from Tools admin:
    • provider_mail_support_assistant
    • active
    • AI-capable (is_ai=1)
  • approved provider_openai access for the token owner if any rule or unmatched fallback uses AI and the owner is not admin

Optional Tools requirements

  • a dedicated relay token provider_mail_support_assistant_mailer if outgoing mail should be sent through POST /api/mail-support-assistant/send-reply
  • permission mail-support-assistant.relay for the relay-token owner when Tools relay should be allowed for non-admin users

Mail / runtime requirements

  • working IMAP credentials stored in Tools admin for each mailbox the runner should poll
  • a reachable Tools base URL from the standalone runtime
  • a chosen outgoing transport strategy:
    • local SMTP
    • PHP mail()
    • custom MTA command
    • or Tools relay
  • PHP runtime with the required extensions and writable local storage/ for logs, summaries, and optional local state

Practical operator note

  • the standalone client does not invent mailbox config locally; mailboxes, rules, responder defaults, unmatched fallback rows, and most behavior are managed in Tools first
  • if there is no Tools account, no valid client token, or no mailbox config in Tools admin, the support assistant will not have enough information to run

Support / changes / issue tracking

If you need help, want a change, or need to report a problem in the standalone client, use GitHub tickets here:

That repository is the right place for runtime/client issues, setup clarifications, and feature requests related to the standalone Mail Support Assistant.

Tools admin UI

Admin-only configuration is available at:

  • /admin/mail-support-assistant

From there you can:

  1. create/update mailboxes
  2. add ordered reply rules per mailbox
  3. rotate the dedicated standalone-client token (provider_mail_support_assistant)
  4. rotate a dedicated relay token (provider_mail_support_assistant_mailer) for outgoing mail relay via Tools
  5. review recent synced support cases that the standalone assistant has pushed back into Tools

Mailbox settings now also include an optional generic AI reply for unmatched emails section. This lets admins enable mailbox-level fallback reply paths for mail that does not match any explicit rule, including:

  • enable/disable unmatched-mail AI fallback
  • optional model override for that mailbox
  • optional reasoning-effort override for that mailbox
  • optional mailbox-level spam-score threshold that suppresses replies and keeps mail unread when the score is too high
  • a strict mailbox-level last fallback with its own checkbox, allow-condition, and final-instructions text
  • mailbox-level add-row IF rules describing which otherwise unmatched mail may be answered before that last fallback is tried
  • per-row reply instructions for those advanced generic replies
  • optional per-row footer/model/reasoning overrides

The Tools admin UI now also supports lightweight AJAX saves for mailbox/rule create/update/delete actions, so common edits no longer have to bounce the whole page through a full form submit. Unmatched fallback IF rows now save in place through AJAX with local busy/success feedback under the row button, and delete also removes the row in place with immediate empty-state restore when the last row is removed.

Important precedence note:

  • mailbox-level generic AI settings are only used when no rule matched the incoming email
  • the unmatched-mail fallback is now enabled only by the mailbox checkbox returned from Tools config; environment-only toggles no longer activate it
  • if that checkbox is off, the standalone client must not evaluate any advanced unmatched row or mailbox-level last fallback at all, even if older unmatched IF/instruction fields still contain text
  • unmatched-mail AI now runs as a strict JSON triage step first: the standalone client only sends a reply when the AI explicitly returns a structured allow decision with high certainty
  • if neither any active unmatched fallback row nor the mailbox's own final allow-condition/instruction pair exists, unmatched-mail AI is treated as unconfigured and no fallback reply is sent
  • unmatched fallback rows are evaluated by sort_order and can fall through to later rows if a prior row is rejected
  • after those advanced rows, the mailbox's own final fallback can still run as the very last unmatched path when its checkbox is enabled and its two text fields are configured
  • that same unmatched-row fallthrough now also applies when one row hits a row-local AI/API evaluation error; the standalone runner logs the failed row and still tries later active rows before giving up
  • if multiple rules match the same incoming email, the standalone client now evaluates all matching rules and chooses the most specific rule first (most active match fields, then longer combined match text, then sort_order)
  • the selected rule and all other matching candidates are now recorded in the standalone diagnostics so rule collisions can be reviewed afterwards
  • if a matched rule has ai_enabled=true, the standalone client now sends that rule's responder/persona/custom instruction/model/reasoning values to Tools as explicit per-request AI overrides
  • the static template text is only used as a fallback if the AI call fails or returns an empty/non-usable reply
  • skipped or non-replied unread messages are now also explicitly pushed back to unread when the IMAP server supports clearing \\Seen, which reduces accidental read-state drift caused by mailbox fetches
  • local message-state may now also keep lightweight thread/context excerpts so later AI replies can continue a conversation more naturally without using that history as a dedupe gate
  • when In-Reply-To / References link a follow-up mail to an earlier handled conversation, the standalone runner can now also reuse the earlier matched rule or prioritize the earlier unmatched fallback row so the thread stays on the same support path
  • standalone-generated replies now also keep a generated outgoing reply_message_id in local state, which makes later Gmail/Outlook follow-ups more likely to reconnect to the same conversation even when the user replies to the assistant's sent mail instead of the original inbound message
  • standalone-generated replies can now also stamp one stable issue/case id into the subject (default format like [Ärende MSA-ABC12345]), and later replies reuse that same tag instead of appending a new one every time the conversation is answered
  • standalone-generated HTML replies now also convert normal markdown from AI/operator reply text into real HTML structure (for example headings, lists, links, emphasis, and inline code) instead of exposing raw markdown markers inside the styled reply card
  • if a follow-up arrives in an older/malformed format where usable In-Reply-To / References are missing, the standalone runner can now still try a normalized subject + same participants fallback before treating the message as a fresh no-match conversation
  • if the linked earlier conversation was already approved through unmatched-mail AI, the standalone runner can now continue that same unmatched row directly for explicitly linked follow-ups instead of re-running the first allow-condition check from scratch on the same thread
  • rate-limited AI failures (429 / Too Many Attempts) are retried automatically before the standalone client gives up on that rule's AI reply
  • if an AI-enabled rule has no explicit template_text fallback, the standalone client no longer sends the old generic sentence Thank you for your message. We have reviewed it. when AI fails; it now aborts that reply and logs the error instead
  • clear-text contact-form style mails that embed body labels such as From:, Subject:, Sender IP:, and Message Body: now preserve the actual problem paragraph in standalone summaries/AI context instead of collapsing to only the first header-like line
  • HTML-only inbound mail is now decoded into readable plain text before rule matching, unmatched-mail AI triage, appended request summaries, and saved local message copies are built, so the standalone client no longer behaves as though only the subject exists when the body arrived as HTML
  • MIME body decoding is now charset-aware, which makes non-UTF8 or otherwise malformed inbound mail much more likely to yield usable body text instead of garbled AI/rule-matching context
  • when the strict unmatched fallback actually sends a reply, the standalone runner marks the message as seen immediately so it is not picked up again by the unread-mail poller

The generated token is stored as a personal api_keys row and is automatically marked AI-capable (is_ai=1).

Relay tokens (provider_mail_support_assistant_mailer) are personal non-global keys and are intended only for POST /api/mail-support-assistant/send-reply. They are not AI-receiver tokens.

The admin UI now also uses neutral support-oriented example values (order status, customer care, reference numbers, processed folders) so new rules do not start from a too-specific legal/copyright scenario.

Threaded case tracking

  • Tools now stores synced Mail Support Assistant conversations as threaded support cases.
  • Admin operators can review them directly from /admin/mail-support-assistant and open a dedicated threaded case page from there.
  • That threaded case page can now also show centrally stored full body text/HTML and the source runner/server identity for each synced message.
  • The standalone dashboard can also link each message card back to the matching Tools case when that thread has already been synced.
  • Outgoing assistant replies can now append a public case-tracking URL for the recipient when the standalone runtime is allowed to create that case link first.

Standalone inbox visibility

  • The standalone dashboard is still not meant to replace full mailbox administration, but it now also merges a live unread IMAP preview into the activity tab.
  • This means unread mail can now appear in the assistant web GUI even before another saved run has written a new latest-run summary.
  • Tools admin itself shows the synced/threaded case view; the standalone dashboard shows the local live unread preview plus latest-run/manual handling actions.

Optional unanswered-message report

  • Standalone can now send one summary mail after a run listing messages that were not answered.
  • Enable it with:
    • MAIL_ASSISTANT_UNANSWERED_REPORT_ENABLED=true
    • MAIL_ASSISTANT_UNANSWERED_REPORT_TO=user@example.com[,second@example.com]
  • The report is intended for operator follow-up and does not stop the run if the report mail itself fails.

API additions used by the standalone client

  • GET /api/mail-support-assistant/cases
    • Auth: same personal AI-capable token model as GET /api/mail-support-assistant/config
    • Purpose: fetch recent Tools-side threaded support cases visible for the token owner
  • POST /api/mail-support-assistant/cases/sync
    • Auth: same personal AI-capable token model as GET /api/mail-support-assistant/config
    • Purpose: let the standalone client upsert one inbound/outbound support-thread snapshot back into Tools
    • Typical payload fields: mailbox_id, message_id, message_key, reply_issue_id, thread_key, subject, from, to, status, reason, additive body_text, additive body_text_reply_aware, additive body_html, body_excerpt, optional reply_message_id, additive reply_body_text, additive reply_body_html, and optional reply_excerpt

Because the standalone client fetches mailbox and rule config directly from Tools by bearer token, many deployments only need the CLI runner or cron job. The small PHP dashboard is optional and does not normally have to be exposed publicly.

That same CLI/dashboard runner stack is now overlap-safe too: each run acquires a local non-blocking lock file under storage/state/run.lock, so if cron or the dashboard tries to start another run while one is already active, the second attempt is skipped cleanly with a runner_already_active conflict instead of polling the same unread mailbox twice.

The shell wrapper cron-run.sh now also keeps its own PID-aware lock directory (default storage/state/cron-run.lock.d) with stale-lock cleanup, so overlapping cron invocations can be rejected even before the PHP runner starts. That shell lock path can be overridden through MAIL_ASSISTANT_CRON_LOCK_DIR when several deployments share the same filesystem.

The standalone dashboard is now also more operator-friendly than before:

  • it presents the latest run as mailbox/message cards instead of only raw JSON blocks
  • configured mailbox cards are now still shown there even before any dry-run or real run has saved message activity, with an explicit note that this is a latest-run operator surface rather than a live IMAP mail client
  • each message can expand thread diagnostics, selected rule/no-match reasoning, and optional saved local headers/body preview when a cached message copy exists
  • the runner now refreshes one stable local message copy per scanned mail, so these dashboard/manual operator flows keep the readable body content more reliably instead of depending only on shorter latest-run excerpts
  • operators can now also act on latest-run messages directly from that dashboard: assign a local rule context, send a manual reply, or mark the message handled/read for manual follow-up without waiting for another cron pass
  • manual replies use the same multipart plain-text + styled HTML reply pipeline as automatic replies, so operator-written replies keep the same visible formatting and appended request-summary behavior
  • Tools config is summarized in a more readable mailbox/rule overview, and the dashboard now exposes readable matched-rule rows plus unmatched AI/IF rows (including footer/model/reasoning details) while raw JSON remains available in collapsible advanced sections
  • explicit alert cards now surface both Tools-side daily AI budget pressure (when exposed in config) and standalone-detected quota/billing failures from the latest run

That dashboard should still be treated as a lightweight operator surface, not as a full replacement for the real Tools admin UI.

Standalone client token

The standalone PHP client should use a personal bearer token such as:

  • provider_mail_support_assistant

Important rules:

  • receiver/client tokens that talk to Tools on OpenAI's behalf should be stored with is_ai=1
  • the upstream provider_openai secret is not a receiver token and is automatically excluded from is_ai
  • if a rule enables AI replies, the token owner still needs approved provider_openai access unless the owner is admin

API endpoint for the standalone client

GET /api/mail-support-assistant/config

Auth:

  • Authorization: Bearer <personal AI-capable token>
  • X-Api-Key: <personal AI-capable token>
  • apikey=<personal AI-capable token>

Expected token model:

  • personal non-global key
  • active
  • AI-capable (is_ai=1) or legacy tools_ai_bearer

Success response:

{
  "ok": true,
  "message": "Mail Support Assistant config loaded.",
  "user": {
    "id": 1,
    "name": "Admin User",
    "email": "admin@example.com",
    "has_openai_access": true,
    "ai_daily_budget": {
      "feature": "social_media_extension",
      "default_model": "gpt-4o-mini",
      "max_output_tokens_default": 800,
      "cap": 60000,
      "used": 4200,
      "remaining": 55800,
      "is_unlimited": false,
      "source": "user_override"
    }
  },
  "token": {
    "provider": "provider_mail_support_assistant",
    "user_id": 1,
    "is_personal": true,
    "is_ai": true
  },
  "mailboxes": [
    {
      "id": 5,
      "name": "Main inbox",
      "imap": {
        "host": "imap.example.com",
        "port": 993,
        "encryption": "ssl",
        "username": "support@example.com",
        "password": "...",
        "folder": "INBOX"
      },
      "defaults": {
        "from_name": "Support Team",
        "from_email": "support@example.com",
        "bcc": "audit@example.com",
        "footer": "Kind regards",
        "run_limit": 20,
        "mark_seen_on_skip": false,
        "spam_score_reply_threshold": 6.5,
        "generic_no_match_ai_enabled": true,
        "generic_no_match_ai_model": "gpt-4o-mini",
        "generic_no_match_ai_reasoning_effort": "medium",
        "generic_no_match_if": "If the unmatched mail is a normal support request and clearly not spam, fraud, phishing, or vague sales outreach, the final fallback may answer.",
        "generic_no_match_instruction": "Reply briefly, ask for the missing account detail, and stay on the sender's original language.",
        "generic_no_match_footer": "Kind regards",
        "generic_no_match_rules": [
          {
            "id": 41,
            "sort_order": 0,
            "is_active": true,
            "if": "If the unmatched mail is an unsolicited sales pitch, we should decline.",
            "instruction": "Decline politely and do not invite follow-up.",
            "footer": "Kind regards",
            "ai_model": "gpt-4o-mini",
            "ai_reasoning_effort": "medium"
          }
        ]
      },
      "rules": [
        {
          "id": 11,
          "name": "Order status reply",
          "match": {
            "from_contains": "customer@example.com",
            "to_contains": "support@example.com",
            "subject_contains": "order status",
            "body_contains": "order number"
          },
          "reply": {
            "enabled": true,
            "ai_enabled": true,
            "subject_prefix": "Re:",
            "from_name": "Customer Care",
            "from_email": "care@example.com",
            "bcc": "archive@example.com",
            "template_text": null,
            "footer_mode": "static",
            "footer_text": "Kind regards",
            "responder_name": "Alex",
            "persona_profile": "Helpful support agent who writes short, clear responses.",
            "mood": "calm",
            "custom_instruction": "Summarize the request and ask for any missing reference number.",
            "ai_model": "gpt-4o-mini",
            "ai_reasoning_effort": "medium"
          },
          "post_handle": {
            "move_to_folder": "Processed",
            "delete_after_handle": false
          }
        }
      ]
    }
  ]
}

Errors:

  • 401 when the bearer token is missing or rejected

POST /api/mail-support-assistant/send-reply

Purpose:

  • relay one outgoing reply through Tools-hosted mail transport when the standalone runtime cannot or should not use local mail()/MTA.

Auth:

  • dedicated personal token with provider provider_mail_support_assistant_mailer
  • token owner must have permission mail-support-assistant.relay (admin bypass applies)

Body (example):

{
  "mailbox_id": 5,
  "rule_id": 11,
  "mode": "fallback_after_php_mail",
  "to": "customer@example.com",
  "cc": [],
  "bcc": ["audit@example.com"],
  "from": "Support Team <support@example.com>",
  "subject": "Re: Order status",
  "body": "Thanks for your message.",
  "body_html": "<html><body><p>Thanks for your message.</p></body></html>",
  "message_meta": {
    "message_id": "<abc@example.com>",
    "uid": 12345
  }
}

Success response:

{
  "ok": true,
  "message": "Reply relayed via Tools mail transport.",
  "relay": {
    "provider": "provider_mail_support_assistant_mailer",
    "user_id": 1,
    "mailbox_id": 5,
    "rule_id": 11,
    "mode": "fallback_after_php_mail"
  }
}

Errors:

  • 401 when relay token is missing/invalid/inactive
  • 403 when token owner lacks mail-support-assistant.relay
  • 422 for invalid mail payload fields

Additive relay field:

  • body_html is optional.
  • When present, Tools relays the outgoing reply as multipart/alternative with the original plain-text body plus the HTML part from body_html.
  • When omitted, Tools keeps the earlier plain-text-only relay behavior.
  • Relay now also stamps outgoing mail with X-Tornevall-Mail-Assistant: sent for anti-loop detection in standalone polling.

Operational notes

  • Mailboxes/rules are configured in Tools, but the actual IMAP polling and reply execution are intended to run from the standalone client / cron job.
  • Mailbox credentials live in Tools admin; the standalone client stays databaseless locally and only stores env/session/log/summary data plus optional local message copies under storage/.
  • Standalone reply transport is now SMTP-first by default (MAIL_ASSISTANT_MAIL_TRANSPORT=smtp) and can be tuned with MAIL_ASSISTANT_SMTP_* keys, so local Postfix/sendmail is no longer required in default setups.
  • For WSL/self-signed/local-CA setups, standalone can now also disable or redirect TLS verification separately for Tools API calls (MAIL_ASSISTANT_TOOLS_SSL_VERIFY, MAIL_ASSISTANT_TOOLS_CA_BUNDLE) and SMTP (MAIL_ASSISTANT_SMTP_SSL_VERIFY, MAIL_ASSISTANT_SMTP_CA_FILE).
  • If neither the matched rule nor the mailbox defaults define a BCC recipient, standalone can now also fall back to MAIL_ASSISTANT_DEFAULT_BCC from its local .env file.
  • Standalone reply transport now also supports an explicit ordered fallback chain through MAIL_ASSISTANT_MAIL_FALLBACK_TRANSPORTS.
  • If primary transport is tools_api but the dedicated relay token is missing, the standalone runner now skips relay mode and continues with SMTP/local fallback transports instead of aborting the reply.
  • If SMTP/local transport fails and fallback is enabled, the standalone runner can retry through POST /api/mail-support-assistant/send-reply.
  • Outgoing replies from the standalone runtime are now generated as multipart/alternative: a plain-text body is always kept for compatibility, and a styled HTML version is added for mail clients that prefer rich formatting.
  • That styled HTML reply body now also renders ordinary markdown from AI/operator text into real HTML structure instead of showing raw markdown syntax in the visible mail.
  • Static-footer reply mode now strips trailing AI-generated signoff blocks before appending the configured footer, which prevents duplicate closings like Best regards followed by Regards.
  • Standalone now treats incoming unread mail with X-Tornevall-Mail-Assistant: sent as assistant-originated loop candidates: they are skipped before rule matching/reply and marked seen to avoid endless self-reply retries.
  • When no rule matches a message, the client should leave it untouched.
  • When mailbox-level unmatched-mail AI fallback is enabled in Tools admin, the standalone client can instead run one strict AI triage using the additive defaults.generic_no_match_* fields returned by /api/mail-support-assistant/config.
  • Those mailbox-level generic_no_match_if / generic_no_match_instruction fields now represent the mailbox's own last fallback and are no longer aliases for the first advanced unmatched row.
  • Advanced unmatched rows remain in defaults.generic_no_match_rules[] and are still checked before that last mailbox-level fallback.
  • Those additive mailbox defaults can now also include defaults.generic_no_match_ai_reasoning_effort.
  • Those additive mailbox defaults can now also include defaults.generic_no_match_if.
  • The AI is instructed to ignore outer SpamAssassin wrapper prose when it is only a forwarding wrapper, while still using SpamAssassin score/tests as safety signals.
  • Unmatched-mail replies are now sent only when the AI returns valid JSON, can_reply=true, certainty="high", and a non-empty reply payload. Any other result leaves the message untouched.
  • The standalone unmatched-mail parser now also tolerates a few common sloppy JSON formatting mistakes from the provider/model (for example smart quotes or trailing commas) before it finally classifies the response as invalid JSON.
  • If that strict unmatched fallback does send a reply, the runner finalizes the message by marking it seen immediately (except during dry-run execution).
  • Standalone diagnostics for that no-match path now also include generic_ai_decision.evaluated_no_match_rules[], so operators can review which unmatched fallback rows were actually tried, in order, before a reply was sent or the mail remained unread.
  • Per-message run diagnostics now also expose thread_key, in_reply_to, and references[], which makes it easier to see whether a skipped message actually carried usable reply-chain metadata.
  • Matched AI-enabled rules now use reply.responder_name, reply.persona_profile, reply.custom_instruction, reply.ai_model, and additive reply.ai_reasoning_effort as authoritative AI overrides for that one reply instead of silently looking like a plain static-template response.
  • The standalone client now also retries temporary AI rate-limit failures before abandoning the AI path.
  • Standalone AI replies now explicitly default to the same language as the incoming email (response_language=auto) unless the sender clearly asks for another language.
  • The standalone fallback model now defaults to o4; that fallback retry intentionally omits reasoning_effort metadata so o4 behaves as a plain non-reasoning fallback here.
  • GET /api/mail-support-assistant/config can now also expose additive user.ai_daily_budget metadata so the standalone client/operator can inspect the effective daily AI token cap, remaining budget, default model, and default max-output-token setting used by Mail Support Assistant's SocialGPT-backed reply path.
  • If an AI-enabled matched rule still fails after retries and no explicit reply.template_text fallback is configured, the client now leaves that reply unsent and logs the error instead of emitting a misleading generic canned answer.
  • Mailbox/run errors from those failed AI replies now also include the model trail that was tried, which makes empty-response cases easier to diagnose.
  • If a message is skipped because no rule matches, or because that generic unmatched-mail fallback is disabled, unconfigured, rejected as unsafe, invalid, empty, or otherwise fails, the message now remains unread even when mark_seen_on_skip is enabled.
  • Exception: when the strict unmatched AI/final-fallback path has actually been evaluated and ends in a terminal reject/error outcome, the standalone runner now marks the message seen for manual follow-up so the same unread mail does not keep consuming AI attempts forever.
  • Standalone quota/billing failures from AI mail flows are now promoted into explicit runtime alerts and can optionally send a cooldown-limited operator alert mail when the standalone runtime configures MAIL_ASSISTANT_QUOTA_ALERT_EMAIL.
  • mark_seen_on_skip should therefore be treated as a deliberate heuristic-spam convenience for cases like high-score SpamAssassin skips, not as a way to auto-dismiss configuration-driven no-match mail.
  • The standalone runner only polls unread mail. Mail that is already marked read at ingest is skipped immediately.
  • The local storage/state/message-state.json file is now diagnostic history only. An unread message may still be re-evaluated on a later run even if the same Message-Id already exists in that file, which means newly added rules can apply without clearing local state first.
  • That same local state may still be used as a continuity hint for reply chains: it can recover the earlier selected rule or earlier matched unmatched-row for a linked follow-up, but it must never block unread reprocessing by itself.
  • That continuity lookup now also understands stored outgoing reply_message_id values and can fall back to normalized subject + same participants when explicit reply headers were lost by an older mail format or intermediary rewrite.
  • AI should only be enabled per rule when you explicitly want Tools/OpenAI involved.
  • The standalone client now also inspects SpamAssassin headers so very high-score junk can be skipped more safely, while wrapper-style SpamAssassin rewrites can still be copied locally and stripped before rule matching or AI reply generation.
  • Mailbox defaults can now include spam_score_reply_threshold; when a message score is above this value, standalone suppresses reply handling for that message and keeps it unread.
  • Score parsing now also reads X-Spam-Score directly when X-Spam-Status does not include a parseable score= token.
  • Outgoing replies now also append a compact excerpt of the original incoming request, and malformed wrapper-style mails are cleaned more aggressively first so forwarded .eml header dumps or SpamAssassin wrapper text do not become the visible quoted request summary.