# SpamFoo Help (full text) > Help for SpamFoo, which stops spam and sorts email into Primary, Transactions, Updates, and Promotions. Each page below starts with its source URL. *** Source: https://help.spamfoo.com/ Last updated: 2026-10-08 # SpamFoo Help Center Find out how to use SpamFoo, run SpamFoo Client on your server, and connect it to your mail server or your own code. ## Start With Who You Are ### I Use SpamFoo for My Email Read your dashboard, rescue messages from spam, and set your own rules. Guides: Use SpamFoo ### I Run the Mail Server Install SpamFoo Client, keep it running, and fix problems. Guides: Install and Run ### I Am Writing Code Call the local API to classify mail and send corrections back. Integrations: Build Your Own ## Popular Guides - [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result) - [See Why a Message Was Spam](https://help.spamfoo.com/guides/why-spam) - [Install SpamFoo Client](https://help.spamfoo.com/guides/install) - [Fix Common Problems](https://help.spamfoo.com/guides/common-problems) ## Popular Technical Pages - [Set Up SmarterMail](https://help.spamfoo.com/integrations/smartermail/setup) - [Start With the Local API](https://help.spamfoo.com/integrations/local-api) - [Classify a Message](https://help.spamfoo.com/api/classify) - [API Overview](https://help.spamfoo.com/api) *** Source: https://help.spamfoo.com/guides Last updated: 2026-10-20 # Get to Know SpamFoo SpamFoo stops spam and sorts the rest of your email so the important messages are easy to find. This page explains the main ideas and where to go next. ## What SpamFoo Does SpamFoo looks at each new message and makes two decisions: - **Is it spam?** Spam and phishing are kept out of your inbox. Phishing is a fake message that tries to trick you into giving up a password, money, or personal details. - **What kind of mail is it?** Mail that isn't spam gets one of four Email Classifications: Primary, Transactions, Updates, or Promotions. SpamFoo looks at each message on its own, so mail from the same company can land in different classifications. See [The Four Email Classifications](https://help.spamfoo.com/guides/how-classification-works#classifications). To make these decisions, SpamFoo looks at many parts of a message, such as who sent it, whether the sender proved who they are, the links inside it, and what the message says. See [How SpamFoo Classifies Email](https://help.spamfoo.com/guides/how-classification-works) for the steps. ## The SpamFoo Client SpamFoo Client is the program that does the work. Its program and service are named `spamfoo-client`. It usually runs on the same server as your mail server, but it can also run on a separate server. - **Checks mail on your server.** SpamFoo Client downloads its models to your server and uses them to check each message there. This is faster than sending mail to an online service, and the message doesn't leave your server to be checked. Rules and learned preferences are stored on your server too. - **Uses the CPU.** No graphics card is needed. Servers with a supported NVIDIA graphics card can turn on [GPU acceleration](https://help.spamfoo.com/guides/gpu-acceleration). - **Includes a dashboard.** The SpamFoo Dashboard is a web page where you can see how your mail was handled and correct mistakes. What you can do there depends on whether you're a user, a domain administrator, or a system administrator. See [Open Your Dashboard](https://help.spamfoo.com/guides/open-dashboard). ## Key Ideas - **Spam Rules:** Simple instructions such as "mail from this sender is always spam" or "never spam". System administrators, domain administrators, and users can each set their own. - **Corrections:** When you mark a message as spam or not spam, or move it to a different classification, SpamFoo learns from it. See [How Corrections Are Used](https://help.spamfoo.com/guides/corrections). - **Personalization:** SpamFoo remembers each user's corrections and uses them for that user's future mail. See [How Personalization Works](https://help.spamfoo.com/guides/personalization). - **Anti-Phishing:** SpamFoo checks for known phishing links and lookalike domain names. With a SpamFoo Anti-Phishing license, it also catches fake senders that pretend to be people in your organization, and the Anti-Phishing model reads each message to catch phishing and scams. See [Protect Against Phishing](https://help.spamfoo.com/guides/phishing-protection). - **Privacy:** Message text stays on your server. Corrections are reported to SpamFoo as mathematical data that can't be turned back into the original text, unless an administrator or the owner of the mailbox chooses to share a sanitized copy. In a sanitized copy, known personal details are replaced with placeholders. See [How Corrections Are Used](https://help.spamfoo.com/guides/corrections). ## Where to Go Next Pick the description that fits you best. ### You Use SpamFoo for Your Email This includes domain administrators who manage mail for one domain. - [Open Your Dashboard](https://help.spamfoo.com/guides/open-dashboard) - [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result) - [Manage Your Rules](https://help.spamfoo.com/guides/my-rules) - [Manage a Domain](https://help.spamfoo.com/guides/manage-domain) ### You Run the Mail Server - [System Requirements](https://help.spamfoo.com/guides/system-requirements) - [Install SpamFoo Client](https://help.spamfoo.com/guides/install) - [Configure SpamFoo Client](https://help.spamfoo.com/guides/configure) - [Use the Admin Console](https://help.spamfoo.com/guides/admin-console) - [Fix Common Problems](https://help.spamfoo.com/guides/common-problems) If you use SmarterMail, start with the [SmarterMail Overview](https://help.spamfoo.com/integrations/smartermail). ### You Are Connecting Your Own Software - [How Integration Works](https://help.spamfoo.com/integrations) - [Start With the Local API](https://help.spamfoo.com/integrations/local-api) - [Send Corrections Back](https://help.spamfoo.com/integrations/send-corrections) - [API Overview](https://help.spamfoo.com/api) ## Get More Help - **Problems:** Start with [Fix Common Problems](https://help.spamfoo.com/guides/common-problems) and [Use Diagnostic Tools](https://help.spamfoo.com/guides/diagnostics). - **Support:** [Contact support](https://www.spamfoo.com/contact) if you need more help. - **Service status:** Check [status.spamfoo.com](https://status.spamfoo.com). *** Source: https://help.spamfoo.com/guides/how-classification-works Last updated: 2026-10-20 # How SpamFoo Classifies Email SpamFoo gives every message a result. This page explains each result and the steps SpamFoo takes to choose it, so you can understand why a message ended up where it did. ## Spam and Phishing SpamFoo first decides whether a message is unwanted. Unwanted mail gets one of two results: - **Spam:** Junk mail you didn't ask for. Examples: bulk mail from unknown senders, fake offers, and scams. - **Phishing:** A fake message that tries to trick you into giving up a password, money, or personal details. SpamFoo uses this result when a phishing check finds a link to a known phishing site, a display name that pretends to be someone your administrator protects, or a sender domain spelled to look like a well-known domain. With a SpamFoo Anti-Phishing license, the Anti-Phishing model can also give this result. See [Protect Against Phishing](https://help.spamfoo.com/guides/phishing-protection). ## The Four Email Classifications Mail that isn't spam or phishing gets one of four Email Classifications. | Classification | What It Means | Examples | | --- | --- | --- | | **Primary** | Important mail you should see first. | Messages from real people, replies in a conversation, sign-in codes, and urgent account alerts. | | **Transactions** | Mail about money, orders, and accounts. | Receipts, order and shipping confirmations, bills, and subscription renewals. | | **Updates** | Useful news that is rarely urgent. | Social media notices, forum posts, newsletters, and project updates. | | **Promotions** | Sales and marketing from real companies. These aren't spam, but they are trying to sell something. | Sale announcements, coupons, and new product offers. | SpamFoo picks a classification for each message, not for each sender. One company can send many kinds of mail. For example, Amazon might send you: - A sign-in code (Primary) - A receipt (Transactions) - An AWS service notice (Updates) - A sale announcement (Promotions) SpamFoo looks at what the message says along with who sent it, so each one can get a different classification. ## What SpamFoo Looks At SpamFoo reads many small clues, called signals, from each message. Some examples: - **The sender:** the sender's address and domain, and the server the message came from. - **Proof of the sender:** results of email checks called SPF, DKIM, and DMARC. These show whether the message really came from the domain it claims to be from. - **Links:** how many links there are and where they go. - **Attachments:** whether the message has any. - **Warning signs:** for example, a reply address on a different domain than the sender, or text that hides words with odd spacing or look-alike characters. - **Meaning:** what the message says and what it is trying to get you to do. SpamFoo turns the text into a list of numbers that captures its meaning, so it can compare messages without keeping their words. ## The Steps SpamFoo runs these checks in order. When an early step reaches a clear answer, the later steps are skipped. 1. **Spam Rules.** A Spam Rule tells SpamFoo to always mark mail from a sender as Spam or Not Spam. Users, domain administrators, and system administrators create them in the SpamFoo Dashboard. If the matching rule is one of your rules or a Domain Spam Rule, it decides the result right away and no other check runs. 2. **Phishing checks.** SpamFoo looks for links to known phishing sites, display names that pretend to be people your administrator protects, and sender domains that are spelled to look like well-known domains. The display name check needs a SpamFoo Anti-Phishing license. A match marks the message as phishing. 3. **Your past corrections.** SpamFoo compares the message to messages you corrected before. If it closely matches one, especially from the same sender, SpamFoo follows your correction. See [How Personalization Works](https://help.spamfoo.com/guides/personalization). 4. **Sender reputation.** SpamFoo checks whether the sender's domain or server is on SpamFoo's list of senders known to be good or bad. A domain on the good list is only trusted when the message proves it really came from that domain. 5. **Spam models.** If nothing above decided, small machine learning models read the message. One looks at the text, one looks at the other signals, and a third combines their answers into the spam result. 6. **Email Classification.** Mail that isn't spam is sorted into Primary, Transactions, Updates, or Promotions. When the model isn't sure, the message goes to Primary so you don't miss it. 7. **Anti-Phishing model.** With a SpamFoo Anti-Phishing license, and when the model is turned on, SpamFoo scores the message with the Anti-Phishing model. A high enough score marks the message as phishing. See [Turn On the Anti-Phishing Model](https://help.spamfoo.com/guides/phishing-protection#anti-phishing-model). 8. **System Spam Rules.** System Spam Rules are applied. See [How System Spam Rules Fit In](https://help.spamfoo.com/guides/my-rules#system-rules). ## What Your License Includes Your license decides which steps run. - With spam protection and Email Classifications, every step runs, and mail that isn't spam gets one of the four classifications. - With Email Classifications only, SpamFoo skips the spam steps and gives every message one of the four classifications. - With spam protection only, SpamFoo decides spam or not spam, and mail that isn't spam gets no classification. - With SpamFoo Anti-Phishing, the Anti-Phishing model step runs and display names are checked against protected identities. Without it, the model doesn't download or run, and protected identities aren't checked. The checks for known phishing links and look-alike domains run either way. ## Where You See Results - **Your mailbox:** How classifications appear depends on your mail program. If you use SmarterMail, see [What SmarterMail Users See](https://help.spamfoo.com/integrations/smartermail/users). - **Your dashboard:** The SpamFoo dashboard shows the result for each message and which step decided it. See [Review Your Messages](https://help.spamfoo.com/guides/my-messages) and [See Why a Message Was Spam](https://help.spamfoo.com/guides/why-spam). *** Source: https://help.spamfoo.com/guides/open-dashboard Last updated: 2026-10-27 # Open Your Dashboard The SpamFoo Dashboard shows how SpamFoo handled your mail and lets you adjust it. This page explains how to open it and what you'll see. ## Open It From SmarterMail If your mail runs on SmarterMail, you open the dashboard from webmail. You don't need a separate password. 1. Sign in to SmarterMail webmail. 2. Go to your email. 3. Open the actions menu and choose **SpamFoo Dashboard**. The dashboard opens in a new window. For more about SpamFoo inside webmail, see [What SmarterMail Users See](https://help.spamfoo.com/integrations/smartermail/users). > **Don't see the link?** The **SpamFoo Dashboard** link only appears when your administrator has turned on SpamFoo for your domain. Ask your administrator. ## Open It on Other Mail Servers On other mail servers, your administrator or your mail system gives you a sign-in link. Ask your administrator if you don't have one. 1. Open the sign-in link. A **Sign in to SpamFoo** page appears. 2. Click **Continue**. Each link works only once and expires after a short time, usually 15 minutes. After you sign in, you stay signed in for 24 hours while you use the dashboard. If the page says **This sign-in link cannot be used**, the link expired or was already used. Ask for a new one. If you go to the dashboard address directly, you may see a **Sign in to SpamFoo** page that asks for a **Proxy secret**. That page is only for the administrator of the server. It gives full control of SpamFoo, so users don't sign in there. ## What You See The menu on the left depends on your role. Your role appears in the top right corner of the dashboard. - **User:** The **My Email** menu, with **Overview**, **My Messages**, and **My Rules**. It covers your own mailbox only. - **Domain Admin:** The same **My Email** menu, plus a **Domain Admin** menu with **Domain Overview**, **Mailboxes**, **Rules**, and **Anti-Phishing**. It covers your own mailbox, plus totals, rules, and Anti-Phishing for your domain. If you signed in as a Domain Admin with a link that doesn't name your own mailbox, you see only the **Domain Admin** menu. A domain admin can see how much mail each mailbox in the domain received, but can't read other people's messages. Only your own messages appear in **My Messages**. System administrators see a different menu for the whole server. See [Use the Admin Console](https://help.spamfoo.com/guides/admin-console). The top right corner also has: - **Your role:** **User** or **Domain Admin**. A domain admin also sees their domain name under it. If you signed in with a link, the role menu also has **Log out**. - **A Domain picker**, when your sign-in link lets you manage more than one domain. Choose the domain to work on. - **A language menu** to change the dashboard language. - **A light bulb button** to switch between light mode and dark mode. ## Pages for Users and Domain Admins - [Read Your Overview](https://help.spamfoo.com/guides/overview): totals and charts for your mailbox. - [Review Your Messages](https://help.spamfoo.com/guides/my-messages): search your messages and see the details of each one. - [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result): correct SpamFoo when it gets a message wrong. - [See Why a Message Was Spam](https://help.spamfoo.com/guides/why-spam): find the reason behind a result. - [Manage Your Rules](https://help.spamfoo.com/guides/my-rules): always mark a sender as spam or not spam, and review what SpamFoo learned from you. - [Manage a Domain](https://help.spamfoo.com/guides/manage-domain): for domain admins. ## Troubleshooting ### SpamFoo Dashboard Unavailable **Cause:** The SpamFoo service on the mail server is starting, stopped, or not responding. **Fix:** Wait a minute. The page refreshes on its own when the service is back. If it keeps happening, contact your administrator. **Cause:** Your administrator hasn't turned on SpamFoo. **Fix:** Ask your administrator to turn on SpamFoo for your domain. ### Pages Show No Mail **Cause:** SpamFoo hasn't checked any mail for your mailbox in the selected time range. **Fix:** Choose a longer time range in the top right of the page. If the page is still empty, ask your administrator whether SpamFoo is checking your mail. *** Source: https://help.spamfoo.com/guides/overview Last updated: 2026-10-20 # Read Your Overview The Overview page is the first page you see in the SpamFoo Dashboard. It sums up how SpamFoo handled the mail sent to your mailbox. ## Open the Overview 1. Open the SpamFoo Dashboard. See [Open Your Dashboard](https://help.spamfoo.com/guides/open-dashboard). 2. In the left menu, go to **My Email > Overview**. The page title is **My Email**, with your email address under it. Everything on the page covers only your own mailbox. ![Overview page titled My Email for user@example.com showing six tiles: Total Scanned, Spam Rate, Busiest Day, Not Spam, Spam, and Phishing, with the Last 30 Days menu in the top right.](https://help.spamfoo.com/images/user-overview-top.png) ## Choose a Time Range Use the menu in the top right to pick **Last 7 Days**, **Last 30 Days**, or **Last 90 Days**. The tiles, the chart, and the Classifications card all follow your choice. The dashboard remembers it the next time you open the page in the same browser. ## Summary Tiles The row of tiles at the top shows totals for the time range. Most tiles have a small line under the number that shows the daily values. | Tile | What It Shows | | --- | --- | | **Total Scanned** | How many messages SpamFoo checked for your mailbox. | | **Caught Rate** | The share of your mail that SpamFoo caught as spam or phishing. | | **Busiest Day** | The day you got the most mail, and how many messages arrived that day. | | **Not Spam** | Messages SpamFoo let through as normal mail. | | **Spam** | Messages SpamFoo marked as spam. | | **Phishing** | Messages SpamFoo marked as phishing. Phishing is a fake message that tries to trick you into giving up a password, money, or personal details. | ## Mail Trend The **Mail Trend** chart shows your **Not Spam**, **Spam**, and **Phishing** mail for each day in the time range. Use it to spot days when spam suddenly went up. ## Classifications The **Classifications** card shows how SpamFoo sorted the mail that wasn't spam. Each row shows a classification, how many messages it got, and its share of your non-spam mail. - **Primary**, **Updates**, **Transactions**, and **Promotions** are the four Email Classifications. See [The Four Email Classifications](https://help.spamfoo.com/guides/how-classification-works#classifications). - **Not classified** counts non-spam mail that didn't get a classification. ## Recent Messages The **Recent Messages** card lists your newest messages. Each row shows: - **Sender:** the sender's domain, with the date and time under it. - **Detection Type:** **Not Spam**, **Spam**, or **Phishing**. - **Classification:** the Email Classification. Spam and phishing show a dash instead. To work with a message: - Click a message to open its details. See [Review Your Messages](https://help.spamfoo.com/guides/my-messages#message-details). - Right-click a message (or press and hold on a touch screen) to open its details, create a rule for the sender, or report a wrong result. See [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result). - Click **View all** to open **My Messages** with all of your messages. *** Source: https://help.spamfoo.com/guides/my-messages Last updated: 2026-10-08 # Review Your Messages The **My Messages** page lists the messages SpamFoo checked for your mailbox. Use it to find a message, see how SpamFoo handled it, and fix a wrong result. Some of your mail may not appear here. For example, if your mail server doesn't send a message to SpamFoo to be checked, SpamFoo never sees it. ## Open My Messages 1. Open the SpamFoo Dashboard. See [Open Your Dashboard](https://help.spamfoo.com/guides/open-dashboard). 2. In the left menu, go to **My Email > My Messages**. Only messages sent to your own mailbox appear here. That's true for domain admins too. ## Choose a Time Range Use the menu in the top right to pick **Last Hour**, **Last 6 Hours**, **Last 24 Hours**, **Last 7 Days**, or **Last 30 Days**. How far back your messages go also depends on how long your administrator keeps scan history. Older messages may not appear even with a longer time range. ## Filter Messages The filters narrow the list to the messages you care about. 1. Set one or more filters. 2. Click **Search**. You can also press Enter in a text box. 3. To remove all filters, click **Clear**. | Filter | What It Does | | --- | --- | | **Detection Type** | Shows only **Not Spam**, **Spam**, **Phishing**, or **Classification only** messages. **All** shows everything. | | **Detection Path** | Shows only messages decided a certain way, such as by sender reputation, by a rule, or by what SpamFoo learned from you. See [Common Reasons](https://help.spamfoo.com/guides/why-spam#common-reasons). | | **Verified** | Shows only messages with one sender check result. See [The Verified Column](https://help.spamfoo.com/guides/my-messages#verified-column). | | **Sender Domain** | Shows only mail from a domain, such as `example.com`. | | **Sender IP** | Shows only mail from one IP address. An IP address is the number that identifies the server that sent the message. | **Classification only** means SpamFoo only sorted the message into an Email Classification. It didn't check that message for spam. This can happen, for example, when your mailbox was recently set up with SpamFoo and SpamFoo sorted the mail already in your Inbox. ## Read the List The heading above the list shows how many messages match, with a colored bar and the percent that were not spam, spam, phishing, or classification only. Each row shows: - **Date:** when SpamFoo checked the message. Newest messages come first. - **Sender:** the sender's domain, with the sender's IP address under it. - **Verified:** whether the sender passed email sign-in checks. This column only shows in wide windows. - **Detection Type:** the result. Extra tags can appear next to it: - **Override** means a spam rule, yours or an administrator's, decided the result. - A reason tag, such as **Reputation block** or **Learned (spam)**, means something other than SpamFoo's usual models decided. See [See Why a Message Was Spam](https://help.spamfoo.com/guides/why-spam). - **Classification:** the Email Classification. Spam and phishing show a dash instead. ## The Verified Column The **Verified** column tells you whether the sender is really who the From address says. SpamFoo works this out from email sign-in checks called SPF, DKIM, and DMARC. These checks let a domain prove which servers are allowed to send its mail. | State | What It Means | | --- | --- | | **Verified** | The sender of this email is valid for the claimed From address. | | **Spoofed** | This message may not be from the domain it says it's from. The sign-in checks failed. | | **Blocked** | The sender is untrusted because it sends a lot of spam. | | **Unknown** | The sender couldn't be verified. This also covers messages that a rule or your past corrections decided before the checks ran. | ## See a Message's Details Click a message to open the **Message Detail** panel. From top to bottom it shows: - **The result** in large type, with any reason tags. - **Basic facts:** **Timestamp**, **Latency** (how long SpamFoo took to check it), **Sender**, **IP**, **Recipient**, and **Classification**. - **Sender Verification:** the Verified state, what it means, and why this message got it. - **Create Rule** and **Report Misclassification** buttons. See [Take Action on a Message](https://help.spamfoo.com/guides/my-messages#take-action). - **Message Content:** the subject and body, if your server keeps them. If it doesn't, you'll see **Message content was not retained**. - **Processing Steps:** each step SpamFoo went through to reach the result, with **Pass** or **Fail** where it applies. To learn how to read these sections, see [See Why a Message Was Spam](https://help.spamfoo.com/guides/why-spam). ## Take Action on a Message Right-click a message in the list (or press and hold on a touch screen) to see these options. Some also appear as buttons in the **Message Detail** panel. - **Open details:** opens the **Message Detail** panel. - **Create rule:** starts a spam rule for the sender's domain. The rule is added to your own rules. See [Manage Your Rules](https://help.spamfoo.com/guides/my-rules). - **Create rule for IP:** starts a spam rule for the IP address that sent the message. - **Report Misclassification:** tells SpamFoo what the message should have been. See [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result). After you report a message, this changes to **Reported to SpamFoo**. In the rule window, check the sender, choose **Spam** or **Not Spam** under **Action**, and click **Create**. The **Create Rule** button in the details panel picks **Not Spam** for you when the message was spam or phishing. > **Note** Rules and actions in the dashboard don't move the message in your mailbox. Move it in your mail app if you want it somewhere else. *** Source: https://help.spamfoo.com/guides/correct-result Last updated: 2026-10-08 # Correct a Wrong Result When SpamFoo misses a spam message, marks a message you wanted as spam, or puts a message in the wrong Email Classification, tell it. Your correction helps SpamFoo handle mail like it better next time. ## Ways to Correct SpamFoo - **In your mail app:** If your mail runs on SmarterMail, marking a message as spam or not spam, or moving it to a different classification, in webmail sends the correction to SpamFoo for you. See [What SmarterMail Users See](https://help.spamfoo.com/integrations/smartermail/users). - **In the SpamFoo Dashboard:** Report the message from **My Messages** or the **Recent Messages** card on your Overview. The steps are below. ## Report a Message in the Dashboard 1. Open the SpamFoo Dashboard and go to **My Email > My Messages**. 2. Find the message. The **Classification** column shows where SpamFoo put it. Filters can help. See [Filter Messages](https://help.spamfoo.com/guides/my-messages#filter-messages). 3. Right-click the message and choose **Report Misclassification**. You can also click the message and then click **Report Misclassification** in the details panel. 4. In the **Report to SpamFoo** window, under **Report this message as**, choose the right result. See [Choose the Right Result](https://help.spamfoo.com/guides/correct-result#which-result). 5. Click **Report**. The window shows the message's **Current Classification**, such as **Spam**, **Phishing**, **Not Spam**, or a classification. That choice is marked **(current)** and can't be picked. ### Choose the Right Result - **Spam that got through:** Choose **Spam**. - **Phishing that got through:** Choose **Phishing** for a fake message that tries to trick you into giving up a password, money, or personal details. SpamFoo treats it as spam for your mailbox and tells SpamFoo it was phishing, which helps improve the phishing checks. - **A message wrongly marked as spam or phishing:** Choose **Not Spam**, or choose the classification it belongs in. Either one tells SpamFoo the message isn't spam. - **A message in the wrong classification:** Choose **Primary**, **Transactions**, **Updates**, or **Promotions**. See [The Four Email Classifications](https://help.spamfoo.com/guides/how-classification-works#classifications). ## What Happens Next When you see **Message reported to SpamFoo**, SpamFoo has done two things: - **It learns your choice for your mailbox.** SpamFoo saves the correction on your mail server. Similar mail from the same sender follows your choice from then on, and very similar mail from other senders can too. Different kinds of mail from that sender are still handled on their own. If a sender's receipts and newsletters both land in the wrong place, correct one of each. - **It sends the correction to SpamFoo.** SpamFoo uses corrections from many servers to improve its models for everyone. If your administrator turned on content sharing, a sanitized copy of the message text is sent too. See [Sharing Message Content](https://help.spamfoo.com/guides/corrections#sharing-content). > **Tip** If you always want a sender's mail marked as **Spam** or **Not Spam**, add a spam rule for that sender. A rule decides every time, while a correction only teaches SpamFoo. See [Add a Spam Rule](https://help.spamfoo.com/guides/my-rules#add-spam-rule). Some things don't change: - The message stays where it is in your mailbox. Move it in your mail app if you want to. - The message keeps its original result in **My Messages**. The menu now shows **Reported to SpamFoo** instead of **Report Misclassification**. To change your answer, click **Reported to SpamFoo** and report a different result. To remove what SpamFoo learned, see [Undo a Correction](https://help.spamfoo.com/guides/correct-result#undo). ## Other Results You May See | Message | What It Means | | --- | --- | | **No report was sent because the SpamFoo model already classifies this message that way** | SpamFoo's models already agree with you, so a report would teach them nothing. Something else decided this message. The message often starts with what that was, such as a spam rule or the sender's reputation, and what to do about it. | | **Your correction was saved for this mailbox** | Appears with the message above. No report was sent, but SpamFoo still learned your choice for your mailbox. | | **No report was sent because the message is already classified that way** | The message already has the result you picked. | | **This correction has already been reported** | You already reported this message with the same result. | | **Too many reports from this mailbox in the last hour** | Each mailbox can send a limited number of reports per hour. Wait and try again later. | ## Always Allow or Always Block a Sender A correction teaches SpamFoo, but it doesn't promise the same result every time. To always treat a sender one way, create a spam rule. See [Add a Spam Rule](https://help.spamfoo.com/guides/my-rules#add-spam-rule). Spam rules only decide spam or not spam. No rule always puts a sender in one classification. ## Review What SpamFoo Learned Go to **My Email > My Rules**. What SpamFoo learned from your corrections is listed under **Trained Rules**. Each row shows: - **Applies To Emails From:** the sender, or a description such as **Emails that look like one you sorted**. - **Result:** **Spam** or **Not Spam**, or a classification such as **Promotions**. - **Training Method:** **Manual** or **Automatic**. See [Trained Rules](https://help.spamfoo.com/guides/my-rules#trained-rules). - **Times used:** how many times the rule has matched since it was created. ## Undo a Correction 1. Go to **My Email > My Rules**. 2. Under **Trained Rules**, find the row. 3. Right-click the row and choose **Delete**, or click the row and then click **Delete**. 4. Click **Delete** to confirm. SpamFoo stops using what it learned for that mail in your mailbox. This doesn't take back a report that was already sent to SpamFoo. ## Troubleshooting ### Report Misclassification Is Missing **Cause:** You already reported this message. **Fix:** Look for **Reported to SpamFoo** instead. Click it to report a different result. If it's grayed out, SpamFoo no longer has the details it needs to report this message again. **Cause:** SpamFoo no longer has the details it needs to report this message, or your server isn't set up to send reports. **Fix:** Correct the message in your mail app instead, or create a spam rule for the sender. If no message offers the option, ask your administrator. ### New Messages Are Still Wrong After a Correction **Cause:** A spam rule from you or an administrator decided whether those messages were spam. Rules come before what SpamFoo learns from you. **Fix:** Check **My Rules** for a rule that matches the sender. **Set By** shows whether the rule is yours or an administrator's. If an administrator's rule matches, ask your administrator to change it. **Cause:** The new messages are too different from the one you corrected. **Fix:** Correct a few more of them, or create a spam rule for the sender. *** Source: https://help.spamfoo.com/guides/why-spam Last updated: 2026-10-20 # See Why a Message Was Spam Every message in the SpamFoo Dashboard records how SpamFoo reached its result. This page shows where to find that reason, what the common reasons mean, and what to do when the result is wrong. ## Find the Reason 1. Open the SpamFoo Dashboard and go to **My Email > My Messages**. 2. Set **Detection Type** to **Spam** or **Phishing**, then click **Search**. 3. Click the message to open the **Message Detail** panel. Look at these parts of the panel, in this order: - **Tags next to the result.** A reason tag, such as **Learned (spam)** or **Look-alike domain**, names what decided the message. An **Override** tag means a spam rule decided it. No reason tag usually means SpamFoo's models decided. - **Sender Verification.** Shows whether the sender passed email sign-in checks and why. For example, **Blocked by global reputation** or **DMARC failed**. - **Processing Steps.** The steps SpamFoo took, in order. When a rule decided the message, this names the rule as **User Rule**, **Domain Rule**, or **System Rule**. ## Common Reasons Most reasons appear as a tag next to the result, both in the list and in the details panel. The **Detection Path** filter on **My Messages** groups them, so you can find every message with the same reason. | Reason | Detection Path Filter | What It Means | | --- | --- | --- | | No reason tag | **ML model** | SpamFoo's models read the message and judged it to be spam. This is the most common reason. Your past corrections can also nudge the models toward spam or not spam. | | **Learned (spam)** | **Learned** | The message looks like mail you marked as spam before. If **Processing Steps** says **Learned (other sender)**, it matched a correction you made for a different sender. | | **User rule (spam)** | **User rules** | One of your **Trained Rules** matched. SpamFoo created that rule from your corrections. | | **Admin block** with an **Override** tag | **Admin override** | A spam rule set to **Spam** matched the sender. It may be your own rule, your domain's rule, or a system rule. **Processing Steps** says which one. | | **Reputation block** | **Reputation** | The sender has a poor reputation because it sends a lot of spam. The **Verified** state is **Blocked**. In the details panel, this reason shows in **Sender Verification** instead of as a tag. | | **PhishTank match** | **Phishing / impersonation** | The message links to a known phishing site. | | **Impersonation** | **Phishing / impersonation** | The sender's display name matches a person your administrator protects, but the message didn't come from one of that person's approved senders. A fingerprint icon also appears. See [Protect Against Phishing](https://help.spamfoo.com/guides/phishing-protection). | | **Look-alike domain** or **Typosquat domain** | **Phishing / impersonation** | The sender's domain is spelled to look like a real one, for example with one letter changed. | | **Phishing Model** | None | The Anti-Phishing model scored the message as phishing. | | No reason tag | **Cached** | SpamFoo saw an identical message recently and reused that result. **Processing Steps** shows **Cache Hit**. | | **Parse error** | **Parse error** | SpamFoo couldn't read the message properly. Badly formed messages are often spam. | ## What Sender Verification Tells You Sender Verification is based on SPF, DKIM, and DMARC. These are checks that let a domain prove which servers may send its mail. - **Spoofed** is a strong warning sign. The message claims to be from a domain, but that domain's own checks say it isn't. - **Blocked** means SpamFoo decided by the sender's reputation before running the checks. - **Unknown** doesn't mean the message is bad. Many senders don't set up these checks, and messages decided by a rule or your corrections skip them. ## If the Result Is Wrong Pick the fix that matches the reason: | Reason | What to Do | | --- | --- | | ML model, Learned, or Cached | Report the message as **Not Spam**, or as the classification it belongs in. See [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result). | | User rule (spam) | Delete the rule under **Trained Rules** on **My Rules**. | | Admin block from your own rule | Turn off, change, or delete the rule in the **Spam Rules** list on **My Rules**. Your own rules show **You** under **Set By**. | | Admin block from a domain or system rule | Ask your administrator. You can't change their rules, and they win over yours. | | Reputation block or a phishing reason | If you trust the sender, add a spam rule set to **Not Spam** for them. See [Add a Spam Rule](https://help.spamfoo.com/guides/my-rules#add-spam-rule). Be careful with phishing results. Only allow a sender you're sure is real. | The quickest way to add that rule is the **Create Rule** button in the **Message Detail** panel. For a spam or phishing message, it fills in the sender's domain and picks **Not Spam** for you. > **Warning** Do not allow a sender just because a message looks familiar. Phishing messages copy the look of real companies and people. *** Source: https://help.spamfoo.com/guides/my-rules Last updated: 2026-10-08 # Manage Your Rules The **My Rules** page holds the rules that apply to your mailbox. Some you create yourself. Others SpamFoo creates from your corrections, and some come from your administrators. ## Open My Rules 1. Open the SpamFoo Dashboard. See [Open Your Dashboard](https://help.spamfoo.com/guides/open-dashboard). 2. In the left menu, go to **My Email > My Rules**. The page is titled **Spam Rules** and has two lists: - **Spam Rules:** senders that are always marked as **Spam** or **Not Spam**. The **Set By** column shows who made each rule: **You**, **Domain** (your domain administrator), or **System** (your system administrator). You can only change rules set by **You**. - **Trained Rules:** what SpamFoo learned from your corrections, for both spam and Email Classifications. You can delete them. Rules set by **Domain** or **System** show **Yes** or **No** under **Enabled**. You can't open, change, or delete them. ## Your Spam Rules A spam rule tells SpamFoo what to do with mail from a sender, every time. Use a **Not Spam** rule for a sender you always want to hear from, and a **Spam** rule for one you never want to hear from. - A rule can match one email address, such as `news@example.com`, or a whole domain, such as `example.com`. - Use `*` as a wildcard. For example, `*@example.com` matches every address at that domain, and `*.example.com` matches the domain and all its subdomains. - A rule decides the result for that sender before SpamFoo checks the message, so it also skips phishing checks. Be careful with **Not Spam** rules. ### Add a Spam Rule 1. In the **Spam Rules** list, click **Add Rule**. 2. In **Email or Domain**, enter the sender's address or domain. 3. To cover subdomains too, turn on **Include subdomains**. 4. Under **Action**, choose **Spam** or **Not Spam**. 5. Optionally, add a **Note** to remind yourself why you made the rule. 6. Click **Create**. You can also create a rule from a message, either for its sender or for the IP address that sent it. See [Take Action on a Message](https://help.spamfoo.com/guides/my-messages#take-action). ### Change, Turn Off, or Delete a Spam Rule You can only change your own rules, the ones with **You** under **Set By**. - **Change:** Click the rule, make your changes in **Edit Spam Rule**, and click **Save**. - **Turn off:** Use the switch in the **Enabled** column. The rule stays on the list but stops matching until you turn it back on. - **Delete:** Right-click the rule and choose **Delete**, or click the rule and click **Delete**. Then confirm. ## When Rules Disagree - **An administrator's rule wins over yours.** A system administrator's rule wins over a domain administrator's, which wins over yours. - **Not Spam wins over Spam** when two rules from the same level match. - **A more exact rule wins over a broader one** at the same level. A rule for one email address wins over a rule for a domain, which wins over a wildcard, which wins over an IP address range. ## How System Spam Rules Fit In System Spam Rules cover everyone on the server, so SpamFoo applies them after your own corrections have had a say. - A System Spam Rule that says **Spam** always applies. Your corrections can't undo a server-wide block. - A System Spam Rule that says **Not Spam** gives way when your past corrections for that same sender decided the message. - A System Spam Rule also replaces the result of the phishing checks. A Not Spam rule delivers the sender's mail even if a phishing check flagged it. For how rules and learning fit together, see [How Personalization Works](https://help.spamfoo.com/guides/personalization). ## Trained Rules SpamFoo fills in the **Trained Rules** list as you correct messages. See [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result). Each row shows: - **Applies To Emails From:** the sender, or a description of the mail the rule covers. Examples are **A mailing list**, **Replies in one email conversation**, and **Emails that look like one you sorted**. - **Result:** **Spam** or **Not Spam** for spam rules, or a classification such as **Promotions** for classification rules. - **Training Method:** **Manual** means it came mostly from corrections you made. **Automatic** means SpamFoo created it on its own. - **Times used:** how many times the rule has matched since it was created. A dash means it hasn't matched yet. To see a trained rule's details, click it. To remove one, right-click it and choose **Delete**, or click it and click **Delete**. Then confirm. SpamFoo stops using what it learned for that mail. You can't add trained rules by hand. To always treat a sender one way, add a rule to the **Spam Rules** list. ## Start Over The **Danger Zone** at the bottom of the page clears everything SpamFoo learned about your mailbox. 1. Click **Clear All Personalization Data**. 2. Click **Clear All Data** to confirm. > **Warning** This permanently removes everything in your **Trained Rules** list. It cannot be undone. SpamFoo starts learning your preferences from scratch. Your own Spam Rules and your message history stay. *** Source: https://help.spamfoo.com/guides/manage-domain Last updated: 2026-10-20 # Manage a Domain If you're a domain admin, the SpamFoo Dashboard has a **Domain Admin** menu for your domain. Use it to watch your domain's mail, set spam rules for everyone in the domain, and protect your people from impersonation. ## Before You Start - Open the SpamFoo Dashboard. See [Open Your Dashboard](https://help.spamfoo.com/guides/open-dashboard). The top right corner shows **Domain Admin** with your domain name under it. - The **Domain Admin** menu covers only your own domain. - Your own mailbox is still under **My Email**. See [Read Your Overview](https://help.spamfoo.com/guides/overview). - You can't read other people's messages. You can see how much mail each mailbox gets, but not the messages themselves. ## Domain Overview Go to **Domain Admin > Domain Overview**. The page title is your domain name. It sums up mail for every mailbox in the domain. ![Domain Overview for example.com showing six tiles: Total Scanned, Spam Rate, Busiest Day, Not Spam, Spam, and Phishing, with the Last 30 Days menu in the top right.](https://help.spamfoo.com/images/domain-overview-top.png) - Use the menu in the top right to pick **Last 7 Days**, **Last 30 Days**, or **Last 90 Days**. - The tiles show **Total Scanned**, **Caught Rate**, **Busiest Day**, **Not Spam**, **Spam**, and **Phishing** for the whole domain. - **Mail Trend** charts Not Spam, Spam, and Phishing for each day. - **Classifications** shows how the domain's non-spam mail was sorted into **Primary**, **Updates**, **Transactions**, and **Promotions**, plus any that was **Not classified**. These work the same way as on your own Overview page. See [Read Your Overview](https://help.spamfoo.com/guides/overview) for what each one means. ## Mailboxes Go to **Domain Admin > Mailboxes**. The list shows each mailbox in your domain that got mail in the time range you pick in the top right. | Column | What It Shows | | --- | --- | | **Mailbox** | The mailbox's email address. | | **Scanned** | How many messages SpamFoo checked for that mailbox. The busiest mailboxes come first. | | **Spam** | How many were spam. | | **Phishing** | How many were phishing. | | **Caught Rate** | The share of that mailbox's mail that was spam or phishing, with a bar showing spam in red and phishing in gold. | Use the search box to find a mailbox by address. Click a column heading to sort by it. A mailbox with a high caught rate may be a good reason to add a domain rule, or to tell that person how to mark spam. See [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result). ## Domain Spam Rules Go to **Domain Admin > Rules**. A domain spam rule always marks mail from a sender as **Spam** or **Not Spam** for everyone in your domain. - A rule can match an email address, a domain, or an IP address or range. An IP range uses CIDR notation, which is an address followed by a slash and the size of the network, such as `10.0.0.0/24`. - Use `*` as a wildcard, such as `*@example.com` or `*.example.com`. - Your domain rules win over any rule your users create for the same sender. - Rules your system administrator sets for the whole server win over yours. They appear in the same list with **System** under **Set By**, and you can't change them. - Your users see your rules on their **My Rules** page with **Domain** under **Set By**, but they can't change them. ### Add a Domain Spam Rule 1. In the **Spam Rules** list, click **Add Rule**. 2. In **Email, Domain, or IP Range**, enter the sender. 3. To cover subdomains too, turn on **Include subdomains**. 4. Under **Action**, choose **Spam** or **Not Spam**. 5. Optionally, add a **Note**. 6. Click **Create**. You can only change rules with **Domain** under **Set By**. To change a rule, click it, edit it, and click **Save**. To pause a rule, use its **Enabled** switch. To remove one, right-click it and choose **Delete**. The **Match By** column shows whether each rule matches an **Email**, a **Domain**, a **Wildcard**, or a **CIDR** (IP range). > **Warning** A **Not Spam** rule lets the sender's mail skip spam and phishing checks for your whole domain. Do not add one for a sender you don't fully trust. For a deeper look at rules for administrators, see [Manage Spam Rules and Senders](https://help.spamfoo.com/guides/rules-protection). ## Anti-Phishing Go to **Domain Admin > Anti-Phishing**. The **Protected identities** tab lists the people attackers are likely to pretend to be, such as your owner, your finance staff, or your HR staff. If a message shows one of these names but doesn't come from an approved sender, SpamFoo treats it as phishing. Protected identities need a SpamFoo Anti-Phishing license. Without it, you can see your identities but not change them, and they aren't checked. - **Add Identity** adds one person. Enter the **Display Name** as it appears in their email and the senders allowed to use it. If the person also sends from another address, such as a personal Gmail account, add that address too. - **Import vCards** adds many people at once from contact cards (`.vcf` files, or a `.zip` of them). - **Strict Mode** controls what counts as an approved sender. When it's on, only exact email addresses count. When it's off, you can also approve whole domains. - **Delegated Senders for Every Identity** approves a sender for all of your protected identities at once. Use it for your own domain, a shared assistant, or a service that puts real names in its notifications. For step-by-step help, see [Protect Against Phishing](https://help.spamfoo.com/guides/phishing-protection). *** Source: https://help.spamfoo.com/guides/system-requirements Last updated: 2026-10-27 # System Requirements Check that your server can run SpamFoo Client before you install it. This page lists the hardware, operating systems, and network access SpamFoo Client needs. > **Using SmarterMail?** SmarterMail downloads and runs SpamFoo Client for you, but the server still needs the hardware and network access on this page. ## Hardware | Resource | Minimum | Recommended | | --- | --- | --- | | Processor | 2 cores, x64 | 4 or more cores, x64 | | Memory | 1 GB free | 2 GB free | | Disk space | 2 GB free | 5 GB free | | Graphics card (GPU) | (Optional) SpamFoo Client runs on the processor by default. Servers with an NVIDIA GPU can [turn on GPU acceleration](https://help.spamfoo.com/guides/gpu-acceleration). | | > **Processor** SpamFoo Client requires an x64 (AMD64) processor. ARM processors are not supported. ### Disk Space Disk use grows over time. These things take up the most space: - **Scan history:** one record per message, kept for the Retention Period (7 days by default). Daily totals are kept for 90 days. - **Stored messages:** the subject and body of scanned messages, up to the Maximum Stored Messages limit. - **Nightly backups:** copies of the databases, 7 nights by default, on the same disk. - **Log files:** deleted after the Retention Period. Detailed logging and debug codes write much more. - **GPU acceleration:** when turned on, SpamFoo Client downloads a larger build of itself that includes NVIDIA's libraries. The **Storage** card on **System > Status** shows the database size and where each folder is. To use less space, change these on **System > Settings**: - Lower [Retention Period](https://help.spamfoo.com/api/settings#retention-period) in the **Scan History** card. - Lower [Maximum Stored Messages](https://help.spamfoo.com/api/settings#maximum-stored-messages), or turn off [Store Message Content](https://help.spamfoo.com/api/settings#store-message-content), in the **Message Content** card. Without stored content, you can't read messages on the **Messages** page. - Lower [Backups to Keep](https://help.spamfoo.com/api/settings#backups-to-keep) in the **Database Backups** card. - In the **Logging** card, set [Log Level](https://help.spamfoo.com/guides/monitoring#log-level) to **Normal** and clear **Debug Log Codes**. The database file shrinks only when SpamFoo Client compacts it, which happens each time it starts and once a week. Restart it to free the space right away. ## Supported Operating Systems | Platform | Supported Versions | | --- | --- | | Windows Server | 2016, 2019, 2022, 2025 | | Windows | 10, 11 | | Ubuntu | 22.04 and later LTS versions | | Debian | 12 and later | | Red Hat Enterprise Linux | 8 and later | | CentOS Stream | 9 and later | | AlmaLinux | 8.10 and later | | Rocky Linux | 8.10 and later | | Fedora | 43 and later | | Docker | Linux x64 hosts (see [Docker](https://help.spamfoo.com/guides/system-requirements#docker)) | Only versions that still get updates from their maker are supported. ## Software Prerequisites SpamFoo Client includes most of what it needs to run, including the .NET runtime. - **Windows:** Nothing else is needed. - **Linux archive:** The server needs the ICU library (`libicu`), which SpamFoo Client uses to read dates and text in different languages. Many servers already have it. On Red Hat, CentOS Stream, AlmaLinux, Rocky Linux, and Fedora, run `sudo dnf install libicu`. On Ubuntu and Debian, run `sudo apt install libicu-dev`. - **Docker:** The image already includes everything. - **GPU acceleration:** Also needs the NVIDIA driver. ## Network Requirements ### Ports SpamFoo Client listens on **port 16253** (HTTP). Your mail server sends messages to this port for scanning, and you open the admin console on it. In standalone mode you can choose another port with [`SPAMFOO_PORT`](https://help.spamfoo.com/guides/configure#port). Who can connect depends on how you run SpamFoo Client: - **Installed on the server:** Only programs on the same server can connect. If your mail server runs on another computer, set the `SPAMFOO_BIND_ADDRESS` environment variable. See [Configure SpamFoo Client](https://help.spamfoo.com/guides/configure#env-network). - **Docker:** Other computers and containers can connect once you publish the port, such as with `-p 16253:16253`. The Docker example in [Install SpamFoo Client](https://help.spamfoo.com/guides/install#docker) publishes it only on the host's `127.0.0.1` address. > **Warning** Do not open port 16253 to the internet. Allow only your mail servers to reach it. ### Outbound Access SpamFoo Client needs outbound HTTPS (port 443) access to these addresses: | Address | Used For | | --- | --- | | `global-api.spamfoo.com` | Update checks, reputation data, and corrections | | `downloads.spamfoo.com` | Model, program, and phishing data downloads | | `developer.download.nvidia.com` | NVIDIA libraries, only when GPU acceleration is turned on | All message scanning happens on your server. - The first start needs this access to check the license and download the models. - After that, if SpamFoo Client loses access, it keeps scanning mail with the models it already has. - While access is down, models, phishing data, and reputation data are not updated. ## Docker SpamFoo Client is also available as a Docker image, `spamfoo/spamfoo-client`. The image includes everything SpamFoo Client needs, including the admin console and the `spamfoo-client` commands. Models download the first time it starts with a license key. | Requirement | Details | | --- | --- | | Host | Linux on x64 | | Port | 16253 | | Storage | A volume mounted at `/data`, so settings, models, and history survive when the container is replaced | GPU acceleration is not available in the Docker image. ## Mail Server Compatibility | Mail Server | How It Connects | Details | | --- | --- | --- | | SmarterMail | Built in | [Set Up SmarterMail](https://help.spamfoo.com/integrations/smartermail/setup) | | Other mail servers | Local HTTP API | [How Integration Works](https://help.spamfoo.com/integrations) | ## Next Steps When your server meets these requirements, go to [Install SpamFoo Client](https://help.spamfoo.com/guides/install). *** Source: https://help.spamfoo.com/guides/install Last updated: 2026-10-27 # Install SpamFoo Client This page shows how to install SpamFoo Client on your mail server or next to it, add your license key, sign in to the dashboard, and check that it works. > **Note:** If you use SmarterMail, you can skip this page. SmarterMail downloads, sets up, and updates SpamFoo Client for you. See [Set Up SmarterMail](https://help.spamfoo.com/integrations/smartermail/setup). Use this page only if you want SpamFoo Client on its own server. See [Use SpamFoo Client on Another Server](https://help.spamfoo.com/integrations/smartermail/setup#external-client). ## Before You Start - Check that your server meets the [System Requirements](https://help.spamfoo.com/guides/system-requirements). - Make sure you can sign in to the server as an administrator (Windows) or as root (Linux). - Get your license key. [Sign in to your SpamFoo account](https://www.spamfoo.com/account/login), go to **Licenses**, and open your license. Copy the key and save it in a file on the server, such as `/root/spamfoo-key.txt`, or click **Download sf.key**. - The [Downloads](https://www.spamfoo.com/account/downloads) page in your SpamFoo account has the install commands and the current files for each kind of install. Get every SpamFoo Client download there. ## Choose How to Install - **[Docker](https://help.spamfoo.com/guides/install#docker):** runs on any Linux host with Docker. Everything SpamFoo Client keeps is stored in one volume. - **[Archive](https://help.spamfoo.com/guides/install#tarball):** a zip file for Windows, or a tarball for Linux systems the package doesn't support. You set up the service yourself. The Linux package, the Docker steps, and the Linux archive steps on this page run SpamFoo Client in **standalone mode**, the mode for a SpamFoo Client that runs on its own instead of being started by SmarterMail. For the difference, see [Mode](https://help.spamfoo.com/guides/configure#mode) in Configure SpamFoo Client. ## Install With Docker The [Downloads](https://www.spamfoo.com/account/downloads) page in your SpamFoo account shows these steps and the current version. The `spamfoo/spamfoo-client` image runs on Linux hosts with x86_64 processors. Everything SpamFoo Client keeps is stored in one volume mounted at `/data`. ### Start It With Docker Compose 1. In an empty folder, create a file named `spamfoo.env` with your license key, and keep it private: ``` SPAMFOO_LICENSE_KEY=paste-your-license-key-here ``` ``` chmod 600 spamfoo.env ``` 2. Save this as `compose.yml` next to it: ``` name: spamfoo services: spamfoo-client: container_name: spamfoo-client image: spamfoo/spamfoo-client:latest restart: unless-stopped stop_grace_period: 30s env_file: spamfoo.env environment: SPAMFOO_MODE: standalone SPAMFOO_PUBLIC_URL: http://127.0.0.1:16253 ports: - "127.0.0.1:16253:16253" volumes: - spamfoo-data:/data volumes: spamfoo-data: {} ``` 3. Start it: ``` docker compose up -d ``` 4. Make a one-time sign-in link, open it, and click **Continue**: ``` docker compose exec spamfoo-client spamfoo-client login-link ``` Each part of the compose file matters: - `SPAMFOO_MODE: standalone` turns on standalone mode. The image doesn't set it, so a container started without it runs the way older versions did. - `restart: unless-stopped` starts SpamFoo Client again when it stops to recover from some errors. - `stop_grace_period: 30s` gives SpamFoo Client time to finish the messages it's scanning. Docker's default of 10 seconds is too short. - The port is published on `127.0.0.1`, so only the Docker host can connect. - `SPAMFOO_PUBLIC_URL` is the address in sign-in links. Change it if you open the dashboard at another address. To add the license key later instead of in `spamfoo.env`, save it in a file and run: ``` docker compose exec -T spamfoo-client spamfoo-client activate --stdin < spamfoo-key.txt ``` It takes effect without a restart. ### Start It With docker run Use the same `spamfoo.env` file without Compose: ``` docker run -d --name spamfoo-client --restart unless-stopped --stop-timeout 30 \ -e SPAMFOO_MODE=standalone --env-file spamfoo.env \ -p 127.0.0.1:16253:16253 -v spamfoo-data:/data \ spamfoo/spamfoo-client:latest ``` Then make a sign-in link with `docker exec spamfoo-client spamfoo-client login-link --url=http://127.0.0.1:16253`. ### Set the Proxy Secret The proxy secret is the password your mail server sends with every request. If you don't set one, SpamFoo Client creates a random one in `/data/etc/sf.proxysecret` the first time it starts. To choose your own, add `SPAMFOO_PROXY_SECRET=your-secret` to `spamfoo.env` before the first start. See [Proxy Secret](https://help.spamfoo.com/guides/configure#proxy-secret). ### Accept Mail From Other Servers To let a mail server on another machine reach the container, publish the port on an address that machine can reach, such as `"16253:16253"` in the compose file or `-p 16253:16253` with `docker run`. Then allow only your mail servers through the host's firewall. > **Warning** Do not open the SpamFoo Client port to the internet. Allow only your mail servers to reach it. ### Use a Folder on the Host The container runs as user ID 10001. If you mount a host folder instead of a named volume, give that user the folder first: ``` sudo mkdir -p /srv/spamfoo sudo chown -R 10001:10001 /srv/spamfoo ``` Then use `/srv/spamfoo:/data` as the volume. ### Pick an Image Tag - `latest` is always the newest release. - A version tag, such as `1.2.3`, stays on that release. - A major or minor tag, such as `1` or `1.2`, moves to the newest release in that line. To update, see [Update Docker](https://help.spamfoo.com/guides/updates#update-docker). ## Install From the Archive Choose your platform. ### Windows #### Extract and Add Your License Key 1. Download `spamfoo-client--windows-x64.zip` from the [Downloads](https://www.spamfoo.com/account/downloads) page in your SpamFoo account. 2. Extract the zip archive to the folder you want to install to, such as `C:\SpamFoo`. 3. Create a folder named `etc` inside the install folder. 4. Copy `sf.key` into the `etc` folder, so its path is `C:\SpamFoo\etc\sf.key`. You can set the `SPAMFOO_LICENSE_KEY` environment variable to your license key instead. If both are set, the environment variable is used. #### Start SpamFoo Client 1. Open PowerShell as an administrator. 2. Start SpamFoo Client: ``` cd C:\SpamFoo .\spamfoo-client.exe ``` SpamFoo Client starts and listens at `http://localhost:16253`. It stops when you close the window. > **Note** When SpamFoo Client installs a program update, it stops and does not start itself again. Start it again after an update. See [Update and Back Up](https://help.spamfoo.com/guides/updates#client-updates). ### Linux Use the tarball on Linux systems the package doesn't support. Download `spamfoo-client--linux-x64.tar.gz` from the [Downloads](https://www.spamfoo.com/account/downloads) page in your SpamFoo account, and check that its SHA-256 hash matches the one on that page: ``` sha256sum spamfoo-client--linux-x64.tar.gz ``` #### Unpack the Files Create the service user and folders, then unpack the files: ``` sudo useradd --system --home-dir /var/lib/spamfoo --shell /usr/sbin/nologin spamfoo sudo mkdir -p /opt/spamfoo /var/lib/spamfoo /etc/spamfoo sudo tar -xzf spamfoo-client--linux-x64.tar.gz -C /opt/spamfoo sudo chown -R spamfoo:spamfoo /opt/spamfoo /var/lib/spamfoo sudo chmod 0750 /var/lib/spamfoo ``` The `spamfoo` user must own `/opt/spamfoo`, because SpamFoo Client updates its own program files there. #### Create the Settings File Create `/etc/spamfoo/spamfoo.conf` with these settings. Add any other settings from [Configure SpamFoo Client](https://help.spamfoo.com/guides/configure) the same way. ``` SPAMFOO_MODE=standalone SPAMFOO_BIND_ADDRESS=127.0.0.1 ``` Then let the service user read it: ``` sudo chown root:spamfoo /etc/spamfoo/spamfoo.conf sudo chmod 0640 /etc/spamfoo/spamfoo.conf ``` #### Add Your License Key ``` sudo /opt/spamfoo/spamfoo-client activate --data-dir=/var/lib/spamfoo --file=/root/spamfoo-key.txt ``` #### Run as a systemd Service 1. Create the file `/etc/systemd/system/spamfoo-client.service` with this content: ``` [Unit] Description=SpamFoo Client After=network-online.target Wants=network-online.target [Service] Type=simple User=spamfoo Group=spamfoo EnvironmentFile=-/etc/spamfoo/spamfoo.conf ExecStartPre=/bin/sh -c 'while [ -e /opt/spamfoo/updates/update.lock ]; do sleep 1; done' ExecStart=/opt/spamfoo/spamfoo-client run --mode=standalone --data-dir=/var/lib/spamfoo WorkingDirectory=/var/lib/spamfoo Restart=always RestartSec=5s RestartPreventExitStatus=78 TimeoutStopSec=30 KillMode=process [Install] WantedBy=multi-user.target ``` 2. Turn on and start the service: ``` sudo systemctl daemon-reload sudo systemctl enable --now spamfoo-client ``` 3. Make a one-time sign-in link, open it, and click **Continue**: ``` sudo /opt/spamfoo/spamfoo-client login-link --data-dir=/var/lib/spamfoo ``` Some lines in the service file matter: - `Restart=always` starts SpamFoo Client again after it stops to install an update. - `KillMode=process` lets the update finish after SpamFoo Client stops. - `ExecStartPre` waits until an update has finished replacing the program files. - `RestartPreventExitStatus=78` stops systemd from restarting SpamFoo Client over and over when a setting is wrong. See [It Stops With a Settings Error](https://help.spamfoo.com/guides/common-problems#config-error). ## Check the Install SpamFoo Client downloads its models the first time it starts with a license key. This can take a few minutes. 1. On the server, check that SpamFoo Client is ready to classify mail: ``` curl http://localhost:16253/health/ready ``` The answer is `{"status":"ready","contract":1}` when it's ready. Until then, it lists the reasons, such as `models_loading` or `not_activated` (no license key). See [Check Health](https://help.spamfoo.com/api/health#ready). With the Linux package or Docker, you can run `spamfoo-client status` instead. 2. Sign in to the dashboard with a sign-in link, as shown above, or open `http://localhost:16253/admin/` and paste the proxy secret. 3. Go to **System > Status**. 4. Check that the status line at the top says **API connected**. 5. Check that the **License** card shows your plan with the status **Active**. 6. Check that the **Versions** card lists a version for the client and for each model. The proxy secret is a random password SpamFoo Client creates the first time it starts, unless you set `SPAMFOO_PROXY_SECRET`. Find it here: | Install | Proxy Secret | | --- | --- | | Linux package or Linux archive | `/var/lib/spamfoo/etc/sf.proxysecret` | | Docker | The value you set in `SPAMFOO_PROXY_SECRET`. If you didn't set one, run `docker compose exec spamfoo-client cat /data/etc/sf.proxysecret`. | | Windows | `C:\SpamFoo\etc\sf.proxysecret` | If the dashboard doesn't load, check that SpamFoo Client is running and look at the log files in the `logs` folder of the data directory. See [Fix Common Problems](https://help.spamfoo.com/guides/common-problems) and [Monitor Logs and Health](https://help.spamfoo.com/guides/monitoring). ## Next Steps 1. Review the settings in [Configure SpamFoo Client](https://help.spamfoo.com/guides/configure). 2. Connect your mail server. See [How Integration Works](https://help.spamfoo.com/integrations). 3. Send a test message and find it in the admin console. See [Use the Admin Console](https://help.spamfoo.com/guides/admin-console). *** Source: https://help.spamfoo.com/guides/configure Last updated: 2026-10-27 # Configure SpamFoo Client SpamFoo Client reads some settings from environment variables, command-line options, a settings file, and a few files in its data directory when it starts. This page lists those settings and their defaults. Settings you change on **System > Settings** in the admin console, or with the settings API, are listed in [Read and Change Settings](https://help.spamfoo.com/api/settings#setting-keys). > **Note:** If you use SmarterMail, it does most of this configuration for you, including the license, folders, and updates. See [Set Up SmarterMail](https://help.spamfoo.com/integrations/smartermail/setup). ## How Settings Are Applied - **Environment variables, command-line options, and the settings file:** read when SpamFoo Client starts. Restart SpamFoo Client after you change one. - **The license key file and the proxy secret file:** changes are picked up while SpamFoo Client runs in some cases. See [License Key](https://help.spamfoo.com/guides/configure#license-key) and [Proxy Secret](https://help.spamfoo.com/guides/configure#proxy-secret). - **Settings page and settings API:** changes apply right away. Saving the Settings page saves every value shown on it, not just the one you changed. See [Read and Change Settings](https://help.spamfoo.com/api/settings#change-settings). When the same setting is set in more than one place, SpamFoo Client uses the first one it finds in this order: 1. A value saved on the Settings page or with the settings API 2. An environment variable 3. A line in the [settings file](https://help.spamfoo.com/guides/configure#settings-file) 4. A command-line option, or a file in the `etc` folder of the data directory 5. The default There are two exceptions: - The `--mode`, `--port`, and `--conf` command-line options win over their environment variables and the settings file. - When the `SPAMFOO_GPU` environment variable is set, it overrides the **GPU Acceleration** switch on the Settings page. If a setting has a value SpamFoo Client can't use, such as an unknown mode or a port that isn't a number, SpamFoo Client prints a line that starts with `FATAL` and stops with exit code 78. See [It Stops With a Settings Error](https://help.spamfoo.com/guides/common-problems#config-error). ## Mode SpamFoo Client runs in one of two modes. The mode is set when it starts, with `SPAMFOO_MODE` or [`--mode`](https://help.spamfoo.com/guides/configure#mode-option). - **Embedded** (the default): the mode SmarterMail uses, and the mode of any install that doesn't set one. - **Standalone:** for SpamFoo Client running on its own. The Linux package uses it, and the Docker and Linux archive steps in [Install SpamFoo Client](https://help.spamfoo.com/guides/install) turn it on. In standalone mode, SpamFoo Client works differently in these ways: - **Proxy secret:** every caller needs the proxy secret, even a program on the same server. Health checks are the only exception. In embedded mode, programs on the same server can classify mail without it. - **Failed sign-ins:** after a certain number of wrong proxy secrets or sign-in links from one IP address within a short time, that address is blocked for several minutes. Its requests get HTTP 429. - **Changing the secret:** a new value in `etc/sf.proxysecret` takes effect within a few seconds, with no restart. See [Change the Secret](https://help.spamfoo.com/guides/roles#change-the-secret). - **License key:** SpamFoo Client starts and waits when there's no key. It picks up a key you add later without a restart. - **Largest message:** the default limit is 64 MB instead of 520 MB. See [Largest Message](https://help.spamfoo.com/guides/configure#largest-message). - **Port and public address:** [Port](https://help.spamfoo.com/guides/configure#port) and [Public URL](https://help.spamfoo.com/guides/configure#public-url) only apply in standalone mode. In embedded mode the port is always 16253. - **Message files:** messages waiting to be scanned are written to the `spool` folder in the data directory, instead of the system's temporary folder. See [Spool Folder](https://help.spamfoo.com/guides/configure#spool-folder). - **File permissions:** on Linux and macOS, SpamFoo Client makes the `etc` folder and the database readable only by the account that runs it. - **Logs:** log lines are also written to the console, so they show up in `journalctl` or `docker logs`. - **Address map:** a mail server can send which mailboxes it has and which addresses deliver to each one. See [Push an Address Map](https://help.spamfoo.com/api/address-map). ## Settings File SpamFoo Client can read startup settings from a file named `spamfoo.conf`. The Linux package creates one at `/etc/spamfoo/spamfoo.conf`. SpamFoo Client uses the first file it finds: 1. The file named by `--conf` or `SPAMFOO_CONF`. SpamFoo Client won't start if that file doesn't exist. 2. `spamfoo.conf` in the data directory (`/data/spamfoo.conf` in Docker). 3. On Linux, `/etc/spamfoo/spamfoo.conf`. Each line sets one environment variable from this page: ``` # Lines that start with # are comments. SPAMFOO_MODE=standalone SPAMFOO_BIND_ADDRESS=127.0.0.1 SPAMFOO_MAX_CLASSIFICATION_CONCURRENCY=4 ``` - Only names that start with `SPAMFOO_` are read. Other lines are ignored with a warning. - A line only applies when the same environment variable isn't already set. - Quotes around a value are removed. If a name appears twice, the last line wins. - Restart SpamFoo Client after you change the file. The same file works as a systemd `EnvironmentFile` or a Docker `--env-file`. The `spamfoo-client` commands read it too, so they use the same data directory, port, and public address as the service. ## Command-Line Options Add command-line options after the program name when you start SpamFoo Client. Write each one with an equals sign, such as `--data-dir=/var/lib/spamfoo`. In paths, `~` means your home folder. ``` ./spamfoo-client --data-dir=/var/lib/spamfoo --models-dir=/var/lib/spamfoo/models ``` The program also has commands for tasks such as adding a license key or making a sign-in link. See [Use SpamFoo Client Commands](https://help.spamfoo.com/guides/commands). #### Data Directory `--data-dir=` The directory that holds the database, license key, proxy secret, personalization data, backups, and logs. See [Data Directory](https://help.spamfoo.com/guides/configure#data-directory). Default: the install folder (`/data` in Docker) #### Models Folder `--models-dir=` The folder where downloaded models are stored. You can also set it with the `SPAMFOO_MODELS` environment variable, which wins over the option. It is only used if the folder already exists. Default: `/trained-models` #### Mode `--mode=` `embedded` or `standalone`. See [Mode](https://help.spamfoo.com/guides/configure#mode). You can also set it with the `SPAMFOO_MODE` environment variable. The option wins if both are set. Default: `embedded` #### Port `--port=` The port SpamFoo Client listens on. Standalone mode only. You can also set it with the `SPAMFOO_PORT` environment variable. The option wins if both are set. Default: 16253 #### Settings File `--conf=` The settings file to read. See [Settings File](https://help.spamfoo.com/guides/configure#settings-file). You can also set it with the `SPAMFOO_CONF` environment variable. The option wins if both are set. Default: `spamfoo.conf` in the data directory, then `/etc/spamfoo/spamfoo.conf` on Linux ## Environment Variables Each setting below shows its environment variable. A few of them, such as the scan queue settings, can also be saved on the Settings page or with the settings API, and a saved value wins. ### Folders #### NVIDIA Libraries Folder `SPAMFOO_RUNTIME` The folder for the NVIDIA libraries that GPU acceleration downloads. Default: `/runtime` #### Spool Folder `SPAMFOO_SPOOL_DIR` The folder where messages are written while they wait to be scanned. Standalone mode only. SpamFoo Client deletes leftover files in it when it starts. In embedded mode, the system's temporary folder is used. Default: `/spool` ### License and Access #### License Key `SPAMFOO_LICENSE_KEY` Your license key. You can also save the key in `etc/sf.key` in the data directory, or with [`spamfoo-client activate`](https://help.spamfoo.com/guides/commands#activate). The environment variable wins if both are set. SpamFoo Client watches `etc/sf.key` and uses a changed key within a few seconds, with no restart. In embedded mode, this works only if SpamFoo Client had a key when it started. Otherwise, restart it after you add the key. #### Proxy Secret `SPAMFOO_PROXY_SECRET` The password you paste to sign in to the admin console. Mail servers and scripts send it with every request. If you choose your own, use at least 32 characters from `A-Z a-z 0-9 - _`, with no spaces before or after it. Letter case matters. You can also save it in `etc/sf.proxysecret` in the data directory. The environment variable wins if both are set. In standalone mode, a new value in the file takes effect within a few seconds. See [Change the Secret](https://help.spamfoo.com/guides/roles#change-the-secret). Default: a random value SpamFoo Client creates and saves in `etc/sf.proxysecret` when it first starts ### Network #### Listen Address `SPAMFOO_BIND_ADDRESS` The network address SpamFoo Client listens on. Use `0.0.0.0` to accept connections from other machines. SpamFoo Client serves HTTP only. Default: `localhost` (`0.0.0.0` in Docker, `127.0.0.1` in the Linux package's settings file) #### Port `SPAMFOO_PORT` The port SpamFoo Client listens on, from 1 to 65535. Standalone mode only. In embedded mode the port is always 16253. You can also set it with the [`--port`](https://help.spamfoo.com/guides/configure#port-option) option, which wins. Default: 16253 #### Public URL `SPAMFOO_PUBLIC_URL` The address people use to reach SpamFoo Client, such as `http://spamfoo.example.com:16253`. Sign-in links start with it. It must start with `http://` or `https://`. Standalone mode only. Without it, a sign-in link made by the dashboard or the API uses the address the request came to, and one made by `spamfoo-client login-link` uses `http://localhost` and the port. > **Warning** Do not open the SpamFoo Client port to the internet. Allow only your mail servers to reach it. ### Updates #### Update Check Interval `SPAMFOO_UPDATE_INTERVAL_HOURS` How often SpamFoo Client checks for new models and program updates, in hours. Default: 6 hours #### Automatic Program Updates `SPAMFOO_DISABLE_BINARY_UPDATE` Whether SpamFoo Client replaces its own program files when a new version is available. Set the variable to `true` to turn this off. Model updates still run. Program updates are always off in a Docker container running in standalone mode and with the Linux package, which you upgrade with its install script. Default: `false` (`true` in Docker and with the Linux package) #### Install Kind `SPAMFOO_INSTALL_KIND` How SpamFoo Client was installed: `package`, `container`, `portable` (an archive), or `sidecar`. Standalone mode only. It decides whether program updates run, and it's shown on your license in your SpamFoo account. Set it only if SpamFoo Client guesses wrong. Default: `container` inside a container, `package` when the program is in `/opt/spamfoo/client`, and `portable` otherwise #### SpamFoo API Timeout `SPAMFOO_TIMEOUT` How long SpamFoo Client waits for SpamFoo's servers to answer, in seconds. Default: 30 seconds ### Scan Queue SpamFoo Client scans a limited number of messages at once. Other messages wait in a queue. There are two queues that share the same slots: - **Spool queue:** messages being delivered right now. These get first claim on the slots. - **Background queue:** requests that only sort mail into Email Classifications, without spam checks. See [Classify Without Spam Checks](https://help.spamfoo.com/api/classify-tabs). #### Max Concurrency `SPAMFOO_MAX_CLASSIFICATION_CONCURRENCY` How many messages are scanned at once, shared by both queues. Each message being scanned can keep one processor core fully busy. See [Concurrency](https://help.spamfoo.com/guides/configure#concurrency). Default: 2 (range 1 to 32) #### Queue Timeout `SPAMFOO_CLASSIFICATION_QUEUE_TIMEOUT_SECONDS` How long a message can wait for a slot before SpamFoo Client returns an error (HTTP 503), in seconds. See [Timeouts](https://help.spamfoo.com/guides/configure#timeouts). Default: 30 seconds #### Spool Queue Timeout `SPAMFOO_SPOOL_TIMEOUT_SECONDS` A separate wait limit for the spool queue, in seconds. Once you save the Settings page, the saved **Queue Timeout** replaces `SPAMFOO_SPOOL_TIMEOUT_SECONDS`. To keep a separate limit after that, set it with the [settings API](https://help.spamfoo.com/api/settings#spool-queue-timeout). Default: same as Queue Timeout #### Background Queue Slots `SPAMFOO_BACKGROUND_MAX_CONCURRENCY` The most slots the background queue can use. It can never take every slot. Default: one less than Max Concurrency, at least 1 #### Background Queue Timeout `SPAMFOO_BACKGROUND_TIMEOUT_SECONDS` A separate wait limit for the background queue, in seconds. Once you save the Settings page, the saved **Queue Timeout** replaces `SPAMFOO_BACKGROUND_TIMEOUT_SECONDS`. To keep a separate limit after that, set it with the [settings API](https://help.spamfoo.com/api/settings#background-queue-timeout). Default: 120 seconds, or Queue Timeout once you save the Settings page ### Request Limits These limits protect the server from very large or very many requests. The defaults are set high so that normal mail is never turned away. #### Largest Message `SPAMFOO_MAX_CLASSIFY_BODY_BYTES` The largest message SpamFoo Client accepts, in bytes. Larger messages get an error (HTTP 413). To protect memory from very large messages, lower it, for example to `33554432` (32 MB). Default: `545259520` (520 MB), or `67108864` (64 MB) in standalone mode #### Messages Held at Once `SPAMFOO_MAX_CLASSIFY_INFLIGHT` The most messages SpamFoo Client holds at once, scanning or waiting. Extra requests get an error (HTTP 503). Default: 1024 #### Intake Wait `SPAMFOO_CLASSIFY_INTAKE_TIMEOUT_MS` How long a request waits for room under **Messages Held at Once**, in milliseconds. Default: 250 milliseconds ### Model Processing #### Model Threads `SPAMFOO_ONNX_THREADS` How many processor threads the models share. Default: about half the processor cores (1 on servers with 2 cores or fewer) #### GPU Acceleration `SPAMFOO_GPU` Set to `true` to always run the text model on an NVIDIA GPU, or `false` to always use the processor. When it's set, it overrides the **GPU Acceleration** switch on the Settings page. See [Turn On GPU Acceleration](https://help.spamfoo.com/guides/gpu-acceleration), which also lists the other GPU variables. ## Change Other Settings Data retention, backups, display, and the other settings not listed here are changed on **System > Settings** in the admin console, or with the settings API. For each one, its place on the Settings page, its settings API key, and its default, see [Read and Change Settings](https://help.spamfoo.com/api/settings#setting-keys). ## Data Directory SpamFoo Client keeps its data in the data directory. This is the install folder unless you set [`--data-dir`](https://help.spamfoo.com/guides/configure#data-directory-location). In Docker it's `/data`, and with the Linux package it's `/var/lib/spamfoo`. The **Storage** card on **System > Status** shows the exact paths. ``` / backups/ Nightly backups correction_archive/ Corrections reported from this server etc/ License key, proxy secret, release channel, installation ID, and encryption keys logs/ Log files personalization/ Each user's rules and learned preferences runtime/ NVIDIA libraries (only with GPU acceleration) spool/ Messages waiting to be scanned (standalone mode only) trained-models/ Downloaded models (downloaded again if missing) admin.db Settings, scan history, and totals address-map.json The address map a mail server pushed domain_age.db Saved domain ages spamfoo.conf Startup settings, if you created this file update-status.json The result of the last install script upgrade (Linux package only) ``` To learn what to back up, see [Update and Back Up](https://help.spamfoo.com/guides/updates#backup). ## Tune Performance The defaults keep processor and memory use low, which suits most mail servers. Change them only if messages wait too long to be scanned or the server is dedicated to email scanning. ### Concurrency [Max Concurrency](https://help.spamfoo.com/guides/configure#max-concurrency) sets how many messages are scanned at once. A higher value lets busy servers keep up, but uses more memory and processor time. On servers with few processor cores, a value of 1 can work better. After a change, watch the **Memory** and **CPU** tiles on **System > Status**. ### Speedup Optimizations [Enable Speedup Optimizations](https://help.spamfoo.com/api/settings#enable-speedup-optimizations) lets SpamFoo Client use more processor and memory to scan each message faster. It's off by default and applies to the next message scanned, with no restart. > **Warning** Turn this on only if the server is dedicated to SpamFoo Client or has a lot of unused processor and memory. It can take processor time and memory that your mail server needs. - **What it does:** the text model gets its own set of threads that can use every processor core, and the model runtime turns on extra optimizations. - **Processor:** while a message is scanned, SpamFoo Client can use every core. On a busy mail server this competes with mail delivery. - **Memory:** the models keep memory between messages instead of releasing it, so memory use goes up and stays up. - **Concurrency:** each message can already use every core, so a high Max Concurrency can make scanning slower. Start at 2. - **GPU servers:** it also changes how GPU memory is managed. Watch memory after you turn it on. To see the effect, compare **Average Scan Time** on the **Mail Health** page before and after. If delivery slows down or memory runs short, turn it off. If the server has an NVIDIA GPU, [GPU acceleration](https://help.spamfoo.com/guides/gpu-acceleration) is usually a better choice: - The text model does the same small math many thousands of times. A GPU is built to do that kind of work all at once. - The work moves off the processor, so scanning and mail delivery stop competing for the same cores. ### Timeouts Your mail server waits a limited time for each scan. **Queue Timeout** only covers the time a message waits for a free slot, and the scan takes more time after that. Set **Queue Timeout** a few seconds below your mail server's limit. Then, when SpamFoo Client is too busy, it returns an error (HTTP 503) that your mail server can handle, instead of the mail server cutting off the request. ## Configure Docker In Docker, pass environment variables with `-e`, or list them in an env file. The image already sets these: - `SPAMFOO_BIND_ADDRESS=0.0.0.0`, so your mail server can reach the container - `SPAMFOO_DISABLE_BINARY_UPDATE=true`, so you update by pulling a new image - The data directory is `/data`, so the models, settings, and history are all stored on the volume The image doesn't set `SPAMFOO_MODE`, so a container runs in embedded mode unless you set it. Pulling a newer image never changes the mode of a container you already run. To use standalone mode, add `SPAMFOO_MODE=standalone`, as in [Install With Docker](https://help.spamfoo.com/guides/install#docker). ``` docker run -d --name spamfoo-client --restart unless-stopped --stop-timeout 30 \ -e SPAMFOO_MODE=standalone --env-file spamfoo.env \ -e SPAMFOO_MAX_CLASSIFICATION_CONCURRENCY=4 \ -p 127.0.0.1:16253:16253 -v spamfoo-data:/data \ spamfoo/spamfoo-client:latest ``` Settings you save on the Settings page or through the settings API are kept in `/data`, so they survive when you replace the container. A `spamfoo.conf` file in `/data` is also read when the container starts. ## Next Steps - Connect your mail server. See [How Integration Works](https://help.spamfoo.com/integrations). - Learn the admin console. See [Use the Admin Console](https://help.spamfoo.com/guides/admin-console). - Plan updates and backups. See [Update and Back Up](https://help.spamfoo.com/guides/updates). *** Source: https://help.spamfoo.com/guides/commands Last updated: 2026-10-27 # Use SpamFoo Client Commands The `spamfoo-client` program has commands for common admin tasks: making a sign-in link, adding a license key, checking that the service is ready, and changing the proxy secret. This page explains each one. - Commands work on the files in the data directory, so most of them also work while the service is stopped. Only `status` contacts the running service. - Running `spamfoo-client` with no command, or with `run`, starts the service instead. - To see the commands and options on the server, run `spamfoo-client help`. ## Run a Command How you run a command depends on how you installed SpamFoo Client: ### Docker Run it inside the running container. It uses `/data` as the data directory. Use the service name from your compose file, or the container name with `docker exec`. ``` docker compose exec spamfoo-client spamfoo-client login-link docker exec spamfoo-client spamfoo-client login-link ``` ### Archive Run the program from the install folder and pass the data directory. On Linux, run it as root. ``` sudo /opt/spamfoo/spamfoo-client login-link --data-dir=/var/lib/spamfoo ``` When root runs a command on Linux, the files it writes are given to the owner of the folder they're in, so the service can still read them. ### Options for Every Command Write options as `--name=value` or `--name value`. | Option | What It Does | | --- | --- | | `--data-dir=` | The data directory. Without it, the command uses `/data` in a container, `/var/lib/spamfoo` with the Linux package, and the program's own folder otherwise. | | `--conf=` | The settings file to read. The command reads the same settings file as the service. See [Settings File](https://help.spamfoo.com/guides/configure#settings-file). | | `--mode=standalone` | Confirms that the data directory belongs to a SpamFoo Client in standalone mode. See [Commands for Standalone Mode](https://help.spamfoo.com/guides/commands#standalone-only). | | `--port=` | The port the service listens on, if it isn't 16253. | ## Make a Sign-In Link ``` spamfoo-client login-link [options] ``` Prints a one-time link to the dashboard. Whoever opens the link and clicks **Continue** is signed in with the role in the link. The command makes the link from the proxy secret file, so it works even while the service is stopped. | Option | What It Does | | --- | --- | | `--role=` | `SystemAdmin` (the default), `DomainAdmin`, or `User`. | | `--domain=` | The domain a Domain Admin manages. Repeat the option, or separate domains with commas, to give more than one. | | `--email=
` | The mailbox for a User. Required for `User`. Optional for the other roles. | | `--minutes=` | How long the link can be used, from 1 to 1440 minutes. The default is 15. | | `--return-path=` | A dashboard page to open after sign-in, starting with `/admin/`. | | `--url=
` | The address the link starts with, such as `http://spamfoo.example.com:16253`. | Examples: ``` spamfoo-client login-link --role=DomainAdmin --domain=example.com,example.org spamfoo-client login-link --role=User --email=pat@example.com --minutes=60 ``` - The link starts with `--url`. Without it, the link uses [`SPAMFOO_PUBLIC_URL`](https://help.spamfoo.com/guides/configure#public-url) in standalone mode. Otherwise it uses `http://localhost` and the port, and the command prints a note. - Each link works once. Changing the proxy secret stops every unused link. - For what each role sees, see [Manage Sign-In and Roles](https://help.spamfoo.com/guides/roles). > **Warning** Treat a sign-in link like a password until it's used or expires. Anyone who opens it first gets its access. ## Add a License Key ``` spamfoo-client activate --file=/root/spamfoo-key.txt spamfoo-client activate --stdin < /root/spamfoo-key.txt ``` Saves the license key to `etc/sf.key` in the data directory, readable only by its owner. The key is read from a file or from standard input, never typed after the command, so it stays out of your shell history. - A running service in standalone mode picks up the key within a few seconds. Check with [`status`](https://help.spamfoo.com/guides/commands#status). - If `SPAMFOO_LICENSE_KEY` is set, the command warns you, because the environment variable wins over the file. - In Docker, pass the file with `docker compose exec -T spamfoo-client spamfoo-client activate --stdin < spamfoo-key.txt`. ## Check Readiness ``` spamfoo-client status ``` Shows whether a license key is set up, then asks the running service whether it's ready to classify mail. It prints one of these lines: - `ready`: SpamFoo Client is ready. The exit code is 0. - `not ready`, with the reasons: SpamFoo Client is running but can't classify mail yet. The exit code is 1. - `down`: nothing answered. The exit code is 2. The exit codes make it easy to use `status` in scripts. It checks the same thing as [`/health/ready`](https://help.spamfoo.com/api/health#ready). ## Change the Proxy Secret ``` spamfoo-client rotate-secret ``` Writes a new random proxy secret to `etc/sf.proxysecret`. In standalone mode, the running service picks it up within a few seconds. - Everyone signed in to the dashboard is signed out, and unused sign-in links stop working. - Mail servers can keep using the old secret for 15 minutes. Give them the new one before then. - If `SPAMFOO_PROXY_SECRET` is set, the command does nothing, because the environment variable wins over the file. Change the variable instead. See [Change the Secret](https://help.spamfoo.com/guides/roles#change-the-secret). ## Print the Connection String ``` spamfoo-client connection-string [--url=
] ``` Prints the server's address and the proxy secret together as one value that starts with `spamfoo+http://` or `spamfoo+https://`. The address follows the same rules as [`login-link`](https://help.spamfoo.com/guides/commands#login-link). The same value is on the **Connection string** card on **System > Status** in standalone mode. Use it to connect SmarterMail to this SpamFoo Client: paste it into the setup card for SpamFoo in SmarterMail. Use `--url`, or set `SPAMFOO_PUBLIC_URL`, so the address is one SmarterMail can reach. See [Use SpamFoo Client on Another Server](https://help.spamfoo.com/integrations/smartermail/setup#external-client). > **Warning** The connection string contains the proxy secret. Treat it like a password. ## Commands for Standalone Mode `activate` and `rotate-secret` only change a SpamFoo Client in standalone mode. They refuse to run unless one of these is true: - The settings file or the environment sets `SPAMFOO_MODE=standalone`. - You add `--mode=standalone` to the command. - The command found the data directory on its own, as it does with the Linux package and Docker. They never change a SpamFoo Client that SmarterMail runs. SmarterMail manages its license key and secret itself. ## Exit Codes | Code | Meaning | | --- | --- | | `0` | The command worked. | | `1` | The command failed, or `status` found the service not ready. | | `2` | `status` got no answer from the service. | | `64` | The command or an option was wrong. The message says which. | | `78` | A setting is wrong, or the command can't read the settings file. With the Linux package, run commands as root. | *** Source: https://help.spamfoo.com/guides/admin-console Last updated: 2026-10-27 # Use the Admin Console The admin console is the SpamFoo Dashboard as a System Admin sees it. This page is a tour of each part of the console and links to the pages that explain each task in detail. ## Open the Console - **With SmarterMail:** sign in to SmarterMail as a system administrator and open the SpamFoo Dashboard from there. No extra setup is needed. See [Open Your Dashboard](https://help.spamfoo.com/guides/open-dashboard). - **Without SmarterMail:** on the server running SpamFoo Client, go to `http://localhost:16253/admin/` and sign in with the proxy secret. You can also run `spamfoo-client login-link` on the server and open the one-time link it prints. See [Sign In Without SmarterMail](https://help.spamfoo.com/guides/roles#sign-in-without-smartermail). - **From another computer:** set up a reverse proxy first. See [Set Up Remote Access](https://help.spamfoo.com/guides/roles#remote-access). ## Find Your Way Around The menu on the left has these items for a System Admin: | Menu Item | What It Shows | | --- | --- | | **Overview > Mail Health** | Mail volume, caught rate, and any problems that need attention. This is the page the console opens on. | | **Overview > Domains** | Every domain on this server that has received mail, with totals for each. | | **Messages** | A searchable history of the messages SpamFoo has checked. | | **Senders** | The domains, IP addresses, and email addresses sending mail to this server. | | **Rules** | System Spam Rules, which apply to every domain on the server. | | **Anti-Phishing** | The Anti-Phishing model and its sensitivity, plus protected identities and delegated senders for each domain. | | **System > Status** | The health of SpamFoo Client, its license, versions, updates, and storage. | | **System > Settings** | Settings you can change from the console. | | **System > Logs** | A log viewer. | | **Help** | Opens this help site in a new tab. | The bar across the top has: - **A search box** that finds domains, sender domains, sender email addresses, sender IP addresses, and mailboxes. It only appears for a System Admin who is not viewing as another role. - **A language picker** and a button to switch between light and dark mode. - **The role button**, which shows your current role. Click it to view the console as a Domain Admin or User. When you signed in with the proxy secret or a sign-in link, it also has **Log out**. See [View as Another Role](https://help.spamfoo.com/guides/roles#view-as-another-role). ## Check Mail Health Mail Health is the first page you see. Use the time range menu at the top right to pick the last hour, 6 hours, 24 hours, 7 days, or 30 days. - **Alerts:** a banner appears at the top when SpamFoo Client can't reach the SpamFoo API, or when the license is missing, past due, suspended, expired, or canceled. Each banner links to System Status. While the API is unreachable, SpamFoo Client keeps classifying with what is already on the server, but models are not updated and domain reputation services are unavailable. - **Status line:** whether the API is connected, how long the engine has been running, how many messages it is checking per minute, and how many are waiting in the scan queue. - **Tiles:** Total Scanned, Caught Rate, Average Scan Time, Not Spam, Spam, and Phishing for the selected range. Caught Rate is the share of scanned mail that was spam or phishing. - **Mail Trend:** a chart of Not Spam, Spam, and Phishing over the range. The label at the top right says whether each point is a minute, an hour, or a day. - **Classifications for Non-Spam Email:** how much of the mail that was not spam went to Primary, Updates, Transactions, and Promotions, plus any that was not classified. See [The Four Email Classifications](https://help.spamfoo.com/guides/how-classification-works#classifications). - **Top Spam Sources:** the domains or IP addresses that sent the most spam and phishing in the time range. **Caught %** is the share of each sender's mail that was spam or phishing, with spam in red and phishing in gold. Click a row to see those messages. **All senders** opens the Senders page. ## Review Domains The Domains page lists every domain on this server that has received mail. Pick the last 7, 30, or 90 days at the top right. - Four tiles show Total Scanned, Not Spam, Spam, and Phishing for all domains together. - Use the search box to find a domain. The **All**, **Active**, and **Idle** buttons filter the list. An idle domain shows a tag with how many days it has been quiet. - Under each domain name, tags show how many Domain Spam Rules and protected identities the domain has. - The **Mailboxes** column counts mailboxes that received mail in the range. **Scanned** includes a small chart of daily volume. - Click a column heading to sort. Right-click a domain for **View Domain**, **View Messages**, and **View as Domain Admin**. ### Domain Details Click a domain to open its details for the same time range. - **View as Domain Admin** switches the console to that domain's Domain Admin view. **View Messages** opens the Messages page filtered to the domain. - Tiles show Total Scanned, Not Spam, Spam, and Phishing, followed by a daily Mail Trend chart and the Classifications breakdown. - **Mailboxes** lists each mailbox that received mail in the range, with its Scanned, Spam, and Phishing counts and a caught rate, highest caught rate first. Click a mailbox to see its messages, or right-click it and choose **View as User**. - **Top Senders Into This Domain** lists the sender domains that sent this domain the most spam and phishing in the last 24 hours. Click a sender to see those messages. ## Search Messages The Messages page is a history of the messages SpamFoo has checked on this server. As a System Admin you see messages for every mailbox. - Filter by **Detection Type**, **Detection Path**, **Verified**, **Sender Domain**, **Sender IP**, or **Recipient**, then click **Search**. **Clear** resets the filters. - The heading shows how many messages match and how they split between not spam, spam, phishing, and classification only. - Click a message to open its details, including the signals behind the result, Sender Verification, and the processing steps. Right-click a message to create a rule for the sender or the sender's IP address. For more, see [See Why a Message Was Spam](https://help.spamfoo.com/guides/why-spam), [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result), and [Check Sender Verification](https://help.spamfoo.com/guides/rules-protection#sender-verification). ## Review Senders The Senders page shows who is sending mail to this server, by domain, IP address, or email address. You can open a sender's activity and create a rule from it. See [Review Senders](https://help.spamfoo.com/guides/rules-protection#review-senders). ## Manage Rules The Rules page holds the System Spam Rules. Each one marks mail from a sender as Spam or Not Spam on every domain. See [Manage Spam Rules and Senders](https://help.spamfoo.com/guides/rules-protection). ## Manage Anti-Phishing The Anti-Phishing page has two tabs: - **Detection:** turns the Anti-Phishing model on or off and sets its sensitivity. The model needs a SpamFoo Anti-Phishing license. - **Protected identities:** each domain's protected identities, the names attackers are likely to fake, such as executives or the payroll team. See [Protect Against Phishing](https://help.spamfoo.com/guides/phishing-protection). ## Check System Status **System > Status** shows everything about SpamFoo Client itself. - **Status line:** whether the API is connected, how long SpamFoo Client has been up, its version and release channel, and whether a GPU is in use. - **Tiles:** Messages per Minute, Processed Today, Scan Queue, Memory, CPU, and Database. A green dot next to a tile name means the value updates live. - **License:** the license status, plan, license key, license ID, expiration date, and the features the plan includes. See [Read the License Card](https://help.spamfoo.com/guides/admin-console#license-card). - **Connection string:** in standalone mode only, the server's address and the proxy secret as one value. The value is hidden until you click **Show**, and the copy button copies it either way. Paste it into the setup card for SpamFoo in SmarterMail to connect SmarterMail to this SpamFoo Client. Treat it like a password. See [Use SpamFoo Client on Another Server](https://help.spamfoo.com/integrations/smartermail/setup#external-client). - **Versions:** the version of SpamFoo Client and of each model. - **Maintenance:** **Update Models** and **Update Client**. Each asks you to confirm, and the service may restart to apply an update. When program updates are turned off, **Update Client** is grayed out and explains how to update instead. In a Docker container running in standalone mode, the **Update Client** row instead checks for a newer version and tells you to pull the new image. See [Update and Back Up](https://help.spamfoo.com/guides/updates). - **Storage:** the database size, how many messages are stored, the oldest record, and the folder paths for the database, personalization data, models, backups, logs, and license key. ### Read the License Card The status tag at the top of the card can be: - **Active** or **Trial:** the license is in good standing. - **Grace Period:** the license has expired, and SpamFoo keeps it working for a short time so you can renew it. The card shows **Grace Period Ends** and a **License in grace period** message with the date. Renew the license in your SpamFoo account before then. - **Waiting for a license key:** no key is set up. Add one with [`spamfoo-client activate`](https://help.spamfoo.com/guides/commands#activate). - Other statuses, such as past due, suspended, expired, or canceled, mean the license needs attention in your SpamFoo account. SpamFoo Client checks its license with SpamFoo regularly. If it can't reach SpamFoo by this date, it stops classifying mail. When the license has expired for good, the card shows **License expired**, and classification requests are refused until a valid license key is active. See [Requests Fail After the License Expires](https://help.spamfoo.com/guides/common-problems#license-expired). ## Change Settings **System > Settings** groups settings into cards: Display, Classification, Hardware, Logging, Scan History, Message Content, and Database Backups. Change any fields, then click **Save Changes** in the bar at the bottom of the page. **Discard** throws away edits you haven't saved. See [Read and Change Settings](https://help.spamfoo.com/api/settings) for what each setting does. ## Read Logs **System > Logs** lets you search SpamFoo Client's logs by date, category, and text. See [Read Logs in the Admin Console](https://help.spamfoo.com/guides/monitoring#log-viewer). ## See What Other Roles See Domain Admins and Users see a smaller menu that covers only their own domain or mailbox. To see exactly what they see, use **View as Domain Admin** or **View as User**. See [Manage Sign-In and Roles](https://help.spamfoo.com/guides/roles), [Manage a Domain](https://help.spamfoo.com/guides/manage-domain), and [Read Your Overview](https://help.spamfoo.com/guides/overview). *** Source: https://help.spamfoo.com/guides/roles Last updated: 2026-10-27 # Manage Sign-In and Roles This page explains how people sign in to the SpamFoo Dashboard, what each role can see and change, and how to reach the dashboard from another computer. ## Understand the Three Roles | Role | What They See | | --- | --- | | **System Admin** | The full [admin console](https://help.spamfoo.com/guides/admin-console): all domains, Messages and Senders for the whole server, System Spam Rules, Anti-Phishing for any domain, and the System pages (Status, Settings, and Logs). | | **Domain Admin** | Their own mailbox (Overview, My Messages, and My Rules) plus a **Domain Admin** menu for their domain: Domain Overview, Mailboxes, Rules, and Anti-Phishing. See [Manage a Domain](https://help.spamfoo.com/guides/manage-domain). | | **User** | Their own mailbox only: Overview, My Messages, and My Rules. See [Read Your Overview](https://help.spamfoo.com/guides/overview). | A Domain Admin can see the totals for every mailbox in the domain, but can't open another person's messages. ## How Sign-In Works - **With SmarterMail:** people sign in to SmarterMail and open the SpamFoo Dashboard from there. SmarterMail tells SpamFoo who they are and gives them a role. SmarterMail system administrators become System Admins, domain administrators become Domain Admins, and everyone else is a User. See [Open Your Dashboard](https://help.spamfoo.com/guides/open-dashboard). - **With the proxy secret:** the proxy secret is a long random password that SpamFoo Client keeps in a file on the server. Anyone who signs in with it is a System Admin. - **With a sign-in link:** a System Admin, or a script that has the proxy secret, makes a one-time link for a System Admin, a Domain Admin of one or more domains, or a User with one mailbox. Whoever opens the link gets that role. See [Give Someone a Sign-In Link](https://help.spamfoo.com/guides/roles#sign-in-links). Opening the dashboard on the server itself does not skip sign-in. Requests from the same machine still need the proxy secret or a sign-in link. ## Sign In Without SmarterMail 1. On the server running SpamFoo Client, open `http://localhost:16253/admin/` in a browser. The **Sign in to SpamFoo** page appears. 2. Open the file `etc/sf.proxysecret` inside the SpamFoo data directory and copy its contents. With the Linux package, the file is `/var/lib/spamfoo/etc/sf.proxysecret`. With Docker, print it with this command: ``` docker exec spamfoo-client cat /data/etc/sf.proxysecret ``` 3. Paste the value into **Proxy secret**. 4. Click **Sign in**. The browser sends the secret once and gets a sign-in cookie in return, so the secret isn't stored in the browser. The sign-in lasts 24 hours and is renewed while you use the dashboard. To sign out, click the role button at the top right and choose **Log out**. If the page shows **That secret did not work**, check that you pasted the whole value. Older versions of SpamFoo Client saved the secret in the browser. The first time you open the dashboard after an update, it signs you in with that saved secret and then deletes it. If the secret has changed since, the page shows **Saved secret no longer works**, and you paste the new value. In standalone mode, too many wrong secrets or bad sign-in links from one IP address in a short time block that address for a while. See [Requests Fail With a 429 Error](https://help.spamfoo.com/guides/common-problems#error-429). > **Warning** Treat the proxy secret like a password. Anyone who has it gets full System Admin access to the dashboard. ### Give Someone a Sign-In Link A sign-in link lets someone open the dashboard without the proxy secret, with only the access you choose. Make one on the server with [`spamfoo-client login-link`](https://help.spamfoo.com/guides/commands#login-link): ``` sudo spamfoo-client login-link --role=DomainAdmin --domain=example.com ``` Scripts and other programs that have the proxy secret can make links with the API. See [Create a Sign-In Link](https://help.spamfoo.com/api/login-link). - **System Admin:** the full admin console. - **Domain Admin:** the Domain Admin menu for the domains in the link. With more than one domain, a **Domain** picker appears at the top of the dashboard. If the link also names a mailbox, the person sees that mailbox's My Email menu too. - **User:** the My Email menu for the mailbox in the link. When someone opens the link, a page asks them to click **Continue**. The link is used up only when they click it, so a mail scanner that opens links in advance doesn't use it. Each link works once and expires after 15 minutes unless you choose a different time. If the link has expired, was already used, or stopped working because the proxy secret changed, the page says **This sign-in link cannot be used**. Make a new link. ### Choose Your Own Secret When SpamFoo Client starts and has no secret, it makes a random one and saves it to `etc/sf.proxysecret`. To use a value you choose instead, set the `SPAMFOO_PROXY_SECRET` environment variable before SpamFoo Client starts. This is the easiest way to keep a known secret for a Docker container. Use at least 32 characters. See [Proxy Secret](https://help.spamfoo.com/guides/configure#proxy-secret). ### Change the Secret Change the secret if you think someone else may have it. - **If you set `SPAMFOO_PROXY_SECRET`:** set it to a new value and restart SpamFoo Client. - **In standalone mode:** run [`spamfoo-client rotate-secret`](https://help.spamfoo.com/guides/commands#rotate-secret), or put a new value in `etc/sf.proxysecret`. The running service picks it up within a few seconds, with no restart. - **In embedded mode:** stop SpamFoo Client, delete `etc/sf.proxysecret`, and start it again. A new secret is created. - **If you use SmarterMail:** SmarterMail creates and manages this file. Do not change or delete it yourself. When the secret changes, everyone signed in to the dashboard is signed out, and unused sign-in links stop working. If a mail server sends messages to SpamFoo Client, give it the new secret. In standalone mode, the old secret keeps working for mail for 15 minutes, so you have time to update it. ## View as Another Role A System Admin can switch the dashboard to show exactly what a Domain Admin or User sees. This helps with support and with checking that rules apply the way you expect. Only System Admins can do this. 1. Click the role button at the top right. 2. Choose **Domain Admin** or **User**. 3. In the dialog, select a domain. For a user, select the domain first and then the user. 4. Click **OK**. The role button now shows the role and the domain or user you are viewing. To go back, click the role button and choose **System Admin**. You can also switch from other pages: - On **Overview > Domains**, right-click a domain and choose **View as Domain Admin**. - On a domain's details page, click **View as Domain Admin**. - In the **Mailboxes** list on a domain's details page, right-click a mailbox and choose **View as User**. When you view as a Domain Admin, only the Domain Admin menu appears, because you are viewing a domain and not a specific mailbox. ## Know Who Can Change What | What | Who Can Change It | | --- | --- | | System Spam Rules (every domain) | System Admin only | | Domain Spam Rules | System Admin, or a Domain Admin of that domain | | A mailbox's own Spam Rules | System Admin, or the owner of that mailbox | | Protected identities and delegated senders | System Admin, or a Domain Admin of that domain | | System Status, Settings, Logs, and updates | System Admin only | ## Set Up Remote Access If you use SmarterMail, there is nothing to set up. SmarterMail passes dashboard requests on to SpamFoo Client, so people reach the dashboard through SmarterMail's web interface from any computer that can reach SmarterMail. Without SmarterMail, you need something to do that job. To open the dashboard from your own computer, put a reverse proxy, such as Nginx or Caddy, in front of SpamFoo Client. A reverse proxy is a web server that accepts the connection and passes it on to SpamFoo Client. > **Warning** Do not open port 16253 to the internet. Set up the reverse proxy so it: - **Uses HTTPS (TLS).** The proxy secret is sent with every request, so it must be encrypted on the network. - **Passes on only the `/admin/` path.** In embedded mode, SpamFoo Client treats requests from the same machine as trusted for classifying mail. If the proxy passes on every path, anyone who can reach the proxy can send it messages to classify. - **Limits who can connect.** Allow only your admin network or VPN, and add the proxy's own sign-in if you can. People still sign in to the dashboard with the proxy secret or a sign-in link after they get through the proxy. In standalone mode, set [`SPAMFOO_PUBLIC_URL`](https://help.spamfoo.com/guides/configure#public-url) to the proxy's address, such as `https://spamfoo-admin.example.com`, so sign-in links made on the server point to it. Every request through a proxy on the same server comes from the same address. In standalone mode, too many failed sign-ins through the proxy block that address, and so everyone who uses the proxy, for a while. Here are examples for Nginx and Caddy. Replace the server name, certificate paths, and network range with your own. ### Nginx ``` server { listen 443 ssl; server_name spamfoo-admin.example.com; ssl_certificate /etc/ssl/certs/spamfoo-admin.crt; ssl_certificate_key /etc/ssl/private/spamfoo-admin.key; allow 10.0.0.0/8; deny all; location /admin/ { proxy_pass http://127.0.0.1:16253; proxy_set_header Host $host; proxy_buffering off; } } ``` `proxy_buffering off` lets the live numbers on **System > Status** update as they change. ### Caddy ``` spamfoo-admin.example.com { tls /etc/ssl/certs/spamfoo-admin.crt /etc/ssl/private/spamfoo-admin.key @admin { remote_ip 10.0.0.0/8 path /admin/* } handle @admin { reverse_proxy 127.0.0.1:16253 { flush_interval -1 } } handle { respond 403 } } ``` `flush_interval -1` lets the live numbers on **System > Status** update as they change. If the server name is reachable from the internet, you can leave out the `tls` line and Caddy gets a certificate for you. ### Docker The Docker image listens on all network interfaces so your mail server can reach it. Publish port 16253 only to your mail server's network, or keep the container behind a firewall. *** Source: https://help.spamfoo.com/guides/rules-protection Last updated: 2026-10-20 # Manage Spam Rules and Senders This page shows System Admins how to make System Spam Rules, find the senders that need a rule, and read Sender Verification to tell real senders from fakes. ## Understand System Spam Rules - A Spam Rule marks all mail from a sender as **Spam** or **Not Spam**. - System Spam Rules apply to every domain and mailbox on this server. Only a System Admin can manage them, on the **Rules** page. - Domain Admins make Domain Spam Rules for their own domain. See [Manage a Domain](https://help.spamfoo.com/guides/manage-domain). - Users make Spam Rules for their own mailbox on **My Rules**. They can see the System and Domain Spam Rules that apply to them, but can't change them. See [Manage Your Rules](https://help.spamfoo.com/guides/my-rules). For Anti-Phishing settings such as the Anti-Phishing model and protected identities, see [Protect Against Phishing](https://help.spamfoo.com/guides/phishing-protection). ## Add a System Spam Rule 1. In the menu, click **Rules**. 2. Click **Add Rule**. 3. In **Email, Domain, or IP Range**, enter the sender. 4. To cover the domain's subdomains too, check **Include subdomains**. 5. Under **Action**, choose **Spam** or **Not Spam**. 6. Optionally, add a **Note** that says why the rule exists. 7. Click **Create**. ### What a Rule Can Match | Enter | Example | Matches | | --- | --- | --- | | An email address | `news@example.com` | Mail from that one address | | A domain | `example.com` | Mail from any address at that domain | | A wildcard | `*.example.com` or `*@example.com` | Anything that fits the pattern. `*` stands for any text. `*.example.com` also covers `example.com` itself. | | An IP address or range | `10.0.0.0/24` | Mail sent from a server in that range. The range uses CIDR notation, which is an address followed by a slash and the size of the network. | A wildcard must contain at least one letter or number, and the value can be up to 320 characters long. ### Change or Remove a Rule - Click a rule to edit it. The edit dialog also has a **Delete** button. - Use the switch in the **Enabled** column to turn a rule off without deleting it. - Right-click a rule and choose **Delete** to remove it. - Use the **All**, **Spam**, and **Not Spam** buttons to filter the list. ## Know How Rules Work Together When more than one Spam Rule matches the same message, SpamFoo uses one of them: 1. A rule made by a System Admin wins over one made by a Domain Admin, which wins over one made by a User. 2. Among rules made by the same role, **Not Spam** wins over **Spam**. 3. If there is still a tie, the more exact rule wins: an email address, then a domain, then a wildcard, then an IP range. When the chosen rule is a Domain Spam Rule or a mailbox rule, it decides the result before any other check runs. A System Spam Rule works a little differently: - SpamFoo runs its other checks first and applies the System Spam Rule at the end. - This lets a user's own choices win for their mailbox. A **Not Spam** System Spam Rule gives way when the user's past corrections for that sender decided the message. A **Spam** System Spam Rule always applies. - In every other case, the System Spam Rule decides the result, including over phishing checks. See [How SpamFoo Classifies Email](https://help.spamfoo.com/guides/how-classification-works) and [How Personalization Works](https://help.spamfoo.com/guides/personalization). ## Create Rules From Messages and Senders You don't have to type every rule by hand. A rule made from these pages is a System Spam Rule. - **Messages:** right-click a message and choose **Create rule** for the sender, or **Create rule for IP** for the server it came from. You can also click **Create Rule** in a message's details. - **Senders:** right-click a sender and choose **Create rule**, or click **Create Rule** in the sender's details. ## Review Senders The **Senders** page shows who is sending mail to this server. Use it to find senders that send a lot of spam, or senders that need a Not Spam rule. - Switch between **Domains**, **IP Addresses**, and **Email Addresses**. - Pick a time range at the top right, from the last hour to the last 30 days. - Each row shows how many messages were scanned, how many were spam and phishing, the caught rate (the share that was spam or phishing), and when the sender was last seen. - The list starts with the most spam at the top. Click a column heading to sort another way. - Right-click a row for **Open details**, **View messages**, **Create rule**, and **Copy value**. Click a row to open its details. You see totals, when the sender was first and last seen, a daily activity chart, and the sender's recent messages. Daily activity is only kept for domains and IP addresses, not single email addresses. The Senders page is about mail coming in from outside. To see the domains hosted on your server, use **Overview > Domains**. ## Check Sender Verification Sender Verification tells you whether a message really came from the domain in its From address. SpamFoo works it out from three standard email checks: - **SPF:** whether the sending server is allowed to send for the domain. - **DKIM:** whether the message carries a valid digital signature from the domain. - **DMARC:** the domain's own policy that ties SPF and DKIM to the From address. Each message gets one of four states: | State | What It Means | | --- | --- | | **Verified** | DMARC passed, or the domain has no DMARC policy and SPF or DKIM passed. | | **Spoofed** | DMARC failed, or SPF failed and DKIM did not pass. The message may not be from the domain it claims. | | **Blocked** | SpamFoo's global reputation data marks the sender's domain as untrusted because of high spam volume. | | **Unknown** | Nothing proved the sender real or fake. This includes messages that a rule, a learned preference, or a cached result decided before authentication was checked. | Where to find it: - The **Verified** column on the Messages page shows the state for each message. - The **Verified** filter narrows the list to one state. For example, filter on **Spoofed** to look for forged mail that got through. - The **Sender Verification** card in a message's details explains the state and the reason for it. > **Tip** Before you add a Not Spam rule for a domain, check that its messages are Verified. A rule for a domain that is often Spoofed also lets forged mail through. *** Source: https://help.spamfoo.com/guides/phishing-protection Last updated: 2026-10-20 # Protect Against Phishing Phishing is mail that pretends to come from someone you trust so it can steal passwords, money, or data. This page explains how SpamFoo spots phishing, how to set up the Anti-Phishing model, and how to set up protected identities for your domains. ## How SpamFoo Spots Phishing SpamFoo runs three phishing checks on every message. The Impersonation check needs a SpamFoo Anti-Phishing license. - **Known phishing links:** links in the message are compared to a list of known phishing sites. SpamFoo Client downloads this list on its own and refreshes it about once an hour. No setup is needed. - **Impersonation:** the sender's display name is compared to the [protected identities](https://help.spamfoo.com/guides/phishing-protection#protected-identities) for the domain the message was sent to. If the name matches but the sender isn't approved for that identity, the message is phishing. This check needs a SpamFoo Anti-Phishing license. - **Look-alike domains:** the sender's domain is checked for typos and look-alike characters that copy a well-known internet domain. These checks run early. Only Domain Spam Rules and mailbox rules come before them. A message that fails a check gets the **Phishing** result and is handled like spam. See [How SpamFoo Classifies Email](https://help.spamfoo.com/guides/how-classification-works). With a SpamFoo Anti-Phishing license, SpamFoo also scores each message with the [Anti-Phishing model](https://help.spamfoo.com/guides/phishing-protection#anti-phishing-model). The model reads the message and judges how likely it is to be phishing or a scam, so it can catch phishing that the three checks above miss. ## Find Phishing Messages On the Messages page, a phishing message shows **Phishing** as its Detection Type. Set the **Detection Path** filter to **Phishing / impersonation** to list them. The message details show which check caught it: | Label | What It Means | | --- | --- | | **PhishTank match** | The message links to a known phishing site. | | **Impersonation** | The display name matches a protected identity, but the sender is not approved for it. | | **Look-alike domain** | Same as Impersonation, and the sender's domain is also a typo or look-alike of an approved domain. | | **Typosquat domain** | The sender's domain copies a well-known internet domain. | | **Phishing Model** | The Anti-Phishing model scored the message as phishing. | ## Turn On the Anti-Phishing Model The Anti-Phishing model needs a SpamFoo Anti-Phishing license: - **With the license:** the model downloads with model updates. It is off until you turn it on. - **Without the license:** the model doesn't download or run, and the switch to turn it on can't be changed. As a system administrator, go to **Anti-Phishing > Detection** and use **Anti-Phishing detection model** to turn the model on or off. The model adds some processing time to each message. ## Choose a Phishing Sensitivity Sensitivity sets how sure the model must be before it marks a message as phishing. On **Anti-Phishing > Detection**, move **Sensitivity** to one of three levels: - **Conservative:** fewer legitimate messages are marked as phishing, but more phishing reaches the inbox. - **Recommended:** the default, and the best choice for most servers. - **Aggressive:** catches the most phishing, but more legitimate messages may be marked as phishing. Each Anti-Phishing model update comes with its own cutoff for each level, so a level keeps about the same balance when the model changes. The page shows the current model's cutoff for the level you pick, and says so when two levels use the same cutoff. ## Understand Protected Identities Many phishing attacks don't use a dangerous link at all. Instead, the attacker writes as someone the reader knows and trusts. A message from the "owner" asks for an urgent wire transfer, "payroll" asks an employee to update their bank details, or "IT support" asks for a password. This is often called business email compromise. The message usually comes from a free mail account or a look-alike domain, but it shows the real person's name, and many mail apps show only the name. A protected identity tells SpamFoo who those people are. Each identity has a display name and a list of approved senders, which are the addresses or domains that person really sends from. When a message uses a protected name but comes from a sender that isn't approved, SpamFoo marks it as phishing. For example, if you protect the name "Pat Jones" with the approved sender `yourcompany.com`, a message from "Pat Jones" at `pat.jones@freemail-example.com` is marked as phishing. Add every address the person really sends from, including personal ones. If Pat sometimes writes from a personal Gmail account, add that full address as an approved sender. Otherwise, Pat's own messages from that account are marked as phishing. Add the address, not the domain, because approving `gmail.com` would approve every Gmail user. Name matching also catches look-alike characters, names in a different order, and small typos. If the display name doesn't match, SpamFoo also checks whether the subject line names a protected identity. A sender at a look-alike of an approved domain, such as `yourc0mpany.com` with a zero, is caught too. Protected identities belong to one domain. SpamFoo checks the identities of the domains the message is addressed to. If it can't tell, for example when the only recipients are in Bcc, it checks the identities of every domain. Protected identities need a SpamFoo Anti-Phishing license. Without it: - SpamFoo doesn't check display names against protected identities. - The **Protected identities** tab still shows your identities and delegated senders, but you can't add, change, or delete them. - Your identities, delegated senders, and Strict Mode setting are kept. They're checked again as soon as the license is active. ## Open Protected Identities - **System Admin:** click **Anti-Phishing** in the menu, open the **Protected identities** tab, and pick a domain in the **Domain** box. - **Domain Admin:** click **Domain Admin > Anti-Phishing**. You manage your own domain only. ## Add a Protected Identity 1. Click **Add Identity**. 2. In **Display Name**, enter the full name as it appears in email. 3. Under **Senders** (or **Email Addresses** in Strict Mode), enter an approved sender and add it to the list. Repeat for each one. 4. Optionally, add a **Note** about the person's role. 5. Click **Add Protected Identity**. ### Import From vCards To add many identities at once, click **Import vCards** and choose a `.vcf` file or a `.zip` file of `.vcf` files. A vCard is the standard contact file that most address books can export. The file can be up to 50 MB, and a `.zip` can hold up to 100 MB once unpacked. Identities that already exist are skipped. ### Edit, Turn Off, or Delete Identities - Right-click an identity for **Edit**, **Disable** or **Enable**, and **Delete**. The **Enabled** switch also turns an identity on or off without deleting it. - Select several identities to add the same sender to all of them, or to delete them together. ## Choose a Matching Mode Each domain has one matching mode, set with the **Strict Mode** switch at the top of the list. | Mode | Who Counts as Approved | | --- | --- | | **Relaxed** (Strict Mode off) | Anyone whose domain is in the identity's **Senders** list or is a delegated sender. If you enter an email address, SpamFoo uses its domain. | | **Strict** (Strict Mode on) | Only the exact addresses in the identity's **Email Addresses** list, plus any [delegated senders](https://help.spamfoo.com/guides/phishing-protection#delegated-senders). | Use Strict when other people share the identity's domain and could fake the name, such as a large company or a public email service. ## Add Delegated Senders Some real mail shows a protected person's name but comes from somewhere else. Examples are an outside assistant, a scheduling service, or tools like Bitbucket, Jira, and Google Docs that put a person's name in their notifications. Instead of adding these senders to every identity, add them under **Delegated Senders for Every Identity**, below the identity list. They are approved for all protected identities on the domain. 1. Click **Add Delegated Sender**. 2. Enter an email address or a domain. 3. Optionally, add a note. 4. Click **Add**. > **Note** A delegated sender approves everyone at its domain, even when you enter a full email address. To approve only one address, turn on Strict Mode and add the address to the identity's **Email Addresses** list instead. ## Let Mail Through From a Flagged Sender If Anti-Phishing keeps flagging a sender you trust, first check the cause: - For **Impersonation** or **Look-alike domain**, add the sender to the identity or as a delegated sender. - For **Phishing Model**, you can set [Sensitivity](https://help.spamfoo.com/guides/phishing-protection#sensitivity) to **Conservative** so the model flags fewer messages. - For any phishing result, you can add a **Not Spam** Spam Rule for the sender. A Spam Rule decides the result even when a phishing check fails. See [Manage Spam Rules and Senders](https://help.spamfoo.com/guides/rules-protection). Check [Sender Verification](https://help.spamfoo.com/guides/rules-protection#sender-verification) before you do this. A Not Spam rule for a sender that is often Spoofed lets real phishing through. *** Source: https://help.spamfoo.com/guides/monitoring Last updated: 2026-10-27 # Monitor Logs and Health This page shows how to read SpamFoo Client's logs, check its health, and connect it to the monitoring tools you already use. ## Read Logs in the Admin Console As a System Admin, go to **System > Logs**. The viewer opens on today's date. - **Search box:** finds log lines that contain your text within the chosen dates. - **Date range:** picks the first and last day to show. - **Category:** shows one log category, or **All Categories**. Debug log codes you have turned on also appear here as numbers. - **Refresh** loads the logs again. **Load more** loads the next set of lines, and **Jump to bottom** moves to the newest lines. - **Download** saves the lines you have loaded as a text file. For most problems, the viewer is the fastest way to find what you need. Use the files on disk when you want to send logs to another system. In standalone mode, SpamFoo Client also writes every log line to its console output. With the Linux package, read it with `journalctl -u spamfoo-client`. In Docker, read it with `docker logs`. This lets you collect logs with the tools you already use for other services. ## Find the Log Files Log files are in the `logs` folder inside the SpamFoo data directory. The exact path is listed under **Storage** on **System > Status**. | File | Category | What It Holds | | --- | --- | --- | | `classification-YYYYMMDD.txt` | Classification | Results for each message SpamFoo checks | | `updates-YYYYMMDD.txt` | Updates | Model downloads, version checks, and client updates | | `personalization-YYYYMMDD.txt` | Personalization | Corrections and the rules learned from them | | `system-YYYYMMDD.txt` | System | Startup, shutdown, settings changes, and everything else | | `errors-YYYYMMDD.txt` | Errors | Every warning, error, and fatal error from all categories | - A new file starts each day, or sooner when a file reaches 50 MB. A file started early gets a number at the end, such as `classification-20260330_001.txt`. - Log files are deleted after the **Retention Period** in the Scan History card on **System > Settings**. The default is 7 days. ### Read a Log Line Each line starts with the date, time, and time zone, then the level in brackets: ``` 2026-03-30 14:25:47.123 +00:00 [INF] Message text ``` Lines about one message may also show that message's log ID in brackets after the level. Longer entries, such as error details, continue on the lines below. | Tag | Level | Meaning | | --- | --- | --- | | `[DBG]` | Debug | Extra detail for troubleshooting | | `[INF]` | Information | Normal activity | | `[WRN]` | Warning | Something unexpected that didn't stop SpamFoo from working | | `[ERR]` | Error | A failure that affected one message or task | | `[FTL]` | Fatal | A failure that stopped the service | ## Change How Much Is Logged Use the Logging card on **System > Settings**, then click **Save Changes**. - **Log Level** sets the lowest level that is written. Choose **Detailed** (includes Debug), **Normal** (the default), **Warning**, **Error**, or **Fatal**. The change takes effect right away. If you choose **Detailed** to look into a problem, set it back to **Normal** when you are done, because detailed logging fills the disk faster. - **Debug Log Codes** are for advanced troubleshooting. Each code turns on extra logs for one part of SpamFoo. Use these only when SpamFoo support asks. ### Turn On Debug Codes 1. Type the codes support gave you in **Debug Log Codes**, separated by commas or spaces. 2. Click **Save Changes**. 3. Reproduce the problem. 4. Go to **System > Logs**, choose the code as the category, and click **Download**. Each code writes its own file in the `logs` folder, named after the code, such as `12-20261005.txt`. 5. Clear **Debug Log Codes** and click **Save Changes**. ## Check Health SpamFoo Client has three health endpoints, and none of them need a sign-in. The table below says what to watch on each. See [Check Health](https://help.spamfoo.com/api/health) in the API Reference for every response. ## Know What to Watch | Signal | Where to Find It | Look Into It When | | --- | --- | --- | | Service is running | `/health/live` | It doesn't answer, or answers with anything other than `200`. | | Ready to classify | `/health/ready` | It answers `503` for more than a few minutes. The `reasons` field says why. | | Messages waiting | `spoolQueue.waiting` in `/health`, or Scan Queue on System Status | It stays above zero, which means SpamFoo can't keep up with incoming mail. | | Timeouts | `spoolQueue.totalTimedOut` in `/health` | The number keeps going up. Those messages got no result. | | Rejected requests | `classifyIntake.totalRejected` in `/health` | The number goes up. | | Scan time | Average Scan Time on Mail Health | It climbs well above what is normal for your server. | | Caught rate | Caught Rate and Mail Trend on Mail Health | It jumps or drops suddenly. | | Connection and license | Alerts on Mail Health, and System Status | An alert says the API is disconnected or the license has a problem. | | Errors | `errors-*.txt` log files, or the Errors category in the log viewer | New errors appear, or they appear more often. | | Disk space | Your operating system's tools, and Storage on System Status | The drive that holds the SpamFoo data directory is getting full. | If messages are waiting or timing out, see [Tune Performance](https://help.spamfoo.com/guides/configure#performance) and [System Requirements](https://help.spamfoo.com/guides/system-requirements). ## Connect External Monitoring ### Set Up Uptime Checks Point your monitoring tool, such as Uptime Kuma, Pingdom, or Datadog, at `/health/ready`. A check that expects a `200` response tells you SpamFoo Client is running and able to classify mail. To check only that the service is running, use `/health/live`. A load balancer can use `/health/ready` to stop sending mail to SpamFoo Client. In standalone mode, when SpamFoo Client is asked to stop, `/health/ready` answers `503` with `shutting_down` for a few seconds before it closes. SpamFoo Client listens only on the server itself unless you changed it. Either run the monitoring agent on the same server and check `http://localhost:16253/health/ready`, or let a reverse proxy pass on just the `/health` paths. ### Use Docker Health Checks The SpamFoo Client Docker image already includes a health check that calls `/health/live`, so `docker ps` shows whether the container is healthy. To use your own timing, or to wait until SpamFoo Client is ready, add a `healthcheck` to the service in Docker Compose: ``` healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost:16253/health/ready"] interval: 30s timeout: 5s retries: 3 ``` ## Watch Live Metrics The Memory, CPU, and Scan Queue tiles on **System > Status** update every few seconds. A green dot next to the tile name means the live feed is connected. To read the same feed from a script, connect to `/admin/api/system/metrics/live`. It needs the proxy secret in the `X-SpamFoo-Proxy-Auth` header. See [Sign In Without SmarterMail](https://help.spamfoo.com/guides/roles#sign-in-without-smartermail) for where to find the secret. If you changed the port in standalone mode, use that port in place of 16253 in the examples on this page. ``` curl -N http://localhost:16253/admin/api/system/metrics/live \ -H "X-SpamFoo-Proxy-Auth: YOUR_ACCESS_SECRET" ``` Every 5 seconds it sends a `metrics` event with these fields: `cpuPercent`, `memoryMb`, `gcMemoryMb`, `queueSize`, `globalApiConnected`, and `timestamp`. *** Source: https://help.spamfoo.com/guides/updates Last updated: 2026-10-27 # Update and Back Up SpamFoo Client updates its models and its own program on its own. In Docker, it updates its models, and you update the program by pulling a new image. See [Update Docker](https://help.spamfoo.com/guides/updates#update-docker). This page explains how updates work, how to back up SpamFoo Client, and how to move it to a new server. ## Model Updates SpamFoo Client checks for new models about every 6 hours. You can change how often with `SPAMFOO_UPDATE_INTERVAL_HOURS` (see [Configure SpamFoo Client](https://help.spamfoo.com/guides/configure#env-updates)). When a new model is available, SpamFoo Client does the following: - Downloads the model. - Checks the file against its SHA-256 hash and SpamFoo's digital signature. A file that fails either check is not used. - Pauses scanning briefly to load the new model, while new messages wait in the queue. If that doesn't work, it finishes the messages in progress and restarts with the new model. - Keeps the current and the previous version of each model, and deletes older ones. The **Versions** card on **System > Status** shows the version of the client and of each model. To check for new models right away: 1. Go to **System > Status**. 2. In the **Maintenance** card, click **Run** next to **Update Models**. 3. Confirm. The result appears under the action when it finishes. ## Program Updates SpamFoo Client checks for a new version of itself at the same time it checks for models. When a new version is available, SpamFoo Client does the following: - Downloads the new version and checks it against its SHA-256 hash and SpamFoo's digital signature. - Waits for the messages it's scanning to finish, then stops. - Runs an update script that replaces the program files. Your data, settings, and models are not touched. - Keeps copies of the two previous versions in the `updates/backup` folder of the install folder. - If the same version fails to install three times, stops trying that version until a newer one is released. SpamFoo Client does not start itself again after an update. What happens next depends on how you run it: | How You Run It | After an Update | | --- | --- | | SmarterMail | SmarterMail starts SpamFoo Client again. | | Linux archive with a systemd service | systemd starts SpamFoo Client again, if the service file has `Restart=always` and `KillMode=process` as shown in [Install From the Archive](https://help.spamfoo.com/guides/install#tarball). | | Windows, or started by hand | Start SpamFoo Client again yourself. | | Docker | Pull a new image instead. See [Update Docker](https://help.spamfoo.com/guides/updates#update-docker). | The **Versions** card on **System > Status** shows which version of the client is running. ### Update the Program Now In Docker, **Update Client** only shows whether a newer version is available. To update, see [Update Docker](https://help.spamfoo.com/guides/updates#update-docker). With the Linux package, or when program updates are turned off, **Update Client** is grayed out and explains how to update instead. 1. Go to **System > Status**. 2. In the **Maintenance** card, click **Run** next to **Update Client**. 3. Confirm. ### Turn Off Automatic Program Updates Set the environment variable `SPAMFOO_DISABLE_BINARY_UPDATE=true` and restart SpamFoo Client. With the Linux package, add the line to `/etc/spamfoo/spamfoo.conf`. Model updates keep running. For an archive install: 1. Stop SpamFoo Client. 2. Download the new archive from the [Downloads](https://www.spamfoo.com/account/downloads) page in your SpamFoo account, and extract it over the install folder. 3. Start SpamFoo Client. > **Note** If you leave SpamFoo Client without updates for a long time, it may stop getting new models, and some features may stop working. ### Update Docker In Docker, SpamFoo Client never replaces its own program. To update it, start a new container from a newer image. Models, settings, history, and the proxy secret carry over because they're stored in the `/data` volume. #### If You Use docker run 1. Pull the new image. Use `latest`, or the version tag you want: ``` docker pull spamfoo/spamfoo-client:latest ``` 2. Stop and remove the old container. Use your container's name in place of `spamfoo-client`: ``` docker stop spamfoo-client docker rm spamfoo-client ``` 3. Run the same `docker run` command you used to install it, with the same `/data` volume and environment variables. If you use a version tag, change it to the new one. See [Install SpamFoo Client](https://help.spamfoo.com/guides/install). #### If You Use Docker Compose 1. If your compose file uses a version tag, such as `image: spamfoo/spamfoo-client:1.2.3`, change it to the new version. Skip this step if it uses `latest`. 2. In the folder with your compose file, pull the new image and recreate the container: ``` docker compose pull spamfoo-client docker compose up -d spamfoo-client ``` Use the service name from your compose file in place of `spamfoo-client`. Docker Compose keeps the same volume and environment variables, so nothing else changes. Besides `latest` and exact versions such as `1.2.3`, each release is also tagged with its major and minor version, such as `1` and `1.2`. Those tags move to the newest release in that line when you pull. See [Pick an Image Tag](https://help.spamfoo.com/guides/install#docker-tags). In standalone mode, the **Update Client** row in the **Maintenance** card on **System > Status** tells you when a newer version is available. > **Note** Older images kept models in `/data/models`. Newer images keep them in `/data/trained-models`, so the first start after the update downloads the models again. You can delete `/data/models` afterward. Old images stay on disk after an update. To remove them, run `docker image prune`. ## Database Maintenance SpamFoo Client looks after its own database: - Old scan records and totals are deleted once a day, and each time SpamFoo Client starts, based on the [retention settings](https://help.spamfoo.com/api/settings#scan-history). - Nightly backups run on their own when turned on. See [Back Up SpamFoo Client](https://help.spamfoo.com/guides/updates#backup). The **Storage** card on **System > Status** shows the database size, how many messages are stored, the date of the oldest record, and where each folder is. To use less space, see [Disk Space](https://help.spamfoo.com/guides/system-requirements#disk-space). ## Back Up SpamFoo Client ### Nightly Backups The **Database Backups** card on **System > Settings** controls the built-in backups. By default they're on, run at 2 AM server time, and keep 7 nights. Each night, SpamFoo Client creates a folder named for the date, such as `backups/2026-10-05`, in the data directory. It holds: - `admin.db`: settings, scan history, and totals - `user_settings.db`: personalization settings - `correction_archive.db`: corrections reported from this server - `domain_age.db`: saved domain ages - `personalization/`: each user's learned preferences A backup that doesn't finish is never saved as a finished one. A failed backup is written to the log and does not affect mail. The backups are on the same disk as the live data. Copy the newest backup folder to another machine or storage location as part of your normal server backups. On SmarterMail, some personalization files are stored in each user's mail folder. SmarterMail's own backups cover those files. ### What to Back Up - **The newest folder in `backups/`**. - **The `etc/` folder.** Nightly backups don't include it. It holds the license key, the proxy secret, the release channel, and the `keys` folder that protects stored passwords. Without `etc/keys`, stored passwords can't be read after a restore. - **The settings file**, if you use one: `/etc/spamfoo/spamfoo.conf` with the Linux package, or `spamfoo.conf` in the data directory. You don't need to back up `trained-models/`, `runtime/`, or `logs/`. Models and NVIDIA libraries are downloaded again when needed. > **Warning** Do not copy `admin.db` or other database files while SpamFoo Client is running. A copy made during a write can be damaged. Use the files in the newest `backups/` folder, or stop SpamFoo Client first. > **Important** The `etc/` folder holds secrets. Anyone with the license key or proxy secret can act as your server. Limit who can read your backups, and encrypt them where they're stored. ## Restore or Move to a New Server 1. Install SpamFoo Client on the new server. See [Install SpamFoo Client](https://help.spamfoo.com/guides/install). 2. Stop SpamFoo Client on the new server. 3. Copy the `etc/` folder from the old server into the new data directory, and copy the settings file if you use one. 4. Copy the files from the newest backup folder into the new data directory, as shown in the table below. 5. Start SpamFoo Client. | From the Backup Folder | To the Data Directory | | --- | --- | | `admin.db` | `admin.db` | | `domain_age.db` | `domain_age.db` | | `user_settings.db` | `personalization/user_settings.db` | | `correction_archive.db` | `correction_archive/correction_archive.db` | | `personalization/` | `personalization/` | On Linux, give the copied files to the account that runs SpamFoo Client before you start it. With the Linux package, that's `sudo chown -R spamfoo:spamfoo /var/lib/spamfoo`. SpamFoo Client downloads the latest models when it starts. Personalization data is stored only on your server. Without a backup, it can't be recovered. Users rebuild it over time as they correct their mail. ## Log Rotation SpamFoo Client starts new log files and deletes old ones on its own. See [Find the Log Files](https://help.spamfoo.com/guides/monitoring#log-files). You don't need a separate log rotation tool such as `logrotate`, and one may conflict with SpamFoo Client's own rotation. *** Source: https://help.spamfoo.com/guides/gpu-acceleration Last updated: 2026-10-27 # Turn On GPU Acceleration SpamFoo Client runs on the server's processor (CPU) by default, and for most servers that is all it needs. If the server has an NVIDIA graphics card (GPU), you can have SpamFoo Client use it to scan mail faster. - The GPU runs the model that reads message text, which is the largest part of each scan. - The other models stay on the CPU. - You only install the NVIDIA driver. SpamFoo Client downloads everything else it needs, directly from NVIDIA, and checks it before use. ## Requirements - An NVIDIA GPU that supports CUDA 13. CUDA is NVIDIA's software for running programs on its GPUs. Examples include the GeForce RTX 30 and 40 series and the NVIDIA T4. Older GPUs, such as the GeForce GTX 10 series, and other GPU brands are not supported. - An NVIDIA driver that supports CUDA 13. - Windows or Linux on an x64 processor. GPU acceleration is not available in the Docker image. - Outbound HTTPS (port 443) access to `developer.download.nvidia.com` for the NVIDIA libraries. If the server can't reach it, see [Servers That Can't Reach NVIDIA](https://help.spamfoo.com/guides/gpu-acceleration#offline-servers). - Outbound HTTPS access to `downloads.spamfoo.com` for the GPU build of SpamFoo Client. > **Note** GPU acceleration uses more memory and disk space. Watch memory use after you turn it on, especially on servers that are close to their limit. ## Turn On GPU Acceleration 1. Install the NVIDIA driver for your GPU from [nvidia.com/drivers](https://www.nvidia.com/drivers). Restart the server if the installer asks you to. 2. Open the admin console and go to **System > Settings**. 3. In the **Hardware** card, turn on **GPU Acceleration**. 4. Click **Save Changes**. 5. If a box asks **Enable GPU acceleration?**, click **Enable and download**. SpamFoo Client does the rest: - **If it's running the standard build:** it moves to the GPU release channel and installs the GPU build, the same way it installs a program update. It stops while the new files are put in place. See [Update and Back Up](https://help.spamfoo.com/guides/updates#client-updates) for how it starts again. - **On the GPU build:** it downloads NVIDIA's CUDA 13 and cuDNN 9 libraries, checks them against NVIDIA's published SHA-256 hashes, and saves them in the `runtime` folder of the data directory. A progress bar appears under the **GPU Acceleration** switch while this happens. - **During the download:** scanning continues on the CPU, so mail is not held up. - **When the download finishes:** scanning moves to the GPU. No restart is needed. To check that the GPU is in use, go to **System > Status**. The status line at the top says **GPU active**. If it says **GPU present, not in use**, the switch is off, the NVIDIA libraries are still downloading, no message has been scanned since the download finished, or the card is too old to support CUDA 13. In that last case, SpamFoo Client keeps scanning on the processor. Download messages also appear in the system log. See [Monitor Logs and Health](https://help.spamfoo.com/guides/monitoring). ## Fix a Switch You Can't Turn On ### The Switch Says No GPU Was Detected The message under the switch says **No compatible NVIDIA GPU was detected on this server**. **Cause:** SpamFoo Client did not find an NVIDIA GPU, or the NVIDIA driver is not working. **Fix:** Check that the card is installed and the NVIDIA driver is installed and working. Then restart SpamFoo Client. ### The Switch Is Locked by an Environment Variable The message under the switch says **GPU acceleration is controlled by the SPAMFOO_GPU environment variable and cannot be changed here**. **Cause:** The `SPAMFOO_GPU` environment variable is set on the server. **Fix:** Remove the variable and restart SpamFoo Client to control GPU acceleration from the Settings page. ## Turn Off GPU Acceleration 1. Go to **System > Settings**. 2. In the **Hardware** card, turn off **GPU Acceleration**. 3. Click **Save Changes**. SpamFoo Client goes back to the CPU starting with the next message. If turning the switch on moved it to a GPU release channel, it also goes back to the release channel it was on before. The NVIDIA libraries stay in the `runtime` folder, so turning GPU acceleration on again later is faster. ## Environment Variables Most servers only need the switch on the Settings page. These variables are for servers where you want the GPU settings fixed ahead of time, such as automated setups. #### `SPAMFOO_GPU` Set to `true` to always use the GPU, or `false` to always use the CPU. While it's set, the switch on the Settings page is locked. `true` only works when the GPU build is installed. Default: not set #### `SPAMFOO_GPU_DEVICE` Which GPU to use when the server has more than one. GPUs are numbered from 0. Default: `0` #### `SPAMFOO_GPU_AUTODOWNLOAD` Set to `false` to stop SpamFoo Client from downloading the NVIDIA libraries. GPU acceleration still works if the libraries are already installed on the server. Default: `true` #### `SPAMFOO_RUNTIME` The folder where the NVIDIA libraries are saved. Default: `/runtime` ## Servers That Can't Reach NVIDIA 1. Set `SPAMFOO_GPU_AUTODOWNLOAD=false`. 2. Install NVIDIA's CUDA 13 runtime and cuDNN 9 libraries on the server yourself. 3. Restart SpamFoo Client. When the libraries are installed for the whole system, SpamFoo Client finds and uses them without downloading anything. Moving to the GPU build still needs access to `downloads.spamfoo.com`. ## Next Steps - See the other settings in [Configure SpamFoo Client](https://help.spamfoo.com/guides/configure). - Compare speed before and after with **Average Scan Time** on the **Mail Health** page. See [Use the Admin Console](https://help.spamfoo.com/guides/admin-console). *** Source: https://help.spamfoo.com/guides/common-problems Last updated: 2026-10-27 # Fix Common Problems This page lists common problems with SpamFoo Client, what causes them, and how to fix each one. To dig deeper into a problem, see [Use Diagnostic Tools](https://help.spamfoo.com/guides/diagnostics). > **Using SmarterMail?** For problems with the SmarterMail connection, its SpamFoo settings, or what mailbox users see, go to [Troubleshoot SmarterMail](https://help.spamfoo.com/integrations/smartermail/troubleshoot). Many fixes below mention the **data directory**. This is the directory where SpamFoo Client keeps its database, logs, models, and the `etc` folder with its keys. It is the install folder unless you start SpamFoo Client with `--data-dir=`. In Docker it is `/data`, and with the Linux package it is `/var/lib/spamfoo`. To see the exact folders, open the admin console and go to **System > Status**. The **Storage** card lists each location. ## SpamFoo Client Won't Start If you can't tell why SpamFoo Client stopped, read its startup messages first. See [Read Startup Messages](https://help.spamfoo.com/guides/diagnostics#startup-messages). ### It Stops Right After It Starts **Cause:** Another program is already using port 16253. SpamFoo Client always listens on this port, so only one copy can run on a server. **Fix:** Find the program that is using the port and stop it. If it is another copy of SpamFoo Client, keep only one running. #### Windows In a Command Prompt, find the process ID (the last number on the line), then look up its name: ``` netstat -ano | findstr :16253 tasklist /fi "pid eq " ``` #### Linux ``` sudo ss -ltnp | grep 16253 ``` #### Docker Check for another container that publishes port 16253, then check the host as shown on the Linux tab. ``` docker ps --filter publish=16253 ``` **Cause:** The account that runs SpamFoo Client can't write to the data directory. SpamFoo Client creates its log folder, database, and keys there when it starts. **Fix:** Give that account permission to create and change files in the data directory. #### Windows In File Explorer, right-click the data directory, choose **Properties > Security**, and give the account **Modify** permission. #### Linux ``` sudo chown -R : ``` #### Docker The container runs as user ID 10001. A named volume sets this up for you. If you mount a host folder instead, give it to that user: ``` sudo chown -R 10001:10001 ``` ### It Stops With a Settings Error **Cause:** A startup setting has a value SpamFoo Client can't use, such as a `SPAMFOO_MODE` other than `embedded` or `standalone`, a `SPAMFOO_PORT` that isn't a port number, a `SPAMFOO_PUBLIC_URL` that doesn't start with `http://` or `https://`, or a `--conf` file that doesn't exist. SpamFoo Client prints a line that starts with `FATAL` and stops with exit code 78. The Linux package's service doesn't restart after this exit code, so it stays stopped. **Fix:** Read the `FATAL` line in the [startup messages](https://help.spamfoo.com/guides/diagnostics#startup-messages). It names the setting. Correct it in the environment, the command line, or the [settings file](https://help.spamfoo.com/guides/configure#settings-file), then start SpamFoo Client again. ## Mail Isn't Being Classified ### Messages Get No Result **Cause:** SpamFoo Client isn't running. **Fix:** Run a [health check](https://help.spamfoo.com/guides/diagnostics#health-check). If it fails, start SpamFoo Client the same way you set it up in [Install SpamFoo Client](https://help.spamfoo.com/guides/install). If it won't stay running, see [SpamFoo Client Won't Start](https://help.spamfoo.com/guides/common-problems#wont-start). **Cause:** Your mail server never sends messages to SpamFoo Client. If no new messages show up on the **Messages** page in the admin console, mail isn't reaching it. **Fix:** Check that your integration sends each message to `http://localhost:16253/classify-stream`, or to the server's address if the mail server runs on another computer. See [How Integration Works](https://help.spamfoo.com/integrations). **Cause:** The mail server runs on a different computer. By default, SpamFoo Client only accepts connections from the computer it runs on. **Fix:** Set the `SPAMFOO_BIND_ADDRESS` environment variable to an address the mail server can reach, then restart SpamFoo Client. See [Configure SpamFoo Client](https://help.spamfoo.com/guides/configure). The Docker image already listens on all addresses. **Cause:** Requests don't include the proxy secret. SpamFoo Client answers them with a 401 error and `{"error":"unauthorized"}`. See [Mode](https://help.spamfoo.com/guides/configure#mode) for which requests need it. **Fix:** Have the mail server send the proxy secret in the `X-SpamFoo-Proxy-Auth` header. The secret is the contents of `etc/sf.proxysecret` in the data directory, or the value of `SPAMFOO_PROXY_SECRET` if you set it. See [API Overview](https://help.spamfoo.com/api). **Cause:** The proxy secret changed, and the mail server still sends the old one. In standalone mode, the old secret keeps working for 15 minutes after the change, then requests get a 401 error. **Fix:** Give the mail server the new secret. See [Change the Secret](https://help.spamfoo.com/guides/roles#change-the-secret). ### Requests Wait a Long Time, Then Fail **Cause:** SpamFoo Client hasn't downloaded its models yet. It downloads them the first time it starts. Until they are ready, classification requests wait, and they fail if the models don't arrive within about 10 minutes. The download needs a license key and a connection to the SpamFoo API. **Fix:** Run `curl http://localhost:16253/health/ready` on the server. While the models are missing, the answer lists `models_loading`, and `not_activated` means there is no license key. Open the admin console. If **Mail Health** shows "No license set up" or "API disconnected", follow the steps in [Updates Fail](https://help.spamfoo.com/guides/common-problems#updates-fail). Then check the **Updates** log for download errors. See [Read the Logs](https://help.spamfoo.com/guides/diagnostics#logs). ### Requests Fail With a 403 Error **Cause:** Your license doesn't include the feature the request needs. The response contains `"error":"feature_not_licensed"` and names the features. **Fix:** Go to **System > Status** and check the **License** card to see which features your plan includes. To add a feature, change your plan in your SpamFoo account. **Cause:** The license has expired. **Fix:** Follow the steps in [Requests Fail After the License Expires](https://help.spamfoo.com/guides/common-problems#license-expired). ### Requests Fail After the License Expires **Cause:** SpamFoo Client stops classifying mail when its license has expired and the grace period has ended. Every classification request gets a 403 error with `"error":"feature_not_licensed"`. The **License** card on **System > Status** shows **License expired**. In embedded mode, SpamFoo Client keeps classifying. **Fix:** Renew the license in your SpamFoo account. If you got a new license key, add it with [`spamfoo-client activate`](https://help.spamfoo.com/guides/commands#activate). SpamFoo Client starts classifying again once it confirms the license, with no restart. **Cause:** SpamFoo Client couldn't reach SpamFoo to confirm the license. **Fix:** Allow the server to make outbound HTTPS connections (port 443) to `global-api.spamfoo.com`. SpamFoo Client checks the license again every few hours. To check right away, restart SpamFoo Client. ### Requests Fail With a 429 Error **Cause:** An IP address sent a wrong proxy secret or a bad sign-in link too many times in a short period. SpamFoo Client blocks every request from that address for some time. The response contains `"code":"auth_blocked"` and a `Retry-After` header with the seconds left. **Fix:** Find the program sending the wrong secret, often a mail server or script that still has an old one, and give it the current secret. Then wait for the block to end, or restart SpamFoo Client to clear it. If a reverse proxy runs on the same server, every request through it shares one address, so one bad client can block everyone behind the proxy. ## Too Much or Too Little Spam Start by finding the message on the **Messages** page in the admin console. When a rule, sender reputation, Anti-Phishing, or a mailbox's past corrections decided the result, a tag next to the result says so, such as **Admin block** or **Learned (spam)**. With no tag, the machine learning model decided. Click the message to see more detail. See [Look Up a Message](https://help.spamfoo.com/guides/diagnostics#messages). ### Good Mail Is Marked as Spam **Cause:** A Spam Rule marks the sender as Spam. The tag reads **Admin block** or **User rule (spam)**. **Fix:** Go to **Rules** and change or delete the rule. Users manage their own rules on **My Rules**. See [Manage Spam Rules and Senders](https://help.spamfoo.com/guides/rules-protection). **Cause:** The sender has a poor reputation. The tag reads **Reputation block**. **Fix:** On the **Messages** page, right-click the message and choose **Create rule**. Set **Action** to **Not Spam**. **Cause:** Anti-Phishing flagged the message. The tag reads **Phishing Model**, **Impersonation**, **Look-alike domain**, or **Typosquat domain**. A typosquat domain is a domain spelled almost like a well-known one. **Fix:** If the tag reads **Phishing Model**, go to **Anti-Phishing > Detection** and set **Sensitivity** to **Conservative**. For any of these tags, you can add a Spam Rule with the action **Not Spam** for a sender you trust. See [Protect Against Phishing](https://help.spamfoo.com/guides/phishing-protection). **Cause:** The mailbox's past corrections decided the result. The tag reads **Learned (spam)**. **Fix:** Have the user mark the message as Not Spam. See [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result). **Cause:** The machine learning model got it wrong. **Fix:** Right-click the message and choose **Report Misclassification**, or have the user mark it as Not Spam. See [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result) and [How Corrections Are Used](https://help.spamfoo.com/guides/corrections). ### Spam Gets Through **Cause:** A Spam Rule allows the sender. The tag reads **Admin allow** or **User rule (allow)**. **Fix:** Go to **Rules** and change or delete the rule. **Cause:** The mailbox's past corrections let the message through. The tag reads **Learned (allow)**. **Fix:** Have the user mark the message as Spam. **Cause:** The machine learning model missed it. **Fix:** Right-click the message and choose **Report Misclassification**, or have the user mark it as Spam. To block a sender right away, right-click the message and choose **Create rule** or **Create rule for IP**. **Cause:** The models are out of date because SpamFoo Client can't download updates. **Fix:** Go to **System > Status** and check the **Versions** card and the status line at the top. If it says "API disconnected", see [Updates Fail](https://help.spamfoo.com/guides/common-problems#updates-fail). ## Slow Responses ### Messages Take a Long Time to Scan **Cause:** Mail arrives faster than SpamFoo Client can scan it. The **Scan Queue** tile on **System > Status** shows messages waiting. **Fix:** Check the **CPU** tile on the same page. If the CPU has room to spare, go to **System > Settings** and raise **Max Concurrency** in the **Classification** card. This sets how many messages SpamFoo Client scans at the same time. If the CPU is already busy, give the server more resources or [turn on GPU acceleration](https://help.spamfoo.com/guides/gpu-acceleration). See [System Requirements](https://help.spamfoo.com/guides/system-requirements). **Cause:** **Enable Speedup Optimizations** is on while several messages are scanned at once. This setting lets a single scan use every CPU core, so scans running together compete for the same cores. **Fix:** Go to **System > Settings**. In the **Classification** card, lower **Max Concurrency** or turn off **Enable Speedup Optimizations**. **Cause:** SpamFoo Client just started or is loading new models. It loads its models when the first message arrives, and it pauses scanning briefly while it switches to updated models. **Fix:** Wait a few moments. A [health check](https://help.spamfoo.com/api/health#health) shows `"modelReloading": true` while new models load. ### Requests Fail With a 503 Error **Cause:** A message waited in the scan queue longer than the **Queue Timeout** setting. The response contains `"error":"Classification queue timeout"`. **Fix:** Follow the steps in [Messages Take a Long Time to Scan](https://help.spamfoo.com/guides/common-problems#slow-scans). You can also raise **Queue Timeout** in the **Classification** card on **System > Settings**. A value of 0 means messages wait as long as needed. ## Updates Fail When updates fail, SpamFoo Client keeps classifying mail with the models it already has. Details about each update check are in the **Updates** log. See [Read the Logs](https://help.spamfoo.com/guides/diagnostics#logs). ### Models and the Client Don't Update **Cause:** No license key is set up. **Mail Health** shows **No license set up**. Without a key, SpamFoo Client doesn't check for updates at all. **Fix:** Add the key with [`spamfoo-client activate`](https://help.spamfoo.com/guides/commands#activate). For other ways to add it, and when you need to restart, see [License Key](https://help.spamfoo.com/guides/configure#license-key). **Cause:** SpamFoo Client is using a different data directory than you expect, so it doesn't find your key file. **Fix:** The **License key** row in the **Storage** card on **System > Status** shows exactly where SpamFoo Client looks for the key file. Put the key there, or start SpamFoo Client with the data directory you meant to use. If you start SpamFoo Client with a custom data directory, write the option with an equals sign, such as `--data-dir=/var/spamfoo`. A space instead of the equals sign is ignored. **Cause:** Your license is past due, suspended, expired, or canceled. **Mail Health** shows an alert with the license status. **Fix:** Bring the license back to good standing in your SpamFoo account. **Cause:** SpamFoo Client can't reach the SpamFoo API. **Mail Health** and **System > Status** show **API disconnected**. **Fix:** Allow the server to make outbound HTTPS connections (port 443) to `global-api.spamfoo.com`. To check for updates right away, go to **System > Status** and click **Run** next to **Update Models** in the **Maintenance** card. ### A Client Update Keeps Failing **Cause:** The update can't replace the files in the install folder, usually because of disk space or permissions. After three failed tries on the same version, SpamFoo Client stops trying that version until a newer one is released. **Fix:** Read `updates/update.log` in the install folder to find the error. Make sure the account that runs SpamFoo Client can change files in the install folder and that the disk has free space. **Cause:** You run SpamFoo Client in Docker. The container never replaces its own program. **Update Client** only shows whether a newer version is available. Models still update on their own. **Fix:** Pull a newer image tag and recreate the container with the same `/data` volume. See [Update and Back Up](https://help.spamfoo.com/guides/updates). ### SpamFoo Client Stops After an Update **Cause:** To install a new version, SpamFoo Client closes and expects the program that manages it to start it again. A Linux service set to `Restart=on-failure` may not restart it. **Fix:** Set the service to restart whenever SpamFoo Client stops. On Linux, change the `[Service]` section of the unit file, then reload it: ``` Restart=always ``` ``` sudo systemctl daemon-reload sudo systemctl restart spamfoo-client ``` ## Database Grows Too Large The **Storage** card on **System > Status** shows the database size, how many messages are stored, and the date of the oldest record. Settings named below are on **System > Settings**. Click **Save Changes** after you change them. ### The Data Directory Uses Too Much Disk Space **Cause:** Scan history, stored messages, nightly backups, or log files have grown. **Fix:** Check the **Storage** card on **System > Status**, then lower the matching settings. See [Disk Space](https://help.spamfoo.com/guides/system-requirements#disk-space). ## Can't Open the Admin Console ### The Console Asks for a Proxy Secret **Cause:** The admin console is protected by a proxy secret. In standalone mode, SpamFoo Client shows a "Sign in to SpamFoo" page until you enter it. **Fix:** Make a one-time sign-in link on the server with [`spamfoo-client login-link`](https://help.spamfoo.com/guides/commands#login-link). Or paste the contents of `etc/sf.proxysecret` from the data directory. If you set `SPAMFOO_PROXY_SECRET`, use that value instead. #### Windows ``` type "\etc\sf.proxysecret" ``` #### Linux ``` sudo cat /etc/sf.proxysecret ``` #### Docker ``` docker exec spamfoo-client cat /data/etc/sf.proxysecret ``` **Cause:** The console shows "That secret did not work." The value you pasted is incomplete, or the secret was changed. **Fix:** Copy the whole value again, using the steps above. **Cause:** The console shows "Saved secret no longer works." An older version of SpamFoo Client saved the secret in your browser, and the secret changed since. **Fix:** Paste the current secret, using the steps above. **Cause:** You were signed in, and now the sign-in page appears again. The proxy secret changed, which signs everyone out, or your sign-in expired after 24 hours. **Fix:** Sign in again with the current secret or a new sign-in link. **Cause:** SmarterMail manages this SpamFoo Client. SmarterMail signs you in, so there is no secret for you to enter. **Fix:** Open the SpamFoo dashboard from SmarterMail. See [Troubleshoot SmarterMail](https://help.spamfoo.com/integrations/smartermail/troubleshoot). ### A Sign-In Link Can't Be Used **Cause:** The page says "This sign-in link cannot be used." The link expired, was already used, or stopped working because the proxy secret changed. **Fix:** Make a new link with `spamfoo-client login-link`. To make a link that lasts longer, add `--minutes=`. See [Make a Sign-In Link](https://help.spamfoo.com/guides/commands#login-link). **Cause:** The link points to `localhost`, so it only opens on the server itself. **Fix:** Make the link with `--url=` set to the address you use to reach SpamFoo Client, or set [`SPAMFOO_PUBLIC_URL`](https://help.spamfoo.com/guides/configure#public-url). ### The Page Won't Load **Cause:** SpamFoo Client isn't running. **Fix:** Run a [health check](https://help.spamfoo.com/guides/diagnostics#health-check) and start SpamFoo Client if needed. **Cause:** You are browsing from another computer. By default, SpamFoo Client only accepts connections from the computer it runs on. **Fix:** Open `http://localhost:16253/admin` on the server itself. To reach the console from other computers, see [Use the Admin Console](https://help.spamfoo.com/guides/admin-console). **Cause:** The console files are missing from the install folder. The startup messages include "SpamFoo Dashboard static files not found." **Fix:** Back up the data directory, then reinstall SpamFoo Client from a fresh download. See [Install SpamFoo Client](https://help.spamfoo.com/guides/install). ### Some Pages Are Missing **Cause:** You are signed in as a Domain Admin or User. Only a System Admin sees server-wide pages such as **Mail Health**, **Messages**, **Senders**, and **System**. **Fix:** Sign in with a System Admin account. See [Manage Sign-In and Roles](https://help.spamfoo.com/guides/roles). ## Still Stuck? - Use the tools in [Use Diagnostic Tools](https://help.spamfoo.com/guides/diagnostics) to find the cause. - Check the **Errors** log for warnings and errors. See [Monitor Logs and Health](https://help.spamfoo.com/guides/monitoring). *** Source: https://help.spamfoo.com/guides/diagnostics Last updated: 2026-10-27 # Use Diagnostic Tools Use these tools to check that SpamFoo Client is running and to find out why it handled a message the way it did. For fixes to known problems, see [Fix Common Problems](https://help.spamfoo.com/guides/common-problems). Some steps below mention the **data directory**. This is the directory where SpamFoo Client keeps its database, logs, and keys. It is the install folder unless you start SpamFoo Client with `--data-dir=`. It is `/data` in Docker and `/var/lib/spamfoo` with the Linux package. The **Storage** card on **System > Status** in the admin console lists the exact folders. ## Check That SpamFoo Client Is Running The health check is the quickest test. Run it on the server where SpamFoo Client is installed. ### Windows ``` curl.exe http://localhost:16253/health ``` ### Linux ``` curl http://localhost:16253/health ``` ### Docker ``` curl http://localhost:16253/health docker ps --filter name=spamfoo-client ``` The image also runs this check on its own, so `docker ps` shows the container as healthy or unhealthy. If the command can't connect, SpamFoo Client isn't running. See [Read Startup Messages](https://help.spamfoo.com/guides/diagnostics#startup-messages) to find out why. A working service answers with `"status": "healthy"`. See [Check Queue Load](https://help.spamfoo.com/api/health#health) for what the other fields mean. ## Check That It Can Classify Mail A healthy answer means SpamFoo Client is running, not that it can classify mail. To check that, use the readiness check: ``` curl http://localhost:16253/health/ready ``` A `200` means it's ready, and a `503` lists the reasons. See [Check That It Is Ready](https://help.spamfoo.com/api/health#ready). With the Linux package or Docker, `spamfoo-client status` shows the same answer and also whether a license key is set up. See [Check Readiness](https://help.spamfoo.com/guides/commands#status). ## Read Startup Messages When SpamFoo Client starts, it prints messages that don't go into the log files. They show which folders it uses, whether it found a license key, and why it stopped if it fails to start. ### Windows Stop SpamFoo Client if it is running. Then start it in a Command Prompt from the install folder and read the output. Add the same options the service uses, such as `--data-dir=`. ``` cd spamfoo-client.exe ``` Press Ctrl+C to stop it when you are done. ### Linux ``` sudo journalctl -u spamfoo-client -n 100 ``` In standalone mode, which the Linux package uses, the journal also has every log line. ### Docker ``` docker logs spamfoo-client ``` Use your container's name in place of `spamfoo-client`. In standalone mode, the container's output also has every log line. Lines worth looking for: - `FATAL:`: a setting is wrong, and SpamFoo Client stopped. The line names the setting. See [It Stops With a Settings Error](https://help.spamfoo.com/guides/common-problems#config-error). - `Configuration file:`: the settings file SpamFoo Client read, and how many settings it applied. - `LicenseKey: Not Configured`: SpamFoo Client found no license key. - `ALERT: SpamFoo API not configured. Update checking disabled.`: models and the client won't update. - `Models directory:`: the folder where SpamFoo Client looks for its models. - `SpamFoo Dashboard static files not found`: the admin console files are missing. ## Check the Status Page In the admin console, go to **System > Status**. Check that the status line says **API connected**, the **License** card shows **Active**, and the **Versions** card lists a version for each model. An empty or old model version points to an update problem. For everything on the page, see [Check System Status](https://help.spamfoo.com/guides/admin-console#system-status). The **Mail Health** page also shows alerts at the top when SpamFoo Client can't reach the SpamFoo API or the license needs attention. ## Look Up a Message The **Messages** page shows how SpamFoo Client handled each scanned message. Use it when someone asks why a message was or wasn't marked as spam. 1. In the admin console, go to **Messages**. 2. Fill in one or more filters: **Sender Domain**, **Sender IP**, **Recipient**, **Detection Type**, or **Detection Path**. 3. Click **Search**. 4. Click a message to open its details. What to look at: - **Tag next to the result:** appears when something other than the machine learning model decided, such as **Admin block**, **Reputation block**, **Learned (spam)**, or **Phishing Model**. - **Verified:** whether the sender passed email authentication checks (SPF, DKIM, and DMARC, which confirm a message really came from the domain it claims). A message from a verified sender that was marked as spam deserves a closer look. - **Processing Steps:** in the message details, each step SpamFoo Client took, marked **Pass** or **Fail** where it applies. - **Message Content:** the stored subject and body, if **Store Message Content** is on. Right-click a message to create a rule for its sender or IP address, or to report a wrong result to SpamFoo. Messages are kept for the **Retention Period** set on **System > Settings**, which is 7 days by default. ## Read the Logs Start with the **Errors** log. It has every warning and error from all the other logs in one place. Read it on **System > Logs** in the admin console, or open the files in the `logs` folder of the data directory. See [Monitor Logs and Health](https://help.spamfoo.com/guides/monitoring) for each log and how to read it. To see the last 100 lines of today's Errors log: ### Windows In PowerShell: ``` Get-Content "\logs\errors-$(Get-Date -Format yyyyMMdd).txt" -Tail 100 ``` ### Linux ``` tail -n 100 /logs/errors-$(date +%Y%m%d).txt ``` ### Docker ``` docker exec spamfoo-client sh -c 'tail -n 100 /data/logs/errors-$(date +%Y%m%d).txt' ``` To log more while you look into a problem, set **Log Level** to **Detailed**, or turn on debug codes if support asks. See [Change How Much Is Logged](https://help.spamfoo.com/guides/monitoring#log-level). *** Source: https://help.spamfoo.com/guides/personalization Last updated: 2026-10-08 # How Personalization Works Different people want different things from their inbox. A newsletter one person reads every day is junk to someone else. This page explains how SpamFoo adjusts to each user, so you can understand results and answer questions about personal data. ## The Two Parts of Personalization - **Spam Rules:** Instructions people set by hand, such as "mail from this sender is always spam". They are simple and predictable. - **Pattern memory:** What SpamFoo learns from a user's corrections. It is built for you automatically when you mark spam, rescue mail from spam, or move a message to a different classification. Both are stored on your server and apply only to the people they were made for. ## Spam Rules - A Spam Rule says mail from a sender is always **Spam** or always **Not Spam**. - A rule can match one email address, a whole domain, or a wildcard pattern. Administrators can also match IP address ranges. - Users manage their own rules in the **Spam Rules** list on the **My Rules** page. The same list shows the Domain Spam Rules and System Spam Rules that apply to the user, marked under **Set By**. Only administrators can change those. - Spam Rules only decide spam or not spam. They don't choose an Email Classification. - When several rules match, a rule made by a system administrator beats one made by a domain administrator, which beats one made by a user. See [When Rules Disagree](https://help.spamfoo.com/guides/my-rules#which-rule-wins). To add or change rules, see [Manage Your Rules](https://help.spamfoo.com/guides/my-rules). ## Pattern Memory Spam Rules only cover senders someone named. Pattern memory covers everything else. - When a user corrects a message, SpamFoo stores a fingerprint of it with the correct answer. A fingerprint is a list of numbers that captures what the message is about. The message's words can't be read back from it. - When new mail arrives for that user, SpamFoo compares it to the stored fingerprints. If it closely matches messages the user already corrected, SpamFoo follows those corrections. - Pattern memory updates immediately, so a correction applies to the next similar message in that mailbox. ### Spam Memory - One correction is enough for SpamFoo to handle similar mail from the same sender the same way. - Mail from other senders is only affected when it is nearly identical to messages the user corrected. Even then, the spam models usually still have a say. ### Classification Memory - Moving a message to Primary, Transactions, Updates, or Promotions teaches SpamFoo where similar mail from that sender belongs, and very similar mail from other senders can follow. - Other kinds of mail from the same sender aren't affected. Moving an Amazon newsletter to Updates doesn't move Amazon receipts. - When a message marked as spam is reported as a classification in the SpamFoo dashboard, spam memory also learns that it isn't spam. ### How Memory Stays Current - Each user's memory holds a limited number of corrections. When it is full, the oldest are dropped, so the memory reflects recent preferences. - If a user later corrects a message the opposite way, older memories that disagree lose weight and are eventually removed. ## Where Personalization Fits - A user's own Spam Rules and Domain Spam Rules are checked first and decide right away. - Pattern memory is checked after the phishing checks and before sender reputation and the spam models. - System Spam Rules are applied last. A System Spam Rule that says Spam always wins. One that says Not Spam gives way when the user's pattern memory for that same sender decided the message. For the full order, see [How SpamFoo Classifies Email](https://help.spamfoo.com/guides/how-classification-works). ## See and Clear What SpamFoo Learned - The **My Rules** page lists what SpamFoo learned under **Trained Rules**. You can remove any item so SpamFoo stops using it. - **Clear All Personalization Data** on the same page removes everything SpamFoo learned for the user. It doesn't remove the user's Spam Rules or message history. - Developers can clear a user's data through the API. See [Manage Personal Data](https://help.spamfoo.com/api/personal-data). ## Where the Data Is Stored - **Pattern memory** is stored in the `personalization` folder of the SpamFoo data directory. When SmarterMail runs SpamFoo, it is stored in a `spamfoo` folder inside each user's SmarterMail folder instead. - **Spam Rules** are stored in SpamFoo's local database, `admin.db`, in the data directory. - **Corrected messages:** SpamFoo also keeps a copy of a limited number of recently corrected messages in the `correction_archive` folder of the data directory. When the limit is reached, the oldest copies are removed. Clearing personalization data doesn't remove these copies. None of this is sent to SpamFoo's servers as part of personalization. To keep it across a reinstall, back up the data directory. See [Update and Back Up](https://help.spamfoo.com/guides/updates). *** Source: https://help.spamfoo.com/guides/corrections Last updated: 2026-10-08 # How Corrections Are Used A correction is what happens when someone tells SpamFoo it got a message wrong. This page explains what SpamFoo does with a correction on your server and what it sends to SpamFoo's servers. ## Kinds of Corrections - **Spam corrections:** Marking a message as spam or phishing, or rescuing a message from spam. Phishing can only be reported from the SpamFoo dashboard. See [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result). - **Classification corrections:** Moving a message to a different Email Classification, such as from Primary to Updates. See [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result). ## How Corrections Reach SpamFoo - **SmarterMail:** When users mark spam, rescue mail, or move messages between classifications, SmarterMail sends the correction to SpamFoo for them. See [SmarterMail Overview](https://help.spamfoo.com/integrations/smartermail). - **Other mail servers and custom software:** The software must send each correction to SpamFoo Client. See [Send Corrections Back](https://help.spamfoo.com/integrations/send-corrections). - **The SpamFoo dashboard:** You can report a message from its details. In the dashboard, a correction is called a report. ## What Happens on Your Server - SpamFoo adds the correction to that user's pattern memory, and it takes effect immediately. - The correction only affects the user who made it. - This works even when SpamFoo's online services can't be reached. See [How Personalization Works](https://help.spamfoo.com/guides/personalization) for how pattern memory decides future mail. ## What Is Sent to SpamFoo Corrections also help train the models that ship to every SpamFoo installation. SpamFoo only reports a correction when its own models got the message wrong. It doesn't report one when: - The user's own past corrections already decided the message. - The message was already classified the way the user chose. A **Phishing** report from the SpamFoo dashboard is sent even when SpamFoo's models already marked the message as spam, because it tells SpamFoo something new. ### Sent by Default By default, a reported correction contains mathematical data about the message, not the message itself: - A fingerprint of the message: a list of numbers that captures what it is about. The words can't be read back from it. - Signals SpamFoo measured, such as sender check results, link counts, and whether there were attachments. - What SpamFoo decided, what the user chose instead, whether the user reported it as phishing, and how sure the models were. - The sender's domain and the IP address of the server that sent the message. - A hashed form of the mailbox address that made the correction. Hashing turns the address into a fixed code instead of readable text. The subject, the message text, and the sender's full address aren't included. ### Sharing Message Content An administrator or the owner of the mailbox can choose to also share a sanitized copy of the message text with corrections. This helps train the models better than the mathematical data alone. - **Off by default.** Nothing beyond the mathematical data is sent until someone turns it on. - **Corrections from SmarterMail or other software:** The mail server decides, for each correction, whether the user agreed to share content. If it doesn't say, SpamFoo treats the answer as no. - **Reports from the SpamFoo dashboard:** A system administrator controls this for the whole server with **Share Correction Content** in the **Message Content** card of **System > Settings**. It only works while **Store Message Content** is on. The first time a system administrator reports a message, SpamFoo asks whether to share. - **Sanitizing:** Before sending, SpamFoo replaces email addresses, phone numbers, IP addresses, Social Security numbers, credit card numbers, and common names with placeholders, and scrambles other numbers. Unusual details, such as a name SpamFoo doesn't recognize, may still get through. > **Warning** Do not turn on content sharing for users who have not agreed to it. ### Automatic Training Reports An administrator can also turn on automatic training reports when starting SpamFoo Client. SmarterMail turns them on when its own setting for reporting to SpamFoo is on. - SpamFoo then sends the same kind of mathematical data for some messages it was unsure about, and for a small random sample of ordinary mail. - These reports never include message text. - They are off unless turned on. ## When SpamFoo's Servers Can't Be Reached - The correction still takes effect on your server right away. - The report waits in a queue on your server and is retried for a while. If it still can't be delivered, it is dropped. - Mail delivery isn't affected. ## What to Expect - **Spam Rules** are exact. A rule for a sender always applies, unless a stronger rule from an administrator matches. See [Manage Your Rules](https://help.spamfoo.com/guides/my-rules). - **Pattern memory** works by similarity. One correction is often enough for the same sender. For mail from many different senders, several corrections of similar messages work better than one. - **Model updates** improve results for every user, including users who never send corrections. They arrive with regular model updates. See [Update and Back Up](https://help.spamfoo.com/guides/updates). *** Source: https://help.spamfoo.com/integrations Last updated: 2026-10-27 # How Integration Works This page explains how a mail server or your own application hands mail to SpamFoo Client and acts on the result, and how to pick an integration method. ## Understand the Local Service SpamFoo Client (`spamfoo-client`) runs as an HTTP service on the same machine as your mail server, or next to it in a Docker container. Every integration talks to it over HTTP on port `16253`. - **Local decisions:** SpamFoo Client classifies each message with models that run on your server. It may ask SpamFoo's online services about a sender's domain reputation to improve the result, but it doesn't require it. Classification keeps working when the internet connection is down. - **Raw message in, JSON out:** you send the raw message (the full `.eml` content, headers and body exactly as received) and get a JSON result back. - **Localhost by default:** the service listens on `localhost` only. Callers on other machines need extra setup, described in [Start With the Local API](https://help.spamfoo.com/integrations/local-api#authenticate). - **A shared secret:** callers send the proxy secret with their requests. See [Authenticate Requests](https://help.spamfoo.com/api#authentication). ## Follow a Message Through 1. A message arrives at your mail server over SMTP. 2. Your mail server posts the raw message to `POST /classify-stream`, along with optional headers that describe the delivery, such as the connecting IP address and the recipient. 3. SpamFoo Client checks allow and block rules, the recipient's personalization, and its local models. 4. SpamFoo Client returns a JSON result with a spam verdict (`isSpam`) and an Email Classification (`classification`: Primary, Transactions, Updates, or Promotions). 5. Your mail server delivers the message to the inbox, files it under a classification, or moves it to the junk folder. 6. When the recipient later corrects a result (for example, marks a message as not spam), your mail server sends the correction back so SpamFoo Client learns that user's preferences. ## Plan for a Busy or Stopped Service SpamFoo Client classifies a limited number of messages at a time and queues the rest. If a request waits too long, or too many requests are already in progress, it returns `503 Service Unavailable`. - Treat a `503`, a timeout, or a refused connection as "no verdict" and deliver the message without classification. - To check whether SpamFoo Client is ready before you send mail, call `GET /health/ready`. See [Check Health](https://help.spamfoo.com/api/health#ready). - To tune how many messages run at once, see [Tune Performance](https://help.spamfoo.com/guides/configure#performance). ## Choose an Integration Method All methods use the same local API. The difference is who writes the calls. | Method | Use It When | Where to Start | | --- | --- | --- | | **SmarterMail** | You run SmarterMail. SpamFoo is built in, including classification, user corrections, and the dashboard. | [SmarterMail Overview](https://help.spamfoo.com/integrations/smartermail) | | **Local API** | You are connecting another mail server, a mail filter, or your own application. | [Start With the Local API](https://help.spamfoo.com/integrations/local-api) | | **Command line** | You want to test SpamFoo Client, run scripts, or classify a folder of saved messages. | [Use the Command Line](https://help.spamfoo.com/integrations/command-line) | If another product already handles spam and you only want Email Classifications, see [Use Classification Only](https://help.spamfoo.com/integrations/classification-only). ## Build Your Own Integration A complete custom integration does three things: - **Classify** each incoming message with `POST /classify-stream` and act on the result. See [Start With the Local API](https://help.spamfoo.com/integrations/local-api). - **Identify the recipient** with the `X-User-ID` header, so SpamFoo Client can personalize results for each mailbox. - **Send corrections** when a user marks spam, rescues a message from junk, or moves a message to another classification. See [Send Corrections Back](https://help.spamfoo.com/integrations/send-corrections). The [API Reference](https://help.spamfoo.com/api) lists every endpoint, header, and response field. ## After You Connect - Open the [admin console](https://help.spamfoo.com/guides/admin-console) at `http://localhost:16253/admin/` and confirm that messages appear as they are classified. - Set up [log and health monitoring](https://help.spamfoo.com/guides/monitoring) so you notice if the service stops. - Read [How Corrections Are Used](https://help.spamfoo.com/guides/corrections) to understand what happens to the feedback your integration sends. *** Source: https://help.spamfoo.com/integrations/smartermail Last updated: 2026-10-27 # SmarterMail Overview SmarterMail has SpamFoo built in. SmarterMail downloads, licenses, runs, and updates SpamFoo Client for you, so there is nothing separate to install. This page explains what the integration does and where to go next. > **Using SmarterMail?** Skip the SpamFoo Client install and configuration guides. SmarterMail runs `spamfoo-client` itself and manages its files, license, and updates. To run SpamFoo Client on its own server instead, see [Use SpamFoo Client on Another Server](https://help.spamfoo.com/integrations/smartermail/setup#external-client). ## What the Integration Does - **Spam checking:** SpamFoo scans each incoming message once per recipient and returns a spam verdict. SmarterMail turns that verdict into a spam weight and adds it to the weights from its other spam checks. - **Email Classifications:** SpamFoo sorts each user's Inbox into Primary, Transactions, Updates, and Promotions. Users see these Classifications in webmail. See [The Four Email Classifications](https://help.spamfoo.com/guides/how-classification-works#classifications). - **Learning from corrections:** When users report spam, report not spam, or move a message to a different Classification, SmarterMail passes the correction to SpamFoo. SpamFoo uses it to personalize results for that mailbox. Spam and not spam reports are passed on only while correction sharing is on. See [How Personalization Works](https://help.spamfoo.com/guides/personalization). - **The SpamFoo dashboard:** System administrators, domain administrators, and users can open the SpamFoo dashboard from inside SmarterMail. Each sees the view that matches their role. Spam checking and Email Classifications are turned on separately. You can use either one or both. With only Email Classifications on, SpamFoo adds no spam weight and SmarterMail's other spam checks decide what is spam. ## What SmarterMail Handles for You - **Download:** SmarterMail downloads the SpamFoo Client package for your operating system from `global-api.spamfoo.com` and checks its SHA-256 hash before installing it. - **Install:** SmarterMail places the client in a `SpamFoo` folder inside the SmarterMail program folder and creates the folders and files the client needs. - **License:** SmarterMail uses the SpamFoo add-on on your SmarterMail license when you have one. Otherwise it requests a SpamFoo license for the server automatically. - **Run:** SmarterMail starts `spamfoo-client` when spam checking or Email Classifications are turned on, watches its health, and stops it when neither is in use. - **Updates:** SmarterMail checks for a newer client before it starts SpamFoo. The running client also downloads new models and client versions on its own schedule. ## What You Need - A SmarterMail system administrator account. - Outbound HTTPS from the mail server to `global-api.spamfoo.com` and `services.smartertools.com`. SmarterMail uses these to download the client, check for updates, and get and verify the SpamFoo license. - Enough memory and CPU on the mail server for SpamFoo Client. See [System Requirements](https://help.spamfoo.com/guides/system-requirements). ## SmarterMail Pages - [Set Up SmarterMail](https://help.spamfoo.com/integrations/smartermail/setup): Turn on spam checking and Email Classifications, and watch mailboxes get classified. - [What SmarterMail Users See](https://help.spamfoo.com/integrations/smartermail/users): The Classifications, menus, and settings users get in webmail. You can share this page with your users. - [Troubleshoot SmarterMail](https://help.spamfoo.com/integrations/smartermail/troubleshoot): Causes and fixes for common problems. - [How SmarterMail Uses SpamFoo](https://help.spamfoo.com/integrations/smartermail/how-it-works): How SmarterMail runs the client, how SpamFoo affects the spam score, and where the files live. *** Source: https://help.spamfoo.com/integrations/smartermail/setup Last updated: 2026-10-27 # Set Up SmarterMail This page shows how to turn on SpamFoo spam checking and Email Classifications in SmarterMail, and how to follow the progress as mailboxes are classified. ## Before You Start - Sign in to SmarterMail as a system administrator. - Go to **Settings > Licensing** and find the **SpamFoo Antispam** and **SpamFoo Classification** cards. Spam checking needs SpamFoo Antispam. Email Classifications need SpamFoo Classification. - Make sure the SpamFoo service is running. Go to **Manage > Troubleshooting > Services** and check the **SpamFoo** row. It runs by default. If someone stopped it, select it and click **Start**. ## Turn On Spam Checking 1. Go to **Settings > Antispam**. 2. Open the **Spam Checks** tab. 3. Click the **SpamFoo** row. 4. Turn on **Enable Spool Filtering**. 5. Set **Spam Weight**. SmarterMail adds this weight when SpamFoo says a message is spam. The default is 20. 6. Set **Not Spam Weight**. SmarterMail adds this weight when SpamFoo says a message is not spam. The default is 0, and a negative number lowers the total. 7. Click **Save**. SmarterMail adds the SpamFoo weight to the weights from its other spam checks, then applies your spam filtering actions to the total. Pick a Spam Weight that, together with your other checks, reaches the spam level you want SpamFoo to trigger. SmarterMail starts SpamFoo Client when you save. The first time, it also downloads the client and the client downloads its models, so the first messages can arrive before SpamFoo is ready. Those messages are delivered without a SpamFoo result. ## Turn On Email Classifications Email Classifications are controlled in two places: - **The domain's mode** decides whether the domain uses Classifications and whether users are asked first. - **Each mailbox's Email Classification switch** decides whether that mailbox is classified. It is on by default. ### Choose a Mode for a Domain 1. Go to **Manage > Domains** and click the domain. 2. Open the **Options** tab. 3. In the **Email Classification** card, choose a mode from the **Email Classification** list. 4. Set **Automatic Classification Inbox Message Limit**. Mailboxes whose Inbox holds more messages than this are not classified automatically. The default is 20,000. 5. Click **Save**. | Mode | What Happens | | --- | --- | | **Disabled** | No mailbox in the domain is classified. Changing a domain to Disabled stops any classification in progress for it. | | **Enabled for All Mailboxes** | Every mailbox with its Email Classification switch on is queued for classification. Mailboxes over the Inbox message limit are skipped, and when SmarterMail queues the domain it turns their switch off. | | **Enabled and Offered to Users** | Each user is asked once, when they sign in to webmail, whether to turn on Classifications. Only mailboxes of users who accept are queued. If you switch from Enabled for All Mailboxes, mailboxes that already have Classifications on stay on and their users are not asked. | ### Set the Default for New Domains 1. Go to **Manage > Domain Defaults**. 2. In the **Email Classification** card, choose the mode and set **Automatic Classification Inbox Message Limit**. 3. Click **Save**. 4. To apply the same settings to existing domains, click **Propagate**, select the Email Classification settings under **Settings to Propagate**, and click **Propagate**. ### Turn Classifications On or Off for One Mailbox 1. Go to **Manage > Domains** and click the domain. 2. Open the **Accounts** tab and click the user. 3. In the **Webmail** card, turn **Email Classification** on or off. 4. Click **Save**. When the domain uses **Enabled and Offered to Users**, changing this switch answers the offer for the user, and they are not asked at sign-in. ### Change the Default for a Domain's Mailboxes 1. Go to **Manage > Domains** and click the domain. 2. Open the **User Defaults** tab. 3. In the **Webmail** card, turn **Email Classification** on or off. 4. Click **Save**. 5. To apply the change to existing users, click **Propagate**, select **Email Classification** under **Settings to Propagate**, choose **All Users** or **Specific Users** under **Push to**, and click **Propagate**. ## Watch Classification Progress When a mailbox is turned on, SmarterMail queues it and classifies the messages already in its Inbox. New mail is classified as it arrives. Users see the Classifications in webmail only after their mailbox finishes. 1. Go to **Manage > Troubleshooting**. 2. Open the **Mailbox Processing** tab. 3. Find the rows with the **Classification** type. - **Status** shows **Queued**, **Message Processing**, or **Completed**. - **Progress** shows how many Inbox messages are done out of the total. - SmarterMail processes the mailboxes with the smallest Inbox first, so small mailboxes finish quickly even when large ones are waiting. - SmarterMail also checks once a day for mailboxes that have Classifications on and are within the Inbox message limit, and queues them. ## Reclassify a Mailbox Reclassify clears the Classification of every message in a mailbox's Inbox and queues the mailbox again, even if its Inbox is over the Inbox message limit. Only mailboxes with Email Classifications on are processed. 1. Go to **Manage > Domains** and click the domain. 2. Open the **Accounts** tab. 3. Select the users. 4. Open the **Actions** menu (three dots) and click **Reclassify**. 5. Click **Reclassify** to confirm. Reclassifying a large Inbox can take a long time. Users don't see their Classifications until it finishes. ## Use Classification Only To keep another antispam product for spam detection and use SpamFoo only for Email Classifications: 1. Leave **Enable Spool Filtering** off for SpamFoo in **Settings > Antispam > Spam Checks**. 2. Turn on Email Classifications for your domains as shown above. In this mode SmarterMail asks SpamFoo only for a Classification. SpamFoo adds no spam weight, and your other spam checks work as before. SmarterMail also runs this way when your license includes SpamFoo Classification but not SpamFoo Antispam. In that case the SpamFoo row in **Spam Checks** shows **(Classification Only)**. ## Control Correction Sharing The **Send classification and spam corrections to SpamFoo** switch is in **Settings > Antispam > Options**. It is on by default. - **On:** SmarterMail sends users' spam and not spam reports to SpamFoo as corrections, users can choose to share a sanitized copy of the message text, and SpamFoo Client may send automatic training reports. - **Off:** Spam and not spam reports are not sent to SpamFoo Client, so SpamFoo doesn't learn from them. No message text is shared, and users can't turn on sharing. Classification changes still personalize each user's mailbox. Changing this switch restarts SpamFoo Client, if it's running, so the new setting takes effect. See [How Corrections Are Used](https://help.spamfoo.com/guides/corrections). ## Use SpamFoo Client on Another Server By default, SmarterMail runs SpamFoo Client on the mail server itself. You can instead connect SmarterMail to a SpamFoo Client you installed on another server with the Linux package or Docker. 1. Install SpamFoo Client on the other server and add its license key. See [Install SpamFoo Client](https://help.spamfoo.com/guides/install). 2. Let the mail server reach it. See [Accept Mail From Other Servers](https://help.spamfoo.com/guides/install#docker-remote) for Docker. 3. On the other server, print its connection string with the address SmarterMail uses to reach it: ``` sudo spamfoo-client connection-string --url=http://spamfoo.example.com:16253 ``` You can also copy it from the **Connection string** card on **System > Status** in that server's dashboard. See [Print the Connection String](https://help.spamfoo.com/guides/commands#connection-string). 4. In SmarterMail, paste the value into the setup card for SpamFoo. SpamFoo Client on the other server runs on its own. You install updates and manage its license key there, as described in [Update and Back Up](https://help.spamfoo.com/guides/updates). > **Warning** The connection string contains the proxy secret. Treat it like a password. ## Next Steps - Tell users what to expect with [What SmarterMail Users See](https://help.spamfoo.com/integrations/smartermail/users). - Open the SpamFoo dashboard from the **SpamFoo Dashboard** link at the bottom of the **Settings** menu. See [Use the Admin Console](https://help.spamfoo.com/guides/admin-console). - If something doesn't work, see [Troubleshoot SmarterMail](https://help.spamfoo.com/integrations/smartermail/troubleshoot). *** Source: https://help.spamfoo.com/integrations/smartermail/users Last updated: 2026-10-27 # What SmarterMail Users See If your mail server uses SmarterMail with SpamFoo turned on, SpamFoo works inside SmarterMail webmail. This page shows what you'll see and what you can do. Some of these features appear only if your administrator has turned them on. ## Turn On Email Classifications Email Classifications sort your Inbox into four groups so the mail that matters is easy to find. Your administrator decides how they are turned on: - **Already on:** Your administrator turned them on for everyone. You don't need to do anything. - **Offered to you:** When you sign in, SmarterMail asks whether you want to use Email Classifications. Click **Enable Now** to turn them on, or **Later** to skip. If you were offered Email Classifications, you can change your answer at any time: 1. Go to **Settings > Account**. 2. In the **Webmail** card, turn **Classify My Inbox** on or off. 3. Click **Save**. After you turn them on, SmarterMail sorts the messages already in your Inbox. This can take a while for a large Inbox. The Classifications appear in webmail when it's done. New mail is sorted as it arrives. ## Find Your Classifications In **Email**, the Classifications are listed above your folders. Each one shows how many messages it holds. Click a Classification to see only those Inbox messages. | Classification | What Goes There | | --- | --- | | **Primary** | Important mail you should see first, such as messages from real people, sign-in codes, and urgent account alerts. | | **Transactions** | Receipts, orders, shipping notices, and delivery confirmations. | | **Updates** | News, newsletters, community and forum digests, and social media alerts. | | **Promotions** | Coupons, sales, and other offers from companies. | SpamFoo picks a classification for each message, not for each sender. One company can send many kinds of mail. For example, Amazon might send you: - A sign-in code (Primary) - A receipt (Transactions) - An AWS service notice (Updates) - A sale announcement (Promotions) SpamFoo looks at what the message says along with who sent it, so each one can get a different classification. - Classifications apply only to your Inbox. Other folders are not sorted. - To hide or show the Classifications, click the sparkle icon at the top of the folder list. Its tooltip says **Show email classifications**. - For a quick reminder of what each one means, click the **Actions** menu (three dots) at the top of the folder list and choose **About Classifications**. For more detail and examples, see [The Four Email Classifications](https://help.spamfoo.com/guides/how-classification-works#classifications). ## Move a Message to a Different Classification If a message landed in the wrong Classification, move it. SpamFoo remembers your choice and uses it for similar messages you get later. 1. Right-click the message in your Inbox. 2. Point to **Modify Classification**. 3. Click the Classification you want. You can also: - Drag the message onto a Classification name above your folders. If you drag it from another folder, it moves to your Inbox too. - Open the message, click **Mark**, and choose **Modify Classification**. See [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result) for more help. ## Report Spam or Not Spam Reporting tells SpamFoo it got a message wrong. When your administrator allows it, SpamFoo learns from your reports for your own mailbox. 1. Right-click the message. 2. Point to **Report**. 3. Click **Spam** or **Not Spam**. **Report** doesn't move the message. It only sends the report. You can report messages in any folder except Drafts, Sent Items, and Scheduled. You can also report a message these ways: - In an open message, click **Mark** and choose **Report**. - In the right-click menu, choose **Move to Junk** or **Move to Inbox**. - Move the message from your Inbox to your Junk Email folder to report spam, or from Junk Email back to your Inbox to report not spam. This works in any mail app connected to your mailbox, such as Outlook or a phone app. SmarterMail waits at least two minutes before it sends a report made by moving a message. If you move the message back before then, nothing is reported. See [Correct a Wrong Result](https://help.spamfoo.com/guides/correct-result) for more help. ## Choose Whether to Share Your Corrections The first time you report a message or change its Classification, SmarterMail may ask **Share Your Corrections**. This only happens if your administrator allows sharing. - **Yes:** Along with your correction, SpamFoo may receive a copy of the message text with personal details removed. Email addresses, phone numbers, Social Security numbers, credit card numbers, IP addresses, and common names are taken out before anything is sent. Unusual details, such as a name SpamFoo doesn't recognize, may still get through. This helps SpamFoo improve results for everyone. - **No:** The message text is not shared. Your corrections still teach SpamFoo about your own mailbox. To change your answer later: 1. Go to **Settings > Account**. 2. In the **Webmail** card, turn **Send classification and spam correction details to SpamFoo** on or off. 3. Click **Save**. If the switch is grayed out, your administrator has turned off sharing for the whole server. ## Open Your SpamFoo Dashboard Your SpamFoo dashboard shows how SpamFoo has handled your mail. 1. Go to **Email**. 2. Click the **Actions** menu (three dots) at the top of the folder list. 3. Click **SpamFoo Dashboard**. The dashboard opens in a new window. If the window says the dashboard is starting up, wait a moment. The page refreshes on its own. To learn your way around, see [Open Your Dashboard](https://help.spamfoo.com/guides/open-dashboard) and [Read Your Overview](https://help.spamfoo.com/guides/overview). *** Source: https://help.spamfoo.com/integrations/smartermail/troubleshoot Last updated: 2026-10-27 # Troubleshoot SmarterMail Use this page when SpamFoo isn't working as expected in SmarterMail. Each problem lists the likely causes, with a fix for each. > **Mail Is Still Delivered** When SpamFoo Client is unavailable or doesn't answer in time, SmarterMail still delivers the mail. The message gets no SpamFoo spam weight and, if Email Classifications are on, goes to Primary. ## Where to Look - **SmarterMail logs:** Go to **Manage > Troubleshooting > View Logs**. SpamFoo start-up and scan messages are in the **Delivery** log and start with `[SpamFoo Client]`. Many of them are written only when the Delivery log is set to detailed logging. SpamFoo license messages are in the **Licensing** log. - **SpamFoo Client logs:** The client writes its own logs to the `logs` folder inside its data directory. See [Where Files Live](https://help.spamfoo.com/integrations/smartermail/how-it-works#file-locations). - **Message headers:** When SpamFoo scans a message for spam, SmarterMail adds a `SpamFoo [...]` entry to the `X-SmarterMail-Spam` header and writes `X-SpamFoo-*` headers. See [How the Spam Score Combines](https://help.spamfoo.com/integrations/smartermail/how-it-works#spam-score). ## SpamFoo Isn't Checking or Classifying Mail **Cause:** The SpamFoo service was stopped. A stopped SpamFoo service stays stopped, even after SmarterMail restarts. **Fix:** Go to **Manage > Troubleshooting > Services**, select the **SpamFoo** row, and click **Start**. **Cause:** Spam checking is turned off for SpamFoo. **Fix:** Go to **Settings > Antispam > Spam Checks**, click the **SpamFoo** row, turn on **Enable Spool Filtering**, and click **Save**. **Cause:** The domain's Email Classification mode is **Disabled**. **Fix:** Go to **Manage > Domains**, click the domain, open the **Options** tab, and choose a mode in the **Email Classification** card. See [Choose a Mode for a Domain](https://help.spamfoo.com/integrations/smartermail/setup#choose-domain-mode). **Cause:** The mailbox's Email Classification switch is off, or the domain offers Classifications and the user chose **Later**. **Fix:** The user can turn on **Classify My Inbox** in **Settings > Account**. You can also go to **Manage > Domains**, click the domain, open **Accounts**, click the user, and turn on **Email Classification** in the **Webmail** card. **Cause:** SmarterMail skips the SpamFoo spam scan for some mail by design: messages from senders who signed in to SmarterMail, trusted senders and contacts that pass authentication, IP addresses set to bypass spam checks, and SmarterMail's own system messages. **Fix:** No fix is needed. With Email Classifications on, mail from signed-in senders goes to Primary, and mail from trusted senders goes to Primary unless the user's own corrections point to another Classification. **Cause:** SpamFoo Client didn't start. The Delivery log shows entries such as `SpamFoo Daemon not found at`, `Failed to download SpamFoo client`, `Could not start SpamFoo`, or `Test connection to SpamFoo Daemon failed after`. **Fix:** Make sure the server can reach `global-api.spamfoo.com` over HTTPS so SmarterMail can download the client. Then check the SpamFoo Client logs for the reason it stopped. SmarterMail tries to start the client again at most every 30 seconds while SpamFoo is needed. **Cause:** A client update is being installed. The Delivery log shows `Update in progress, skipping start attempt.` **Fix:** Wait for the update to finish. SmarterMail starts the client again the next time it needs SpamFoo. If the update stops partway, SmarterMail ignores the leftover update lock after five minutes. ## SpamFoo Shows as Not Licensed **Cause:** Your license includes SpamFoo Classification but not SpamFoo Antispam. The SpamFoo row in **Settings > Antispam > Spam Checks** shows **(Classification Only)**, and the SpamFoo dialog warns that the add-on is not licensed. SpamFoo adds no spam weight, even with **Enable Spool Filtering** on. **Fix:** Go to **Settings > Licensing** and check the **SpamFoo Antispam** card. If it offers **Start Trial**, you can start a trial there. Otherwise add SpamFoo Antispam to your license. Email Classifications keep working in the meantime. **Cause:** SmarterMail couldn't get a SpamFoo license. The Licensing log shows `Failed to obtain lite license` or `Error obtaining lite license`. **Fix:** Allow outbound HTTPS from the mail server to `services.smartertools.com`. If the log says SmarterMail is deferring the license until a set time, it tries again after that time. If the request failed without a retry time, SmarterMail doesn't try again until the SmarterMail service restarts, so restart it after fixing the connection. **Cause:** SmarterMail couldn't verify the SpamFoo license. The Licensing log shows a message that the key `failed to verify; treating as unlicensed`. **Fix:** Allow outbound HTTPS to `global-api.spamfoo.com`. SmarterMail downloads the key it uses to check SpamFoo licenses from there the first time, then keeps a copy in `etc/sf.pubkey`. **Cause:** The SpamFoo add-on was just added to or renewed on your SmarterMail license, and SmarterMail hasn't picked it up yet. **Fix:** Open **Settings > Licensing**. SmarterMail checks the SpamFoo license again when that page loads, so no restart is needed. ## The SpamFoo Dashboard Won't Open **Cause:** The **SpamFoo Dashboard** link is missing. SmarterMail shows the link only while SpamFoo is in use: the SpamFoo service is running, and either SpamFoo spam checking is on or at least one domain has Email Classifications turned on. **Fix:** Start the SpamFoo service and turn on spam checking or Email Classifications. See [Set Up SmarterMail](https://help.spamfoo.com/integrations/smartermail/setup). **Cause:** The dashboard window says **SpamFoo has not been enabled by your administrator**. The SpamFoo service is stopped. **Fix:** Go to **Manage > Troubleshooting > Services**, select **SpamFoo**, and click **Start**. **Cause:** The window says the SpamFoo service is still starting up, or that it is not running. Opening the dashboard asks SmarterMail to start a stopped client. **Fix:** Leave the window open. It refreshes every 5 seconds and shows the dashboard once the client is ready. If it doesn't, check the Delivery log for start-up errors. **Cause:** The window says the SpamFoo service is running but not responding. **Fix:** Check the SpamFoo Client logs. If the client stays unresponsive, go to **Manage > Troubleshooting > Services**, stop **SpamFoo**, and start it again. **Cause:** The dashboard window goes back to the SmarterMail interface instead of opening. The dashboard sign-in expired or was opened in a different browser. **Fix:** Open the dashboard again from the **SpamFoo Dashboard** link in SmarterMail. A dashboard sign-in lasts 24 hours, renews while you use it, and works only in the browser that opened it. ## Mail Is Slow or SpamFoo Times Out SmarterMail waits up to 30 seconds for each SpamFoo call. After that, the message is delivered without a SpamFoo result. **Cause:** Messages are waiting in SpamFoo's scan queue. In the SpamFoo dashboard, the **Scan Queue** tile on **System > Status** stays high. **Fix:** In the SpamFoo dashboard, go to **System > Settings** and raise **Max Concurrency** in the **Classification** card. Raise it only as far as the server's CPU allows. See [Concurrency](https://help.spamfoo.com/guides/configure#concurrency). **Cause:** The server is short on memory or CPU for SmarterMail and SpamFoo Client together. **Fix:** Compare the server with [System Requirements](https://help.spamfoo.com/guides/system-requirements) and add resources if needed. ## Classifications Don't Appear in Webmail **Cause:** The mailbox is still being classified. Webmail hides the Classifications until SmarterMail finishes the user's Inbox. **Fix:** Go to **Manage > Troubleshooting > Mailbox Processing** and check the mailbox's **Classification** row. The Classifications appear after it shows **Completed**. **Cause:** The user hid the Classifications. **Fix:** Have the user click the sparkle icon at the top of the folder list (tooltip **Show email classifications**). **Cause:** Classifications are off for the domain or the mailbox, or the user chose **Later** when offered. **Fix:** Turn them on as described under [SpamFoo Isn't Checking or Classifying Mail](https://help.spamfoo.com/integrations/smartermail/troubleshoot#not-checking-or-classifying). **Cause:** The user's Inbox holds more messages than the domain's **Automatic Classification Inbox Message Limit**. In **Enabled for All Mailboxes** mode, SmarterMail may also have turned the mailbox's Email Classification switch off. **Fix:** Raise the limit on the domain's **Options** tab, then turn **Email Classification** back on for the mailbox. To classify one large mailbox without raising the limit, turn its switch on and use [Reclassify](https://help.spamfoo.com/integrations/smartermail/setup#reclassify). ## Most Mail Lands in Primary **Cause:** SpamFoo Client was unavailable or timed out when the mail arrived. SmarterMail puts that mail in Primary. **Fix:** Fix the client problem using the sections above. Mail that arrives afterward is classified normally. **Cause:** The mail comes from senders who signed in to SmarterMail, such as other users on the server, or from trusted senders and contacts. SmarterMail treats this mail as Primary. **Fix:** No fix is needed. If a user moves a trusted sender's message to another Classification, SpamFoo follows that correction for similar mail later. Mail from signed-in senders always goes to Primary. **Cause:** Your license doesn't include SpamFoo Classification. **Fix:** Check the **SpamFoo Classification** card in **Settings > Licensing**. *** Source: https://help.spamfoo.com/integrations/smartermail/how-it-works Last updated: 2026-10-27 # How SmarterMail Uses SpamFoo This page describes how SmarterMail installs and runs SpamFoo Client, how SpamFoo's result feeds SmarterMail's spam score, and where the files live. It is for administrators who want to understand or troubleshoot SpamFoo in SmarterMail. To turn features on, see [Set Up SmarterMail](https://help.spamfoo.com/integrations/smartermail/setup). ## How SmarterMail Runs the Client ### When the Client Runs SmarterMail runs `spamfoo-client` only while SpamFoo is needed. SpamFoo is needed when both of these are true: - The SpamFoo service is running in **Manage > Troubleshooting > Services**. It runs by default. Stopping it there is saved, so the client stays stopped across SmarterMail restarts until someone starts it again. - SpamFoo spam checking is on (**Enable Spool Filtering**), or at least one domain's Email Classification mode is not **Disabled**. SmarterMail checks this when it starts, when you save the SpamFoo spam check, and a few seconds after a domain's Email Classification mode changes. It starts the client if needed and stops it if not. It also starts a stopped client on demand when a message needs scanning or when someone opens the SpamFoo dashboard. ### First Start The first time SpamFoo is needed, SmarterMail downloads and installs SpamFoo Client, sets up its license, and starts it. No setup is needed on your part. See [Licensing](https://help.spamfoo.com/integrations/smartermail/how-it-works#licensing). ## The Local Endpoint SmarterMail talks to the client at `http://localhost:16253`. The client listens only on localhost by default. Users reach the SpamFoo dashboard through SmarterMail's own web server, so no extra port needs to be open. ## What SmarterMail Sends to SpamFoo ### Incoming Mail - SmarterMail scans each message once per local recipient at delivery time, because results are personalized per mailbox. - With spam checking on and licensed, SpamFoo checks the message for spam and, when Email Classifications are on for the recipient, also sorts it into a Classification. - With spam checking off or not licensed, and Email Classifications on for the recipient, SpamFoo only sorts the message into a Classification. It adds no spam weight. - SpamFoo uses SmarterMail's own SPF, DKIM, and DMARC results instead of checking them again. ### Existing Inbox Messages When a mailbox is queued in **Mailbox Processing**, SmarterMail sends each Inbox message that has no Classification yet to SpamFoo to be classified. Mailboxes with the smallest Inbox go first. Messages moved into the Inbox later are classified the same way. ### Corrections Spam and not spam reports come from the webmail **Report** menu, from **Move to Junk** and **Move to Inbox**, and from moving a message between Inbox and Junk Email in any mail app. - SmarterMail holds each report for at least two minutes. Moving the message back in that time cancels it. - Messages larger than 50 MB are not reported. - Spam and not spam reports are only learned when **Send classification and spam corrections to SpamFoo** is on. When it is off, these reports are not sent to SpamFoo. - When a user moves a message to a different Classification, SpamFoo always learns from it for that user's mailbox, whether or not correction sharing is on. - A copy of the message text is shared with SpamFoo only when correction sharing is on and the user agreed. For a report made in a shared folder, both the person reporting and the mailbox owner must have agreed. See [How Corrections Are Used](https://help.spamfoo.com/guides/corrections). ### The SpamFoo Dashboard SmarterMail serves the SpamFoo dashboard at `/spamfoo-dashboard/` on its own web server and signs users in with their SmarterMail account and role. In a high availability (HA) cluster the path includes the node, as `/spamfoo-dashboard//`, so each node shows its own client. - Dashboard sessions last 24 hours, renew while in use, and are tied to the browser that opened them. - If the client can't be reached, the dashboard shows a page that explains whether SpamFoo is turned off, starting, stopped, or not responding. The page refreshes every 5 seconds, and a stopped client is started. ## How the Spam Score Combines SpamFoo doesn't decide on its own whether SmarterMail treats a message as spam. Instead: 1. SpamFoo returns a spam verdict and probability. 2. SmarterMail picks **Spam Weight** when the verdict is spam, or **Not Spam Weight** when it isn't. The defaults are 20 and 0. 3. SmarterMail adds that weight to the weights from its other spam checks, such as SPF, DKIM, RBLs, and URIBLs. 4. SmarterMail applies its spam filtering actions to the total. A total below zero counts as zero. When SpamFoo scans a message for spam, SmarterMail records the result in the message: - `X-SmarterMail-Spam` gets an entry such as `SpamFoo [Spam,prob:0.97]: 20`. The first value is `Spam`, the Classification, or `Ham` (not spam) when Email Classifications are off for the recipient. - `X-SpamFoo-IsSpam`, `X-SpamFoo-Probability`, `X-SpamFoo-SenderIP`, `X-SpamFoo-SenderEmail`, and `X-SpamFoo-Reason` (the decision path, plus any phishing reasons) are added. - `X-SpamFoo-Classification` and `X-SpamFoo-Classification-Prob` are added when Email Classifications are on for the recipient. - When SpamFoo only classifies the message, with no spam scan, only `X-SpamFoo-Classification` is added. - Any `X-SpamFoo-*` headers the sender included are removed before delivery, so a sender can't fake a SpamFoo result. Some mail skips the SpamFoo spam scan: - **Authenticated senders** (users who signed in to SmarterMail to send): not scanned. With Email Classifications on, the message is stamped Primary. - **Trusted senders and contacts** that pass authentication: not scanned for spam. With Email Classifications on, the message is sorted by the user's own corrections when they match, and is Primary otherwise. - **IP addresses set to bypass spam checks** and **SmarterMail system messages**: not sent to SpamFoo. ## When SpamFoo Is Unavailable SpamFoo never blocks mail delivery. When SpamFoo is unavailable or doesn't answer within 30 seconds: - The message gets a SpamFoo weight of 0. SmarterMail's other spam checks still apply. - If Email Classifications are on for the recipient, the message is stamped Primary. - Corrections that fail are logged and not retried. - The dashboard shows an unavailable page instead of an error. ## Where Files Live All paths start from the SmarterMail program folder, the folder that holds the SmarterMail service program files. Where the configuration and data directories go depends on how SmarterMail is installed: | Install | Configuration directory | Data directory | | --- | --- | --- | | Standard | `SpamFoo/data` | `SpamFoo/data` | | Docker | `spamfoo` in the system data volume (`/app/SystemData/spamfoo` by default) | Same as the configuration directory | | HA cluster | `spamfoo` in the shared system data folder, used by every node | `SpamFoo/data` on each node | In Docker, both directories are on the mounted system data volume, so the license, models, and database survive a container rebuild. - `SpamFoo/bin/`: the client program. It is replaced as a whole on each update, so don't keep your own files here. - `/etc/`: the SpamFoo license key and the keys SmarterMail and the client use to work together. Treat this folder like a password. - `/admin.db`: the client's database. - `/trained-models/`: downloaded models. - `/logs/`: SpamFoo Client logs. ## Updates SpamFoo Client stays up to date in two ways: - **While it runs:** SpamFoo Client checks for new models and new versions of itself about every 6 hours. New models load without a restart. When a new version is available, the client downloads it and stops, and SmarterMail starts the new version the next time SpamFoo is needed. - **When SmarterMail starts it:** SmarterMail checks for a newer version first and installs it before starting the client. It checks at most once a day. If the check fails, it starts the version already installed. SmarterMail never stops a running client just to update it. For SpamFoo Client installed without SmarterMail, see [Update and Back Up](https://help.spamfoo.com/guides/updates). ## Licensing - If the SmarterMail license includes the SpamFoo add-on, SmarterMail uses that add-on's SpamFoo license. - Otherwise, SmarterMail gets a SpamFoo license for Email Classifications on its own. - Without the add-on, SmarterMail sends no spam scans to SpamFoo, and the SpamFoo spam check shows **(Classification Only)**. - Opening **Settings > Licensing** makes SmarterMail check the license again, so a new or removed add-on takes effect without a restart. *** Source: https://help.spamfoo.com/integrations/local-api Last updated: 2026-10-27 # Start With the Local API This walkthrough shows how to connect a mail server or application to SpamFoo Client's local API: send a message, read the result, act on it, and check that the service is healthy. For every field and header, see the [API Reference](https://help.spamfoo.com/api). ## Before You Start - SpamFoo Client is installed and running. See [Install SpamFoo Client](https://help.spamfoo.com/guides/install). - `curl http://localhost:16253/health/ready` on the server running SpamFoo Client returns `200`. - Your code can read each incoming message as raw bytes, exactly as it was received. ## Know the Basics - **Base URL:** `http://localhost:16253` - **Request body:** the raw message, also called raw .eml content. This is the full RFC 5322 message, headers and body, as it arrived over SMTP. Send it with `Content-Type: message/rfc822`. - **Delivery details:** information that is not inside the message, such as the connecting IP address, goes in `X-` request headers. - **Responses:** JSON with camelCase field names. - **Endpoints:** use the `-stream` endpoints, which take the message in the request body. SpamFoo Client does not read message files from disk on your behalf. ## Authenticate Your Requests SpamFoo Client checks who is calling with a shared secret called the proxy secret, sent in the `X-SpamFoo-Proxy-Auth` header. Send this secret on every request. The samples on this page read the secret from the `SPAMFOO_PROXY_SECRET` environment variable on the calling machine. See [Authenticate Requests](https://help.spamfoo.com/api#authentication) for which requests need it, where to find it, and what happens when it changes. SpamFoo Client listens on `localhost` only by default. To accept requests from another machine, set `SPAMFOO_BIND_ADDRESS` as described in [Configure SpamFoo Client](https://help.spamfoo.com/guides/configure), and send the secret on every request. ## Classify a Message `POST /classify-stream` Send the raw message as the body. All headers are optional, but each one you leave out gives SpamFoo Client less to work with. | Header | Value | | --- | --- | | `X-Sender-IP` | IP address of the server that connected to your mail server to deliver the message. Used for authentication checks and IP reputation. | | `X-Sender-Email` | Envelope sender, the address from the SMTP `MAIL FROM` command. Used for authentication checks and to match allow and block rules. | | `X-User-ID` | The recipient mailbox, as a full email address. Turns on personalization for that user. See [Identify Users](https://help.spamfoo.com/integrations/local-api#user-ids). | | `X-Recipient-Email` | The recipient address. Used to match domain-level allow and block rules. | | `X-SpamFoo-Log-ID` | Your own ID for this message, such as a queue ID. It appears in SpamFoo Client's classification log so you can match log lines to your own. Up to 64 letters, digits, and `. _ : -`; other values are ignored. | | `X-SpamFoo-Classify` | Send `false` to get only a spam verdict with no Email Classification. Omit it, or send `true`, to get both. | | `X-SpamFoo-Proxy-Auth` | The proxy secret. Required in standalone mode, and for any request that does not come from loopback. | Some optional headers are for mail servers that already check SPF, DKIM, and DMARC, or that classify old mail in bulk. See [Classify a Message](https://help.spamfoo.com/api/classify#request). ### curl ``` curl -X POST http://localhost:16253/classify-stream \ -H "Content-Type: message/rfc822" \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "X-Sender-IP: 203.0.113.50" \ -H "X-Sender-Email: sender@example.com" \ -H "X-User-ID: user@example.com" \ -H "X-Recipient-Email: user@example.com" \ --data-binary @message.eml ``` ### C# ``` using System.Net.Http.Headers; using System.Net.Http.Json; using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253"), Timeout = TimeSpan.FromSeconds(60) }; var result = await ClassifyAsync(http, "message.eml"); Console.WriteLine(result is null ? "No verdict. Deliver normally." : $"isSpam={result.IsSpam} classification={result.Classification}"); static async Task ClassifyAsync(HttpClient http, string emlPath) { await using var eml = File.OpenRead(emlPath); var content = new StreamContent(eml); content.Headers.ContentType = new MediaTypeHeaderValue("message/rfc822"); using var request = new HttpRequestMessage(HttpMethod.Post, "/classify-stream") { Content = content }; request.Headers.Add("X-SpamFoo-Proxy-Auth", Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET")); request.Headers.Add("X-Sender-IP", "203.0.113.50"); request.Headers.Add("X-Sender-Email", "sender@example.com"); request.Headers.Add("X-User-ID", "user@example.com"); request.Headers.Add("X-Recipient-Email", "user@example.com"); try { using var response = await http.SendAsync(request); if (!response.IsSuccessStatusCode) return null; return await response.Content.ReadFromJsonAsync(); } catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException) { return null; } } record ClassifyResult( bool IsSpam, string Classification, double SpamProbability, double? DisplayConfidence, bool ShouldEscalate, string? BypassReason); ``` ### Python ``` import os import requests SPAMFOO_URL = "http://localhost:16253" def classify(eml_path): headers = { "Content-Type": "message/rfc822", "X-SpamFoo-Proxy-Auth": os.environ.get("SPAMFOO_PROXY_SECRET", ""), "X-Sender-IP": "203.0.113.50", "X-Sender-Email": "sender@example.com", "X-User-ID": "user@example.com", "X-Recipient-Email": "user@example.com", } try: with open(eml_path, "rb") as eml: response = requests.post(f"{SPAMFOO_URL}/classify-stream", data=eml, headers=headers, timeout=60) except requests.RequestException: return None if not response.ok: return None return response.json() result = classify("message.eml") print("No verdict. Deliver normally." if result is None else f"isSpam={result['isSpam']} classification={result['classification']}") ``` Each sample returns no result when SpamFoo Client is busy, stopped, or returns an error, so the caller can deliver the message normally. ## Read the Result A successful request returns `200` with a JSON body. This example is shortened to the fields most integrations use: ``` { "isSpam": false, "classification": "updates", "spamProbability": 0.04, "displayConfidence": 0.06, "shouldEscalate": false, "bypassReason": null } ``` | Field | Meaning | | --- | --- | | `isSpam` | The spam verdict. Base your delivery decision on this field. | | `classification` | For mail that is not spam: `primary`, `transactions`, `updates`, or `promotions`. For spam: `spam`, or `phishing` when SpamFoo Client detected phishing. Empty when no Email Classification was made, for example when an allow or block rule decided the message or the request sent `X-SpamFoo-Classify: false`. Compare values without regard to case. | | `spamProbability` | The raw spam score from 0 to 1. The spam threshold is not fixed at 0.5, so do not compare this value to 0.5 to decide spam. Use `isSpam`. | | `displayConfidence` | The spam score rescaled so the spam threshold sits at 0.5: spam is 0.5 or higher, and other mail is 0.5 or lower. Use it when you show a score to people. It is `null` when a rule or personalization decided the message. | | `shouldEscalate` | `true` when the models were unsure or disagreed with each other. You can use it to flag a message for review. | | `bypassReason` | What decided the message when it was not the global models, such as `system_override` (an allow or block rule from the admin console) or `knn_memory` (the user's own past corrections). `null` when the global models decided. | The response has more fields, including authentication scores and phishing details. See [Classify a Message](https://help.spamfoo.com/api/classify) for the full list. ## Act on the Result - **Spam:** when `isSpam` is `true`, move the message to the junk folder, or handle it the way your server handles spam. You may want to treat `phishing` more strictly than other spam. - **Not spam:** when `isSpam` is `false`, deliver the message. If your mail client shows classification tabs or folders, file it by `classification`. - **No classification:** when `classification` is empty and the message is not spam, deliver it to the inbox. - **No result:** when the request fails, deliver the message as if SpamFoo Client were not installed. - **Remember the verdict:** store `isSpam` and `classification` with the message if you can. You send them back as the original verdict when a user corrects the message. See [Send Corrections Back](https://help.spamfoo.com/integrations/send-corrections). ### curl This shell example uses `jq` to read the result and pick a folder. ``` result=$(curl -s -f -X POST http://localhost:16253/classify-stream \ -H "Content-Type: message/rfc822" \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "X-User-ID: user@example.com" \ --data-binary @message.eml) || result="" if [ -z "$result" ]; then folder="Inbox" elif [ "$(echo "$result" | jq -r '.isSpam')" = "true" ]; then folder="Junk" else case "$(echo "$result" | jq -r '.classification | ascii_downcase')" in transactions) folder="Transactions" ;; updates) folder="Updates" ;; promotions) folder="Promotions" ;; *) folder="Inbox" ;; esac fi echo "$folder" ``` ### C# ``` static string ChooseFolder(ClassifyResult? result) { if (result is null) return "Inbox"; var classification = result.Classification?.ToLowerInvariant() ?? ""; if (result.IsSpam) return classification == "phishing" ? "Quarantine" : "Junk"; return classification switch { "transactions" => "Transactions", "updates" => "Updates", "promotions" => "Promotions", _ => "Inbox" }; } ``` ### Python ``` def choose_folder(result): if result is None: return "Inbox" classification = (result.get("classification") or "").lower() if result["isSpam"]: return "Quarantine" if classification == "phishing" else "Junk" return { "transactions": "Transactions", "updates": "Updates", "promotions": "Promotions", }.get(classification, "Inbox") ``` ## Handle Errors | Status | Meaning | What to Do | | --- | --- | --- | | `400` | The body was empty, or `X-SpamFoo-Classify` was not `true` or `false`. | Fix the request. Deliver the message normally. | | `401` | The proxy secret was missing or wrong, and the request needed it. | Send the correct `X-SpamFoo-Proxy-Auth` header. | | `403` | The license does not include the feature this request needs, or, in standalone mode, the license has expired. The body is `{"error": "feature_not_licensed", ...}`. | Deliver the message without classification. See [Know How Licenses Affect Requests](https://help.spamfoo.com/integrations/classification-only#license-behavior). | | `413` | The message is larger than the configured size limit. | Deliver the message without classification. | | `429` | Standalone mode only. Too many requests from this address sent a wrong secret, so it is blocked for a while. The `Retry-After` header says how many seconds are left. | Fix the secret. Deliver the message without classification. | | `503` | SpamFoo Client is busy. The `code` field says why: `queue_timeout` when the request waited too long in the classification queue, or `server_busy` when too many requests were already in progress. The response includes a `Retry-After` header. | Deliver the message without classification. Retry only if your mail flow can wait. | ## Identify Users for Personalization Personalization means SpamFoo Client adjusts results for each mailbox based on that user's own corrections. It only works when SpamFoo Client knows which user a message belongs to. - Send the recipient's full email address in `X-User-ID`, such as `user@example.com`. - Use the same value every time for the same mailbox, in both classification requests and corrections. A different spelling counts as a different user. - For a message with several local recipients, classify it once per recipient, each with its own `X-User-ID`, if each recipient gets their own copy. - Without `X-User-ID`, SpamFoo Client applies no personalization to that message. - The user ID also labels the message in the admin console, so administrators can find a user's mail. To learn how corrections change future results, see [How Personalization Works](https://help.spamfoo.com/guides/personalization). ## Check Health `GET /health/ready` Use this endpoint to decide whether to send mail to SpamFoo Client. It needs no secret. It returns `200` when SpamFoo Client can classify mail, and `503` with a list of reasons when it can't, such as while its models download or while it shuts down. ``` curl http://localhost:16253/health/ready ``` `GET /health` Use this endpoint to watch the queues. It needs no secret and always returns `200` while the service is up. Check the `status` field: `healthy`, or `shutting_down` while the service is stopping. ``` curl http://localhost:16253/health ``` ``` { "status": "healthy", "shuttingDown": false, "activeRequests": 1, "modelReloading": false, "spoolQueue": { "active": 1, "waiting": 0, "maxConcurrency": 2, "totalProcessed": 5120, "totalTimedOut": 0 } } ``` This example is shortened. A rising `waiting` count or `totalTimedOut` means messages are waiting for classification. See [Check Health](https://help.spamfoo.com/api/health) for every field. ## Next Steps - [Send Corrections Back](https://help.spamfoo.com/integrations/send-corrections) when users mark spam or move messages. - [Use the Command Line](https://help.spamfoo.com/integrations/command-line) to test with saved messages. - [Classify a Message](https://help.spamfoo.com/api/classify) for the full request and response reference. *** Source: https://help.spamfoo.com/integrations/command-line Last updated: 2026-10-27 # Use the Command Line This page shows how to start SpamFoo Client from a terminal and classify saved messages with `curl`. It is useful for testing, scripts, and sorting a folder of `.eml` files. The `spamfoo-client` program has no classify command. Running it with no command, or with `run`, starts the local HTTP service, and you classify messages by sending HTTP requests to that service. The service keeps its models loaded between requests, so classifying many files in a row is fast. The program also has admin commands, such as `login-link`, `activate`, and `status`. See [Use SpamFoo Client Commands](https://help.spamfoo.com/guides/commands). ## Start the Service If SpamFoo Client already runs as a service or a Docker container, skip to [Check the Service](https://help.spamfoo.com/integrations/command-line#check-the-service). To start it by hand, run it from its install folder: ### Windows ``` .\spamfoo-client.exe --data-dir=C:\SpamFoo\data ``` ### Linux ``` ./spamfoo-client --data-dir=/var/lib/spamfoo ``` The service listens on `http://localhost:16253` and runs until you press `Ctrl+C`. To install it as a Windows service, a systemd service, or a Docker container, see [Install SpamFoo Client](https://help.spamfoo.com/guides/install). ### Startup Options Write each option with an equals sign, such as `--data-dir=/var/lib/spamfoo`. When you start the service, a space instead of `=` is not recognized. | Option | What It Does | | --- | --- | | `--data-dir=` | Folder for SpamFoo Client's databases, logs, and personalization data. Defaults to the install folder. | | `--models-dir=` | Folder for model files. Defaults to the `trained-models` folder inside the data directory. | | `--config-dir=` | Folder that holds `etc/sf.key` (the license key) and `etc/sf.proxysecret` (the proxy secret). Defaults to the data directory. Passing this option also tells SpamFoo Client that a host program, such as SmarterMail, manages it and supplies the proxy secret. Leave it out for a standalone install. | | `--mode=` | `embedded` (the default) or `standalone`. See [Mode](https://help.spamfoo.com/guides/configure#mode). | | `--port=` | The port to listen on. Standalone mode only. Defaults to 16253. | | `--conf=` | A settings file to read. See [Settings File](https://help.spamfoo.com/guides/configure#settings-file). | | `--auto-send-training` | Turns on automatic training reports, which send data about some classified messages to SpamFoo to help improve the global models. Off unless you pass this option or set `SPAMFOO_AUTO_SEND_TRAINING=true`. See [Automatic Training Reports](https://help.spamfoo.com/guides/corrections#automatic-reports). | In `--data-dir` and `--models-dir`, a `~` is replaced with your home folder. Most other settings are environment variables; see [Configure SpamFoo Client](https://help.spamfoo.com/guides/configure). ## Check the Service ``` curl http://localhost:16253/health/ready ``` A service that can classify mail returns `200` and `{"status":"ready","contract":1}`. A `503` lists the reasons it isn't ready yet, such as `models_loading`. If the request fails, check that the service started and that nothing else uses port `16253`. See [Fix Common Problems](https://help.spamfoo.com/guides/common-problems). > **Proxy secret** Add `-H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET"` to each `curl` command below. See [Authenticate Requests](https://help.spamfoo.com/api#authentication) for when it's required. ## Classify One Message Post the raw message to `/classify-stream`: ``` curl -X POST http://localhost:16253/classify-stream \ -H "Content-Type: message/rfc822" \ --data-binary @message.eml ``` Use `--data-binary`, not `-d`. The `-d` option strips line breaks and changes the message. The response is JSON. These are the fields you will look at most (the full response has more): ``` { "isSpam": false, "classification": "promotions", "spamProbability": 0.09, "displayConfidence": 0.12, "shouldEscalate": false, "bypassReason": null } ``` To test personalization or rules for a specific mailbox, add delivery headers such as `X-User-ID`, `X-Sender-IP`, and `X-Sender-Email`. [Start With the Local API](https://help.spamfoo.com/integrations/local-api#classify) explains each header and field. ## Classify a Folder of Messages A loop sends each file in turn. These examples print each file name with its result. ### Windows ``` Get-ChildItem C:\mail\saved\*.eml | ForEach-Object { $result = Invoke-RestMethod -Method Post ` -Uri http://localhost:16253/classify-stream ` -ContentType "message/rfc822" ` -InFile $_.FullName "{0}: isSpam={1} classification={2}" -f $_.Name, $result.isSpam, $result.classification } ``` ### Linux ``` #!/bin/bash for file in /path/to/mail/*.eml; do result=$(curl -s -X POST http://localhost:16253/classify-stream \ -H "Content-Type: message/rfc822" \ --data-binary @"$file") echo "$file: $result" done ``` This Linux example copies each message into a folder named after its result. It uses `jq` to read the JSON. ``` #!/bin/bash for file in /path/to/mail/*.eml; do result=$(curl -s -f -X POST http://localhost:16253/classify-stream \ -H "Content-Type: message/rfc822" \ --data-binary @"$file") || { echo "$file: no result" >&2; continue; } if [ "$(echo "$result" | jq -r '.isSpam')" = "true" ]; then folder="spam" else folder=$(echo "$result" | jq -r '.classification | ascii_downcase') [ -z "$folder" ] && folder="unclassified" fi mkdir -p "/path/to/sorted/$folder" cp "$file" "/path/to/sorted/$folder/" done ``` If SpamFoo Client is busy, it returns `503`. In a script, wait the number of seconds in the `Retry-After` header and retry the file. To keep a large batch from competing with live mail, add `-H "X-SpamFoo-Priority: bulk"`. See [Mark Bulk Work](https://help.spamfoo.com/api/classify#priority). Classifying files this way records each message in the admin console, the same as live mail. ## Send a Correction You can also send corrections from the command line. Correction requests always need the proxy secret, even from `localhost`. See [Send Corrections Back](https://help.spamfoo.com/integrations/send-corrections) for examples. ## Next Steps - [Start With the Local API](https://help.spamfoo.com/integrations/local-api) to build a live integration. - [Install SpamFoo Client](https://help.spamfoo.com/guides/install) as a service so it starts with the server. - [API Reference](https://help.spamfoo.com/api) for every endpoint. *** Source: https://help.spamfoo.com/integrations/send-corrections Last updated: 2026-10-27 # Send Corrections Back This page shows when and how your integration should report user corrections to SpamFoo Client, and what SpamFoo Client does with them. Corrections are how SpamFoo Client learns each user's preferences. ## Know When to Send a Correction Send a correction when a person changes SpamFoo Client's result for a message: - **Marks a message as spam**, or moves it to the junk folder, after it was delivered as not spam. - **Marks a message as not spam**, or moves it out of the junk folder. - **Moves a message to a different classification**, such as from Promotions to Primary. Do not send corrections for moves made by your own server rules or filters. Those are not user preferences, and they would teach SpamFoo Client the wrong thing. Send one correction per user action. ## Before You Start - **Proxy secret:** correction requests always need the `X-SpamFoo-Proxy-Auth` header, even from `localhost`. See [Authenticate Your Requests](https://help.spamfoo.com/integrations/local-api#authenticate). - **The message:** send the raw message as the request body, the same way you send it to `/classify-stream`. The user's stored copy is fine. - **The same user ID:** send the same `X-User-ID` value you used when the message was classified. Otherwise the correction applies to a different user. - **The original verdict (optional):** if you stored `isSpam` and `classification` from the classification result, send them back as the original verdict. - **Bulk corrections (optional):** when you send many old corrections at once, such as after an import, add `X-SpamFoo-Priority: bulk`. They then wait in the background queue and can't slow down live mail. See [Report Spam or Not Spam](https://help.spamfoo.com/api/feedback#request). - **Retries (optional):** if your integration can send the same correction twice, add `X-SpamFoo-Event-Id` with an ID that stays the same across retries. SpamFoo Client then applies it only once. See [Send a Correction More Than Once](https://help.spamfoo.com/api/feedback#duplicates). ## Report Spam or Not Spam `POST /feedback-stream` | Header | Required | Value | | --- | --- | --- | | `X-SpamFoo-Proxy-Auth` | Yes | The proxy secret. | | `X-User-ID` | Yes | The mailbox that made the correction, as a full email address. | | `X-Is-Spam` | Yes | The correct answer: `true` if the user marked the message as spam, `false` if the user marked it as not spam. | | `X-Original-Is-Spam` | No | The `isSpam` value SpamFoo Client returned when the message arrived. If you leave it out, SpamFoo Client classifies the message again when the correction arrives and treats that as the original verdict. | | `X-Sender-IP` | No | IP address of the server that delivered the message to your mail server. | | `X-Sender-Email` | No | Envelope sender (SMTP `MAIL FROM`). | | `X-Send-Email-Text` | No | `true` only if the user agreed to share message text. See [Share Message Text Only With Consent](https://help.spamfoo.com/integrations/send-corrections#consent). | | `X-Subject-Strip-Prefixes` | No | A tag your mail server adds to subjects, such as `[SPAM]`. SpamFoo Client removes it from the subject before it learns from the message. Send the header once per tag. | ### curl ``` curl -X POST http://localhost:16253/feedback-stream \ -H "Content-Type: message/rfc822" \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "X-User-ID: user@example.com" \ -H "X-Is-Spam: true" \ -H "X-Original-Is-Spam: false" \ -H "X-Sender-IP: 203.0.113.50" \ -H "X-Sender-Email: sender@example.com" \ --data-binary @message.eml ``` ### C# ``` using System.Net.Http.Headers; using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") }; var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET") ?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET."); await using var eml = File.OpenRead("message.eml"); var content = new StreamContent(eml); content.Headers.ContentType = new MediaTypeHeaderValue("message/rfc822"); using var request = new HttpRequestMessage(HttpMethod.Post, "/feedback-stream") { Content = content }; request.Headers.Add("X-SpamFoo-Proxy-Auth", secret); request.Headers.Add("X-User-ID", "user@example.com"); request.Headers.Add("X-Is-Spam", "true"); request.Headers.Add("X-Original-Is-Spam", "false"); request.Headers.Add("X-Sender-IP", "203.0.113.50"); request.Headers.Add("X-Sender-Email", "sender@example.com"); using var response = await http.SendAsync(request); Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}"); ``` ### Python ``` import os import requests headers = { "Content-Type": "message/rfc822", "X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"], "X-User-ID": "user@example.com", "X-Is-Spam": "true", "X-Original-Is-Spam": "false", "X-Sender-IP": "203.0.113.50", "X-Sender-Email": "sender@example.com", } with open("message.eml", "rb") as eml: response = requests.post("http://localhost:16253/feedback-stream", data=eml, headers=headers, timeout=60) print(response.status_code, response.text) ``` A successful correction returns `200`: ``` { "status": "personalization_only", "userId": "user@example.com" } ``` See [Report Spam or Not Spam](https://help.spamfoo.com/api/feedback) for the full reference. ## Report a Classification Fix `POST /tabs/feedback-stream` Use this endpoint when a user moves a message that is not spam to a different Email Classification. The message must have a `From` address. | Header | Required | Value | | --- | --- | --- | | `X-SpamFoo-Proxy-Auth` | Yes | The proxy secret. | | `X-User-ID` | Yes | The mailbox that made the correction, as a full email address. | | `X-Classification` | Yes | The correct classification: `primary`, `transactions`, `updates`, or `promotions`. Not case-sensitive. | | `X-Original-Classification` | No | The `classification` SpamFoo Client returned when the message arrived. If you leave it out, SpamFoo Client uses its own prediction for the message at correction time. | | `X-Send-Email-Text` | No | `true` only if the user agreed to share message text. | | `X-Subject-Strip-Prefixes` | No | A subject tag your mail server adds. Send the header once per tag. | ### curl ``` curl -X POST http://localhost:16253/tabs/feedback-stream \ -H "Content-Type: message/rfc822" \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "X-User-ID: user@example.com" \ -H "X-Classification: primary" \ -H "X-Original-Classification: promotions" \ --data-binary @message.eml ``` ### C# ``` using System.Net.Http.Headers; using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") }; var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET") ?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET."); await using var eml = File.OpenRead("message.eml"); var content = new StreamContent(eml); content.Headers.ContentType = new MediaTypeHeaderValue("message/rfc822"); using var request = new HttpRequestMessage(HttpMethod.Post, "/tabs/feedback-stream") { Content = content }; request.Headers.Add("X-SpamFoo-Proxy-Auth", secret); request.Headers.Add("X-User-ID", "user@example.com"); request.Headers.Add("X-Classification", "primary"); request.Headers.Add("X-Original-Classification", "promotions"); using var response = await http.SendAsync(request); Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}"); ``` ### Python ``` import os import requests headers = { "Content-Type": "message/rfc822", "X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"], "X-User-ID": "user@example.com", "X-Classification": "primary", "X-Original-Classification": "promotions", } with open("message.eml", "rb") as eml: response = requests.post("http://localhost:16253/tabs/feedback-stream", data=eml, headers=headers, timeout=60) print(response.status_code, response.text) ``` A successful correction returns `200`: ``` { "status": "success", "userId": "user@example.com", "classification": "primary" } ``` See [Report a Classification Fix](https://help.spamfoo.com/api/feedback-tabs) for the full reference. ## Share Message Text Only With Consent By default, a correction that SpamFoo Client sends on to SpamFoo carries only numbers derived from the message, not its text. The `X-Send-Email-Text` header changes that for one correction. - Send `X-Send-Email-Text: true` only when the user has agreed to share message content for training. - With it, the correction also carries the message text with sensitive patterns removed, plus the headers and link addresses used for phishing training. - Without it, or with any other value, no message text is shared. ## Know What Happens After a Correction - **The user's memory updates right away.** SpamFoo Client stores the corrected message in that user's memory of past corrections. Later messages that closely match it, especially from the same sender, can follow the user's correction instead of the global models. - **Earlier results are cleared.** Cached results are dropped, so the next classification reflects the correction. - **Wrong memories are fixed.** If the user's own memory caused the mistake, SpamFoo Client removes or weakens the memory entries that caused it. - **Global model errors are reported.** SpamFoo Client sends the correction to SpamFoo to help improve the global models only when the global models made the mistake. It skips this when the user's own personalization made the decision, or when the correction matches what the models already said. The user ID is hashed before it leaves the server. - **Sender reputation is updated.** A spam or not spam correction also counts toward the sending IP address and, when the message passed DMARC or aligned SPF, the sending domain. For a plain-language version of this process, see [How Corrections Are Used](https://help.spamfoo.com/guides/corrections). ## Understand How Corrections Personalize Results Personalization comes entirely from corrections. Your integration does not need to create rules for each user. - **Per user:** each `X-User-ID` has its own memory. One user's corrections do not change another user's personalization. - **Visible in results:** when a user's memory decides a message, `/classify-stream` returns `bypassReason` set to `knn_memory` or `knn_memory_cross_sender` (the match came from a different sender). When memory decides the Email Classification, the value is `tabs_knn` (in `bypassReason` from `/classify-stream`, or in `personalizationSource` from `/tabs/classify-stream`). - **Admin-wide rules are separate:** allow and block rules set in the admin console apply to many users at once. See [Manage Spam Rules and Senders](https://help.spamfoo.com/guides/rules-protection). To learn more, see [How Personalization Works](https://help.spamfoo.com/guides/personalization). ### Remove a User's Personal Data To delete everything SpamFoo Client has learned for one user, for example when a mailbox is deleted, call both endpoints. The first clears spam personalization and the second clears classification personalization. ``` curl -X DELETE http://localhost:16253/personalization/data \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "Content-Type: application/json" \ -d '{"userId": "user@example.com"}' curl -X DELETE http://localhost:16253/personalization/tabs/data \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "Content-Type: application/json" \ -d '{"userId": "user@example.com"}' ``` See [Manage Personal Data](https://help.spamfoo.com/api/personal-data) for details. ## Handle Errors | Status | Meaning | | --- | --- | | `400` | A required header is missing or has a bad value, the body is empty, the message could not be parsed, or (for classification fixes) the message has no `From` address. The `error` field says which. | | `401` | The proxy secret is missing or wrong. | | `413` | The message is larger than the configured size limit. | | `429` | Standalone mode only. Too many requests from this address sent a wrong secret, so it is blocked for a while. | | `503` | Only for corrections sent with `X-SpamFoo-Priority: bulk`: SpamFoo Client is busy. Wait the seconds in the `Retry-After` header and send it again. | A failed correction does not affect mail delivery. Log it, and retry later if the failure was temporary. *** Source: https://help.spamfoo.com/integrations/classification-only Last updated: 2026-10-27 # Use Classification Only This page shows how to get Email Classifications (Primary, Transactions, Updates, and Promotions) from SpamFoo Client when another product already decides what is spam, and how a license without spam filtering changes the API's behavior. ## Choose an Approach | Approach | Use It When | | --- | --- | | `POST /tabs/classify-stream` | Your other product has already decided a message is not spam, and you want only its Email Classification. This endpoint never runs spam checks, whatever your license includes. | | `POST /classify-stream` | You want one integration that works with any license. With a license that has no spam filtering, SpamFoo Client skips the spam checks and returns only the Email Classification, in the same response format as always. | Both endpoints take the raw message as the body, the same way as described in [Start With the Local API](https://help.spamfoo.com/integrations/local-api). ## Classify Without Spam Checks `POST /tabs/classify-stream` Send only messages your other product passed as not spam. The endpoint assumes the message is not spam and records it that way. | Header | Value | | --- | --- | | `X-User-ID` | The recipient mailbox, as a full email address. Turns on personalization for that user. | | `X-Sender-IP` | IP address of the server that delivered the message. Shown in the admin console. | | `X-Sender-Email` | Envelope sender (SMTP `MAIL FROM`). Shown in the admin console. | | `X-SpamFoo-Log-ID` | Your own ID for this message, written to SpamFoo Client's classification log. | | `X-SpamFoo-Proxy-Auth` | The proxy secret. Required in standalone mode, and for any request that does not come from loopback. | ### curl ``` curl -X POST http://localhost:16253/tabs/classify-stream \ -H "Content-Type: message/rfc822" \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "X-User-ID: user@example.com" \ --data-binary @message.eml ``` ### C# ``` using System.Net.Http.Headers; using System.Net.Http.Json; using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") }; await using var eml = File.OpenRead("message.eml"); var content = new StreamContent(eml); content.Headers.ContentType = new MediaTypeHeaderValue("message/rfc822"); using var request = new HttpRequestMessage(HttpMethod.Post, "/tabs/classify-stream") { Content = content }; request.Headers.Add("X-SpamFoo-Proxy-Auth", Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET")); request.Headers.Add("X-User-ID", "user@example.com"); using var response = await http.SendAsync(request); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(); Console.WriteLine($"{result!.Classification} ({result.Confidence:P0})"); record TabsResult( string Classification, float Confidence, Dictionary ClassificationProbabilities, string? PersonalizationSource, long ElapsedMs); ``` ### Python ``` import os import requests with open("message.eml", "rb") as eml: response = requests.post( "http://localhost:16253/tabs/classify-stream", data=eml, headers={ "Content-Type": "message/rfc822", "X-SpamFoo-Proxy-Auth": os.environ.get("SPAMFOO_PROXY_SECRET", ""), "X-User-ID": "user@example.com", }, timeout=60, ) response.raise_for_status() result = response.json() print(result["classification"], result["confidence"]) ``` The response: ``` { "classification": "promotions", "confidence": 0.91, "classificationProbabilities": { "primary": 0.04, "transactions": 0.02, "updates": 0.03, "promotions": 0.91 }, "personalizationSource": null, "elapsedMs": 41 } ``` | Field | Meaning | | --- | --- | | `classification` | `primary`, `transactions`, `updates`, or `promotions`. File the message by this value. | | `confidence` | The highest probability among the four classifications, from 0 to 1. When the model is unsure, or Primary is close behind, SpamFoo Client returns `primary`, so this value can belong to a different classification than the one returned. | | `classificationProbabilities` | The model's probability for each classification. | | `personalizationSource` | `tabs_knn` when the user's past corrections decided the classification, or `null` when the global model decided. | | `elapsedMs` | Time SpamFoo Client spent classifying the message, in milliseconds. | Things to know about this endpoint: - Allow and block rules from the admin console are not checked, because no spam decision is made. - Messages appear in the admin console as not spam, but they are left out of the spam and not spam counts. - It uses a separate queue from `/classify-stream`. When it cannot classify the message in time, it returns `503`; leave the message in the inbox. See [Classify Without Spam Checks](https://help.spamfoo.com/api/classify-tabs) for the full reference. ## Know How Licenses Affect Requests A SpamFoo license includes one or both of these features, shown in the admin console as **Spam Filtering** and **Classification**. SpamFoo Client learns which features you have when it checks your license with SpamFoo, and it adjusts each request to match. | License Includes | `/classify-stream` | `/tabs/classify-stream` | | --- | --- | --- | | Spam Filtering and Classification | Spam verdict and Email Classification. | Email Classification. | | Classification only | Email Classification only. `isSpam` is `false` and `spamProbability` is `0`, except when an admin console allow or block rule decides the message. | Email Classification. | | Spam Filtering only | Spam verdict only. `classification` is empty for mail that is not spam. | `403` with `"error": "feature_not_licensed"`. | - **Spam-only requests need Spam Filtering.** Sending `X-SpamFoo-Classify: false` to `/classify-stream` with a Classification only license returns `403`. - **Before the license check:** until SpamFoo Client has current license information, for example just after it starts and before it reaches SpamFoo, it does not block local work. `/classify-stream` runs both spam checks and classification, and `/tabs/classify-stream` is available. - **After the license expires:** in embedded mode, SpamFoo Client keeps running both spam checks and classification. In standalone mode, once the license has expired and its grace period has ended, or SpamFoo Client hasn't reached SpamFoo for too long, both endpoints return `403` with `"error": "feature_not_licensed"` until a valid license is active. Treat it as "no verdict" and deliver the message normally. - **Same response format:** with a Classification only license, `/classify-stream` still returns every field it normally does, so code written for a full license keeps working. Check `classification` and ignore the spam fields. To see which features your license includes, open the license details in the [admin console](https://help.spamfoo.com/guides/admin-console). ## Send Classification Corrections When a user moves a message to a different classification, send the correction to `POST /tabs/feedback-stream`. This works with any license and is how SpamFoo Client learns each user's preferences. See [Report a Classification Fix](https://help.spamfoo.com/integrations/send-corrections#classification-feedback). Spam and not spam corrections from your other product do not need to go to SpamFoo Client unless SpamFoo Client also makes spam decisions for you. *** Source: https://help.spamfoo.com/api Last updated: 2026-10-27 # API Overview SpamFoo Client exposes a local HTTP API for classifying messages, sending user corrections, managing a user's stored data, and checking service health. This page covers what every endpoint has in common. Each endpoint has its own page with request and response details. ## Base URL and Port | Property | Value | | --- | --- | | Base URL | `http://localhost:16253` | | Port | `16253`. In standalone mode, it can be changed with `SPAMFOO_PORT`. | | Protocol | HTTP. SpamFoo Client does not serve HTTPS. | | Listen address | `localhost` by default. Set `SPAMFOO_BIND_ADDRESS` to listen on another address. The Docker image listens on all interfaces (`0.0.0.0`). | | Responses | JSON with camelCase field names. Enum values are strings. | | Contract version | Every response has an `X-SpamFoo-Contract` header with the API's version, currently `1`. See [Versioning](https://help.spamfoo.com/api#versioning). | ## Authenticate Requests SpamFoo Client checks callers with a shared secret called the proxy secret. Send it in the `X-SpamFoo-Proxy-Auth` header. - If you set the `SPAMFOO_PROXY_SECRET` environment variable, that value is the secret. - Otherwise the secret is read from `etc/sf.proxysecret` in the configuration directory, which is the data directory unless SpamFoo Client was started with `--config-dir`. Unless SmarterMail runs it, SpamFoo Client creates this file with a random value on first start. In the Docker image the file is `/data/etc/sf.proxysecret`, and with the Linux package it is `/var/lib/spamfoo/etc/sf.proxysecret`. - When SmarterMail runs SpamFoo Client, SmarterMail writes the secret and sends it on its own requests. Without SmarterMail, the same value is the admin console password, so store it like a password. | Endpoints | Secret Required | | --- | --- | | Classification: `/classify-stream`, `/tabs/classify-stream` | In standalone mode, always. In embedded mode, only when the request does not come from loopback (localhost). A wrong or missing secret returns `401` with `{"error": "unauthorized"}`. | | Corrections, personal data, settings, and integration: `/feedback-stream`, `/tabs/feedback-stream`, `/personalization/...`, `/admin/api/settings`, `/integration/...`, `/generate-login-link` | Always, even from loopback. A wrong or missing secret returns `401` with an empty body. | | Health: `/health`, `/health/live`, `/health/ready` | Never. | - **Loopback** means the connection comes from `127.0.0.1` or `::1` on the same machine. Every local process counts, including a reverse proxy running on the same host. - **Docker:** requests to the container's published port arrive from Docker's network, not loopback, so they need the secret. - **Standalone mode** trusts no caller by its address. See [Mode](https://help.spamfoo.com/guides/configure#mode). ### When the Secret Changes In standalone mode, an administrator can change the secret while SpamFoo Client runs. For 15 minutes after the change, SpamFoo Client still accepts the old secret on classification, correction, and `/integration/...` requests, so your integration has time to switch. Every other endpoint accepts only the new secret right away. Embedded mode reads the secret only when it starts. ### Repeated Wrong Secrets After too many wrong secrets from one IP address, SpamFoo Client answers requests from that address with `429` for a while, including health checks. Requests that carry the correct secret, or come from a signed-in dashboard, still get through, so a mail server behind the same reverse proxy keeps working. The body is `{"error": "...", "code": "auth_blocked"}`, and the `Retry-After` header gives the seconds left. The code samples in this reference read the secret from the `SPAMFOO_PROXY_SECRET` environment variable on the calling machine. ## Send the Message in the Request Body Endpoints that work on a message take the raw message as the request body. The raw message, also called raw .eml content, is the full RFC 5322 message, headers and MIME body, exactly as it arrived over SMTP. Details that are not inside the message, such as the connecting IP address, go in `X-` request headers. - Send the bytes unchanged. Do not decode, re-encode, or wrap them in JSON. - Set `Content-Type: message/rfc822`. SpamFoo Client does not check this header on these endpoints, but it describes the body correctly. - An empty body returns `400` with `{"error": "No email content provided"}`. - The body size limit is set by `SPAMFOO_MAX_CLASSIFY_BODY_BYTES`. The default is 520 MiB, or 64 MiB in standalone mode. A larger body returns `413`. [Get Integration Info](https://help.spamfoo.com/api/integration-info) reports the limit in effect. - SpamFoo Client does not read message files from disk on your behalf. Always send the message in the body. Endpoints that take JSON, such as the `DELETE` endpoints on [Manage Personal Data](https://help.spamfoo.com/api/personal-data), need `Content-Type: application/json`. Without it they return `415`. ## Identify the Mailbox and Message These request headers describe who the message is for and how it arrived. Each endpoint page lists which ones it reads. | Header | Meaning | | --- | --- | | `X-User-ID` | The mailbox the message belongs to, usually the recipient's full email address. SpamFoo Client keeps each user's corrections under this ID, so send the same value for the same mailbox on every request, in both classification and correction calls. Without it, only the global models and system rules apply. | | `X-Sender-IP` | IP address of the server that connected to your mail server to deliver the message. Used for SPF checks and IP reputation. | | `X-Sender-Email` | The envelope sender, from the SMTP `MAIL FROM` command. Used for authentication checks and to match allow and block rules. | | `X-Recipient-Email` | The envelope recipient, from the SMTP `RCPT TO` command. Used to match domain-level allow and block rules and to check for impersonation of protected people. | | `X-SpamFoo-Log-ID` | Your own ID for the message, such as a queue ID. It appears in SpamFoo Client's classification log so you can match its log lines to yours. Up to 64 characters from `A-Z a-z 0-9 . _ : -`; any other value is ignored. | SpamFoo Client reads `X-` header values as UTF-8, so mailbox names with non-ASCII characters work. Make sure your HTTP client sends them as UTF-8. In .NET, `HttpClient` rejects non-ASCII header values unless you set `SocketsHttpHandler.RequestHeaderEncodingSelector`. ## How Licenses Affect Requests A SpamFoo license can include spam detection, Email Classification, or both. SpamFoo Client checks the license before it runs an endpoint. | Endpoint | License Needs | | --- | --- | | `/classify-stream` | Spam detection or Email Classification. `/classify-stream` runs only the parts the license includes. See [Classify a Message](https://help.spamfoo.com/api/classify#licenses). | | `/tabs/classify-stream` | Email Classification. | | Corrections, personal data, and health | No license check. | When the license does not include what an endpoint needs, the response is `403` with a body such as `{"error": "feature_not_licensed", "features": ["classification"]}`. The `features` list names what the endpoint accepts. ## Errors and Status Codes Most errors return a JSON body with an `error` field. Some add more fields, such as `code`, `queueDepth`, or `features`. ``` { "error": "Classification queue timeout", "code": "queue_timeout", "queueDepth": 12 } ``` | Status | Meaning | | --- | --- | | `200` | Success. | | `400` | A required header, field, or body is missing or invalid, or the message could not be parsed. The `error` field says which. A missing required query parameter or unreadable JSON body returns `400` with no body. | | `401` | The proxy secret is missing or wrong. See [Authenticate Requests](https://help.spamfoo.com/api#authentication). | | `403` | `feature_not_licensed`: the license does not include what this request needs. | | `413` | `request_body_too_large`: the body is over the size limit. The response includes `maxBytes`. | | `415` | A JSON endpoint was called without `Content-Type: application/json`. | | `429` | `auth_blocked`: too many wrong secrets came from this address. See [Repeated Wrong Secrets](https://help.spamfoo.com/api#blocked). | | `500` | An unexpected error inside SpamFoo Client. Check the SpamFoo Client logs. | | `503` | SpamFoo Client is busy. See [Handle Busy Responses](https://help.spamfoo.com/api#busy). | ### Handle Busy Responses SpamFoo Client limits how much work it accepts at once. Every `503` has a `Retry-After` header, and the `code` field says why: - **`server_busy`:** too many requests were in progress. `{"error": "server_busy", "code": "server_busy", "inFlight": 1024}` with `Retry-After: 1`. The request was turned away before SpamFoo Client read the message. The limit is set by `SPAMFOO_MAX_CLASSIFY_INFLIGHT`. Requests marked as bulk have their own, smaller limit, add `"priority": "bulk"`, and get `Retry-After: 2`. See [Mark Bulk Work](https://help.spamfoo.com/api/classify#priority). - **`queue_timeout`:** the request waited too long for a free classification slot. `{"error": "Classification queue timeout", "code": "queue_timeout", "queueDepth": 12}`. `queueDepth` is how many requests were waiting. - **`deadline_exceeded`:** only for requests that send `X-SpamFoo-Deadline-Ms`, when the wait for a slot would be longer than the deadline. See [Set a Deadline](https://help.spamfoo.com/api/classify#deadline). - **`not_ready` or `shutting_down`:** from `/health/ready`, when SpamFoo Client can't classify mail yet or is stopping. See [Check Health](https://help.spamfoo.com/api/health#ready). During mail delivery, treat a `503` as "no verdict" and deliver the message normally rather than holding it. Retry only when your process can wait. To see how close SpamFoo Client runs to these limits, read the queue counters from [Check Health](https://help.spamfoo.com/api/health). ## Endpoints | Method | Path | Purpose | | --- | --- | --- | | `POST` | `/classify-stream` | Get a spam verdict and Email Classification. See [Classify a Message](https://help.spamfoo.com/api/classify). | | `POST` | `/tabs/classify-stream` | Get an Email Classification only. See [Classify Without Spam Checks](https://help.spamfoo.com/api/classify-tabs). | | `POST` | `/feedback-stream` | Report that a user marked a message as spam or not spam. See [Report Spam or Not Spam](https://help.spamfoo.com/api/feedback). | | `POST` | `/tabs/feedback-stream` | Report that a user moved a message to a different Email Classification. See [Report a Classification Fix](https://help.spamfoo.com/api/feedback-tabs). | | `DELETE` | `/personalization/data` `/personalization/tabs/data` | Delete a user's stored corrections. See [Manage Personal Data](https://help.spamfoo.com/api/personal-data). | | `GET` | `/personalization/knn/stats` `/personalization/tabs/knn/stats` | Count a user's stored corrections. See [Manage Personal Data](https://help.spamfoo.com/api/personal-data). | | `GET` | `/health` `/health/live` `/health/ready` | Check service status, readiness, and queue load. See [Check Health](https://help.spamfoo.com/api/health). | | `GET` | `/integration/info` | Read the version, mode, license, and limits of SpamFoo Client. See [Get Integration Info](https://help.spamfoo.com/api/integration-info). | | `GET` `PUT` | `/integration/address-map` | Send or read the map of mailboxes and the addresses that deliver to them. See [Push an Address Map](https://help.spamfoo.com/api/address-map). | | `DELETE` | `/integration/users/{userId}` | Delete everything stored for a user when their mailbox is removed. See [Delete a User](https://help.spamfoo.com/api/personal-data#delete-user). | | `POST` | `/generate-login-link` | Make a one-time dashboard sign-in link. See [Create a Sign-In Link](https://help.spamfoo.com/api/login-link). | | `GET` `PUT` | `/admin/api/settings` | Read or change SpamFoo Client's settings. See [Read and Change Settings](https://help.spamfoo.com/api/settings). | ## Versioning The API has one version number, sent on every response in the `X-SpamFoo-Contract` header, and in the body of `/health/ready` and `/integration/info` as `contract`. The current version is `1`. - Within a version, changes only add things: new optional request headers, new response fields, and new endpoints. Ignore response fields you don't know. - Removing something, or changing what an existing header or field means, raises the version. *** Source: https://help.spamfoo.com/api/classify Last updated: 2026-10-27 # Classify a Message Send a raw message to get a spam verdict and an Email Classification in one call. This is the endpoint a mail server calls while it delivers mail. `POST /classify-stream` ## When to Use It - Use it for every incoming message you want SpamFoo to judge. - To get only an Email Classification with no spam or phishing checks, use [Classify Without Spam Checks](https://help.spamfoo.com/api/classify-tabs). SpamFoo Client records each verdict for the admin console. It also caches verdicts: the same message sent again with the same headers returns the cached verdict until a correction clears the cache. ## Request The body is the raw message (raw .eml content), sent unchanged. See [Send the Message in the Request Body](https://help.spamfoo.com/api#request-style). | Header | Required | Type | Description | | --- | --- | --- | --- | | `Content-Type` | Recommended | string | `message/rfc822` | | `X-SpamFoo-Proxy-Auth` | In standalone mode, or when not loopback | string | The proxy secret. See [Authenticate Requests](https://help.spamfoo.com/api#authentication). | | `X-User-ID` | No | string | The recipient mailbox, usually a full email address. Turns on the user's own corrections and mailbox-level allow and block rules. | | `X-Sender-IP` | No | IPv4 or IPv6 | IP address of the server that delivered the message to you. Used for SPF and IP reputation. If you can't send it, see `relays` in [Share Your Authentication Results](https://help.spamfoo.com/api/classify#boundary). | | `X-Sender-Email` | No | email address | Envelope sender (SMTP `MAIL FROM`). Used for authentication checks. Allow and block rules are only checked when this header is present. | | `X-Recipient-Email` | No | email address | Envelope recipient (SMTP `RCPT TO`). Used for domain-level allow and block rules and for protected identity checks. | | `X-SpamFoo-Classify` | No | `true` or `false` | Send `false` to skip Email Classification. See [Skip Email Classification](https://help.spamfoo.com/api/classify#skip-classification). | | `X-SpamFoo-Log-ID` | No | string, up to 64 characters | Your own ID for the message, written to the classification log. Letters, digits, and `. _ : -` only; other values are ignored. | | `X-SpamFoo-Priority` | No | `bulk` | Marks work that isn't live delivery, such as sorting old mail. See [Mark Bulk Work](https://help.spamfoo.com/api/classify#priority). | | `X-SpamFoo-Deadline-Ms` | No | integer, 1 to 600000 | How many milliseconds you'll wait for the verdict. See [Set a Deadline](https://help.spamfoo.com/api/classify#deadline). | | `X-SpamFoo-Recipient` | No | `
[; user=]`, repeatable | One recipient of the message, to check it once for all its recipients. See [Check Several Recipients at Once](https://help.spamfoo.com/api/classify#recipients). | | `X-SpamFoo-Personalize` | No | `per-recipient` | Adds each recipient's own verdict. Used with `X-SpamFoo-Recipient`. See [Check Several Recipients at Once](https://help.spamfoo.com/api/classify#recipients). | | `X-SpamFoo-Scan` | No | `keep` | Asks for a `scanId` that later calls for each mailbox can reuse. See [Reuse a Scan for Each Mailbox](https://help.spamfoo.com/api/classify#scan-references). | | `X-SpamFoo-Scan-Id` | No | string | A `scanId` from an earlier answer for the same message. See [Reuse a Scan for Each Mailbox](https://help.spamfoo.com/api/classify#scan-references). | | `X-SpamFoo-Boundary` | No | string | Names the `Authentication-Results` headers your mail server added, so SpamFoo Client can trust them. See [Share Your Authentication Results](https://help.spamfoo.com/api/classify#boundary). | | `X-SpamFoo-Auth-Results` | No | string | Your mail server's SPF, DKIM, and DMARC results for this message. See [Share Your Authentication Results](https://help.spamfoo.com/api/classify#boundary). | | `X-SpamFoo-Caller` | No | `/` | Names your integration and its version, such as `mymailserver/2.1`. Use a name that is unique to your integration. It tells SpamFoo Client's logs which integration sent a request, which makes problems easier to trace and helps SpamFoo support. The name is 1 to 32 characters from `a-z 0-9 . _ -`, and the version is 1 to 32 characters from `A-Z a-z 0-9 . _ + -`. | SpamFoo Client reads `X-SpamFoo-Boundary` and `X-SpamFoo-Auth-Results` only from requests that send the correct proxy secret, even from loopback. `X-SpamFoo-` headers inside the message itself are never read as instructions, so a sender can't use them to change the result. ### Skip Email Classification Send `X-SpamFoo-Classify: false` when you only need the spam verdict. Spam and phishing checks still run. - The response has the same fields, but `classification` is an empty string and `tabsProbs` is an empty array, even for spam and phishing. Use `isSpam`, `bypassReason`, and `phishingReason` instead. - Values are case-insensitive. Any value other than one `true` or `false` returns `400`. - If the license includes Email Classification but not spam detection, the request returns `403` with `"features": ["spam"]`. ### Mark Bulk Work Send `X-SpamFoo-Priority: bulk` when the request isn't part of live mail delivery, such as when you classify the messages already in a mailbox. Bulk requests have their own, smaller limit on how many can be held at once (one less than Max Concurrency, and at least 1), so they can't crowd out live mail. - When the bulk limit is full, the request gets `503` with `"code": "server_busy"`, `"priority": "bulk"`, and `Retry-After: 2`. Wait and send it again. - Any other value, or no header, is live delivery. - The `bulkIntake` counts in [Check Health](https://help.spamfoo.com/api/health#health) show how the bulk limit is used. ### Set a Deadline Send `X-SpamFoo-Deadline-Ms` when your process stops waiting after a set time, such as an SMTP session timeout. The time counts from when SpamFoo Client reads the header, so time spent sending the message counts against it. - When SpamFoo Client expects the wait for a free classification slot to be longer than the time left, it answers right away with `503` and `"code": "deadline_exceeded"`. It also stops waiting when the deadline passes in the queue, and skips work for a caller that has disconnected. - The body is `{"error": "Classification deadline exceeded", "code": "deadline_exceeded", "deadlineMs": 5000, "queueDepth": 8}` with `Retry-After: 1`. Deliver the message normally. - Once classification starts, it runs to the end. The deadline covers only the wait. - The value is a whole number of milliseconds from 1 to 600000. Any other value, or the header sent more than once, returns `400`. - Without the header, nothing changes. A wait that reaches the normal queue timeout first still returns `queue_timeout`. ### Check Several Recipients at Once A mail server that checks a message during the SMTP session sees all of its recipients at once. Send one `X-SpamFoo-Recipient` header per recipient, or one header with a comma-separated list, to check the message once for all of them. - Each value is a recipient address. Add `; user=` when that mailbox's user ID isn't the address, such as `X-SpamFoo-Recipient: sales@example.com; user=ann@example.com`. A value without a valid address returns `400`. - The models run once. The top-level answer is the verdict without any user's corrections or mailbox rules, so `X-User-ID` and `X-Recipient-Email` are ignored on these calls. - The answer adds `recipients`, with one entry per recipient, and `recipientsTruncated`. Only the first 100 recipients are used. When there are more, `recipientsTruncated` is `true`, so don't treat the list as complete. - Each entry's `userId` is the `user=` value, or else the address, changed to the owning user when your [address map](https://help.spamfoo.com/api/address-map) lists it as an alias. - Each entry's `rule` says whether an allow or block rule applies to the sender for that recipient: `action` is `allow` or `block`, and `scope` is `mailbox` or `domain` for rules an administrator set, or `user` for the user's own spam rule. It's `null` when no rule applies. - Send `X-SpamFoo-Personalize: per-recipient` to add each recipient's own `verdict` and `stampHeaders`, with that user's corrections and rules applied. Without `X-SpamFoo-Recipient` it has no effect. Any other value returns `400`. - When recipients get different verdicts, how to act is up to you. Per-recipient verdicts aren't recorded for the admin console. Only a call for one mailbox, with `X-User-ID`, is. ``` "recipients": [ { "address": "ann@example.com", "userId": "ann@example.com", "rule": { "action": "block", "scope": "user" }, "verdict": { ... }, "stampHeaders": [ ... ] } ], "recipientsTruncated": false ``` ### Reuse a Scan for Each Mailbox A mail server that checks a message once during the SMTP session, and then again for each mailbox at delivery, can skip most of the second check: 1. Send the first request without `X-User-ID`, and add `X-SpamFoo-Scan: keep`. The answer includes a `scanId`. 2. For each mailbox, send the same message with `X-User-ID` and `X-SpamFoo-Scan-Id` set to that `scanId`. - When SpamFoo Client still holds the scan, it runs only that user's steps: impersonation checks for their domain, their rules and corrections, and their classification corrections. The answer adds `"scanReused": true`. Otherwise it checks the message in full, as if the header were absent. - `X-SpamFoo-Scan` is ignored on a request with `X-User-ID`. Any value other than `keep` returns `400`. - The answer can leave out `scanId`, such as when a rule decided the message or the license doesn't include spam detection. Classify each mailbox normally then. - Scans are kept in memory for up to 15 minutes, and a restart drops them. SpamFoo Client can drop the oldest sooner when it holds many. - A scan is tied to the message body, not its headers. Headers your mail server adds on the way to the mailbox, and LF line endings in place of CRLF, don't break it. A `scanId` sent with a different message only causes a full check. - A reused result keeps the scores the message got in the first scan, including the phishing detection model's score. - The first scan counts toward the admin console statistics and sender reputation. Results built on it are recorded for each mailbox but not counted again. ### Share Your Authentication Results If your mail server already checks SPF, DKIM, and DMARC (the checks that confirm a message came from the domain it claims), it can tell SpamFoo Client its results. SpamFoo Client then treats those results as trusted, and can skip its own DNS lookups when your results are clear. Without these headers, SpamFoo Client doesn't trust any `Authentication-Results`, `ARC-Authentication-Results`, `Received-SPF` or `X-SmarterMail-` header in the message, because a sender can add one. It runs its own checks instead when the request has `X-Sender-IP` and `X-Sender-Email`, and otherwise reports authentication as unknown. - **`X-SpamFoo-Boundary: authserv-id=`**, such as `X-SpamFoo-Boundary: authserv-id=mx.example.com`. Your mail server adds `Authentication-Results` headers to the top of each message, using `` as its authserv-id (the name at the start of the header value, usually the server's host name). SpamFoo Client trusts the run of `Authentication-Results` headers with that id at the top of the message. It stops at the first header with another id or at the next `Received` header. The id is up to 255 characters from `A-Z a-z 0-9 . - _ :`. - **`X-SpamFoo-Boundary: authserv-id=; relays=,...`**, such as `authserv-id=mx.example.com; relays=172.22.1.0/24,fd4d:6169:6c63:6f77::/64`. Use this when you can't send `X-Sender-IP`. List your own mail hosts (up to 16 IPv4 or IPv6 ranges or addresses). When the request has no `X-Sender-IP`, SpamFoo Client reads the `Received` headers from the top, skips each hop that came from one of your relays, and uses the IP of the first hop from outside them as the sender IP. Your mail server must add the topmost `Received` header. If SpamFoo Client can't read a trustworthy IP, the message is classified with no sender IP. This works on `/classify-stream` only. - **`X-SpamFoo-Boundary: none`**. Trust no authentication results in the message. - **`X-SpamFoo-Auth-Results`**. Your results for this message in the `Authentication-Results` format, such as `spf=pass smtp.mailfrom=example.com; dkim=pass header.d=example.com; dmarc=pass header.from=example.com`. It must include at least one `spf`, `dkim`, or `dmarc` result, and be at most 2048 characters. These results take the place of the ones in the message. You can send it with or without `X-SpamFoo-Boundary`. A value SpamFoo Client can't read is treated as `none`, and the request is still classified. Send each header at most once. These headers change which authentication results SpamFoo Client trusts, and so `senderAuthenticated` and the three auth scores in the result. They don't change how the models read the message. ## What SpamFoo Client Checks SpamFoo Client stops at the first check that decides the message. `bypassReason` in the response tells you which one it was. 1. Allow and block rules an administrator set for the recipient's mailbox or domain. 2. Whether the message can be parsed. A message that cannot be parsed is treated as spam. 3. Phishing checks: links to known phishing sites, impersonation of protected people (when the license includes SpamFoo Anti-Phishing), and sender domains that misspell well-known domains. 4. The user's own past spam and not-spam corrections, when `X-User-ID` is sent. 5. SpamFoo's permanent allow and block entries for the sender's domain or IP address. 6. The global spam models. For mail that is not spam, the Email Classification model, or the user's own classification corrections. 7. The Anti-Phishing model, when the license includes SpamFoo Anti-Phishing and an administrator has not turned it off. Allow and block rules that apply to all mail are checked after the models run. A system-wide allow rule gives way when the user's own corrections for the same sender say the message is spam. See [Manage Spam Rules and Senders](https://help.spamfoo.com/guides/rules-protection) and [Protect Against Phishing](https://help.spamfoo.com/guides/phishing-protection). ## How the License Changes the Result | License Includes | What Runs | Result | | --- | --- | --- | | Spam detection and Email Classification | Everything | Full result. | | Spam detection only | Spam and phishing checks | `classification` is `spam` or `phishing` for spam, and empty otherwise. `tabsProbs` is empty. | | Email Classification only | Email Classification, plus allow and block rules | No spam or phishing checks run. `isSpam` is `false` and the spam scores are `0`, unless an allow or block rule decided the message. `confidenceMargin` holds the classification confidence. | | License expired | Nothing | `403` with `"error": "feature_not_licensed"` until a valid license is active. | See [Use Classification Only](https://help.spamfoo.com/integrations/classification-only) for an integration built on a classification-only license. ## Response A successful request returns `200` with a JSON object. This example is a message that is not spam: ``` { "isSpam": false, "classification": "transactions", "spamProbability": 0.031, "displayConfidence": 0.027, "spamLevel": null, "textSpamScore": 0.052, "metaSpamScore": 0.018, "confidenceMargin": 0.469, "tabsProbs": [0.08, 0.86, 0.04, 0.02], "entropy": 0.138, "disagreement": 0.034, "shouldEscalate": false, "bypassReason": null, "spamBypassReason": null, "knnThresholdShift": 0, "reputationBypass": false, "phishingReason": null, "phishingScore": null, "phishingThreatType": null, "phishingEncoderVersion": null, "senderAuthenticated": true, "spfScore": 1, "dkimScore": 1, "dmarcScore": 1, "attribution": "FromDmarc", "dmarcRecord": "RecordPresent", "attributedFromDomain": "example.com", "attributedEnvelopeDomain": null, "envelopeAttribution": "EnvelopeSpf", "verdict": { "spam": false, "spamLevel": null, "classification": "transactions", "confidence": 0.973, "personalized": false, "decidedBy": "model", "phishing": null }, "stampHeaders": [ ["X-SpamFoo-Status", "ok"], ["X-SpamFoo-Spam", "no"], ["X-SpamFoo-Classification", "transactions"], ["X-SpamFoo-Confidence", "0.97"], ["X-SpamFoo-Personalized", "no"] ] } ``` `verdict` and `stampHeaders` are the simplest fields to act on. See [Verdict](https://help.spamfoo.com/api/classify#verdict). Ignore any field you don't know, because new fields can be added. | Field | Type | Description | | --- | --- | --- | | `isSpam` | boolean | The spam verdict. Base delivery decisions on this field or on `verdict`. | | `classification` | string | For mail that is not spam: `primary`, `transactions`, `updates`, or `promotions`. For spam: `spam` or `phishing`. Empty when no Email Classification ran, such as when an allow or block rule decided the message. Compare values without regard to case. | | `spamProbability` | number | Spam score from 0 to 1. The spam threshold is not 0.5, so use `isSpam` for the verdict. When a check other than the global models decided, this is that check's own score, such as `1` for a block rule. | | `displayConfidence` | number or null | `spamProbability` rescaled so the threshold sits at 0.5: spam is 0.5 or higher and other mail is 0.5 or lower. Use it when you show a score to people. `1` for the known-site, impersonation, and lookalike-domain phishing checks, and `null` when any other check decided. | | `spamLevel` | string or null | The global models' own spam grade, such as `Spam-High`. `null` when another check decided. Read `verdict.spamLevel` instead. | | `textSpamScore` | number | Score from the model that reads the message text. | | `metaSpamScore` | number | Score from the model that reads headers, authentication, and other metadata. | | `confidenceMargin` | number | When the global models decide, how far `spamProbability` is from 0.5. Other checks set their own value, such as `1` for a rule. | | `tabsProbs` | array of numbers | Email Classification probabilities in the order primary, transactions, updates, promotions. Empty when no Email Classification ran. | | `entropy` | number | How uncertain the spam score is. Higher means less certain. | | `disagreement` | number | Difference between `textSpamScore` and `metaSpamScore`. | | `shouldEscalate` | boolean | `true` when the global models were unsure or disagreed. You can use it to flag a message for review. | | `bypassReason` | string or null | Which check decided the message when it was not the global models. See [Bypass Reasons](https://help.spamfoo.com/api/classify#bypass-reasons). | | `spamBypassReason` | string or null | Set when the user's corrections or a SpamFoo permanent allow entry let the message through. It keeps that reason even when `bypassReason` shows `tabs_knn`. `null` otherwise. | | `knnThresholdShift` | number | How far the user's past corrections moved the spam threshold without deciding the message on their own. Negative leans toward spam, positive toward not spam, `0` when unused. | | `reputationBypass` | boolean | `true` when a SpamFoo permanent allow or block entry decided the message. | | `phishingReason` | array of strings or null | Why the message is phishing: `phishtank_match`, `identity_impersonation`, `identity_impersonation_lookalike`, or `domain_typosquat`. When the Anti-Phishing model flags a message, this is `["identity_impersonation_lookalike"]`. | | `phishingScore` | number or null | Score from the Anti-Phishing model. `null` when the model did not run. | | `phishingThreatType` | string or null | The Anti-Phishing model's threat type: `PHISHING`, `SCAM`, `UNWANTED`, or `NONE`. | | `phishingEncoderVersion` | string or null | Name of the Anti-Phishing model that scored the message. | | `senderAuthenticated` | boolean | `true` when DMARC passed, or when the domain has no DMARC result and SPF passed for a Return-Path that matches the From domain. `false` when authentication was not checked. Based only on trusted results and SpamFoo Client's own checks (see [Share Your Authentication Results](https://help.spamfoo.com/api/classify#boundary)). | | `spfScore` `dkimScore` `dmarcScore` | number or null | Authentication results: `1` pass, `0.5` neutral or no record, `0.3` softfail, `0.2` permerror, `0` fail. `null` when not checked. Like `senderAuthenticated`, they come only from trusted results and SpamFoo Client's own checks. | | `attribution` `dmarcRecord` `attributedFromDomain` `attributedEnvelopeDomain` `envelopeAttribution` | string or null | Which sender domain SpamFoo Client proved, and how. SpamFoo uses these for domain reputation. You don't need them to act on the verdict. | | `verdict` | object | The verdict in a short, stable form. See [Verdict](https://help.spamfoo.com/api/classify#verdict). | | `stampHeaders` | array of [name, value] pairs | Headers you can add to the delivered message. See [Stamped Headers](https://help.spamfoo.com/api/classify#stamp-headers). | | `scanId` | string | Only with `X-SpamFoo-Scan: keep`. See [Reuse a Scan for Each Mailbox](https://help.spamfoo.com/api/classify#scan-references). | | `scanReused` | boolean | `true` when the result was built on a kept scan. Left out otherwise. | | `recipients` `recipientsTruncated` | array, boolean | Only with `X-SpamFoo-Recipient`. See [Check Several Recipients at Once](https://help.spamfoo.com/api/classify#recipients). | ### Verdict All `verdict` values are lowercase, so you can compare them as plain text. | Field | Type | Description | | --- | --- | --- | | `spam` | boolean | Same as `isSpam`. | | `spamLevel` | string or null | `low`, `medium`, or `high` when `spam` is `true`, otherwise `null`. See [Spam Levels](https://help.spamfoo.com/api/classify#spam-levels). | | `classification` | string or null | `primary`, `transactions`, `updates`, `promotions`, `spam`, or `phishing`. `null` when no Email Classification ran on mail that is not spam. | | `confidence` | number | How sure SpamFoo Client is of the verdict it gave, from 0 to 1, for spam and not spam alike. `1` for rules. | | `personalized` | boolean | `true` when the user's own rules or corrections took part in the result. | | `decidedBy` | string | `model`, `user_rule`, `user_memory` (the user's corrections), `system_rule` (an allow or block rule from the admin console), `reputation`, `phishing`, `parse_error`, or `other`. | | `phishing` | object or null | `{"score": ..., "threatType": ...}` from the phishing detection model, or `null` when it didn't run. | ### Stamped Headers `stampHeaders` lists headers you can add to the copy of the message you deliver, so mail filters and Sieve scripts can act on the verdict. Add them in the order given. Remove any `X-SpamFoo-` headers that arrived with the message first. Otherwise a sender could write `X-SpamFoo-Spam: no` and fool a filter. | Header | Values | | --- | --- | | `X-SpamFoo-Status` | `ok`. When your integration couldn't classify a message, it can write its own value, such as `skipped; reason=timeout`. | | `X-SpamFoo-Spam` | `yes` or `no`. | | `X-SpamFoo-Spam-Level` | `low`, `medium`, or `high`. Left out when the message is not spam. | | `X-SpamFoo-Classification` | The value of `verdict.classification`. Left out when it's `null`. | | `X-SpamFoo-Confidence` | `verdict.confidence` with two decimals, such as `0.97`. | | `X-SpamFoo-Personalized` | `yes` or `no`. | ### Spam Levels SpamFoo Client grades each spam verdict, so every mail server sees the same grade for the same message. The grade says how strict a spam cutoff the message still counts as spam at: - **`high`:** spam even at the strictest cutoff. Spam decided by something other than the global models, such as a rule, the user's corrections, a SpamFoo permanent block entry, or a phishing check, is also `high`. - **`medium`:** spam at the normal cutoff, but not the strictest one. - **`low`:** spam only at the loosest cutoff. The **Spam Sensitivity** setting on **System > Settings** decides which grades count as spam at all. **Conservative** marks only `high` as spam, **Recommended** marks `medium` and `high`, and **Aggressive** marks all three. A message that is not spam has no grade. SpamFoo Client only grades. Your integration decides what to do with each grade. A recommended starting point: | Result | Recommended Starting Action | | --- | --- | | Not spam | Deliver. Add the stamped headers only. | | `low` | Deliver to the inbox with a tag. Don't file to Junk on this grade alone. | | `medium` | File to Junk. | | `high` | File to Junk. If the administrator opts in, your mail server can treat it as its own highest spam level. | | `phishing` classification | File to Junk. SpamFoo doesn't yet recommend rejecting phishing during the SMTP session. | ### Bypass Reasons | Value | Meaning | | --- | --- | | `null` | The global models decided. | | `system_override` | An allow or block rule from the admin console decided. `classification` is empty. | | `parse_error` | The message could not be parsed, so it was marked as spam. `classification` is `Spam`. | | `phishtank_match` | The message links to a known phishing site. | | `identity_impersonation` | The sender pretends to be a protected person from outside that person's real domains. | | `identity_impersonation_lookalike` | The sender pretends to be a protected person from a domain that misspells or imitates one of that person's real domains. | | `domain_typosquat` | The sender's domain is a misspelling of a well-known domain. | | `knn_memory` | The user's past corrections for messages from the same sender decided. | | `knn_memory_cross_sender` | The user's past corrections of very similar messages from other senders decided. | | `reputation_allow` | A SpamFoo permanent allow entry for the sender's IP address or authenticated domain. The spam models were skipped. | | `reputation_block` | A SpamFoo permanent block entry for the sender's domain or IP address. | | `tabs_knn` | The user's past classification corrections set the Email Classification. Check `spamBypassReason` for what decided spam or not spam. | | `phishing_encoder` | The Anti-Phishing model flagged the message as phishing. | ## Errors | Status | Body | Cause | | --- | --- | --- | | `400` | `{"error": "No email content provided"}` | The body was empty. | | `400` | `{"error": "X-SpamFoo-Classify must be true or false"}` | The header had another value, or was sent more than once. | | `400` | `{"error": "X-SpamFoo-... must be ..."}` | An `X-SpamFoo-Recipient`, `X-SpamFoo-Personalize`, `X-SpamFoo-Scan`, or `X-SpamFoo-Deadline-Ms` value isn't valid. | | `401` | `{"error": "unauthorized"}` | The proxy secret was missing or wrong, and the request needed it. | | `403` | `{"error": "feature_not_licensed", "features": [...]}` | The license includes neither spam detection nor Email Classification, `X-SpamFoo-Classify: false` was sent without spam detection in the license, or the license has expired. | | `413` | `{"error": "request_body_too_large", "maxBytes": ...}` | The message is over the body size limit. | | `429` | `{"error": "...", "code": "auth_blocked"}` | Standalone mode only. Too many wrong secrets came from this address. See [Repeated Wrong Secrets](https://help.spamfoo.com/api#blocked). | | `503` | `{"error": "server_busy", "code": "server_busy", ...}` or `{"error": "Classification queue timeout", "code": "queue_timeout", ...}` | SpamFoo Client is busy. Deliver the message normally. See [Handle Busy Responses](https://help.spamfoo.com/api#busy). | | `503` | `{"error": "Classification deadline exceeded", "code": "deadline_exceeded", ...}` | Only with `X-SpamFoo-Deadline-Ms`. The deadline couldn't be met. Deliver the message normally. See [Set a Deadline](https://help.spamfoo.com/api/classify#deadline). | ## Example ### curl ``` curl -X POST http://localhost:16253/classify-stream \ -H "Content-Type: message/rfc822" \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "X-User-ID: user@example.com" \ -H "X-Sender-IP: 203.0.113.50" \ -H "X-Sender-Email: sender@example.org" \ -H "X-Recipient-Email: user@example.com" \ -H "X-SpamFoo-Log-ID: queue-4F2A91" \ --data-binary @message.eml ``` ### C# ``` using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") }; await using var eml = File.OpenRead("message.eml"); var content = new StreamContent(eml); content.Headers.ContentType = new MediaTypeHeaderValue("message/rfc822"); using var request = new HttpRequestMessage(HttpMethod.Post, "/classify-stream") { Content = content }; request.Headers.Add("X-SpamFoo-Proxy-Auth", Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET")); request.Headers.Add("X-User-ID", "user@example.com"); request.Headers.Add("X-Sender-IP", "203.0.113.50"); request.Headers.Add("X-Sender-Email", "sender@example.org"); request.Headers.Add("X-Recipient-Email", "user@example.com"); request.Headers.Add("X-SpamFoo-Log-ID", "queue-4F2A91"); using var response = await http.SendAsync(request); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(); var isSpam = result.GetProperty("isSpam").GetBoolean(); var classification = result.GetProperty("classification").GetString(); Console.WriteLine($"isSpam={isSpam} classification={classification}"); ``` ### Python ``` import os import requests headers = { "Content-Type": "message/rfc822", "X-SpamFoo-Proxy-Auth": os.environ.get("SPAMFOO_PROXY_SECRET", ""), "X-User-ID": "user@example.com", "X-Sender-IP": "203.0.113.50", "X-Sender-Email": "sender@example.org", "X-Recipient-Email": "user@example.com", "X-SpamFoo-Log-ID": "queue-4F2A91", } with open("message.eml", "rb") as eml: response = requests.post( "http://localhost:16253/classify-stream", data=eml, headers=headers, timeout=60, ) response.raise_for_status() result = response.json() print(f"isSpam={result['isSpam']} classification={result['classification']}") ``` *** Source: https://help.spamfoo.com/api/classify-tabs Last updated: 2026-10-27 # Classify Without Spam Checks Send a raw message to get only its Email Classification: `primary`, `transactions`, `updates`, or `promotions`. No spam, phishing, or allow and block checks run. `POST /tabs/classify-stream` ## When to Use It - Another system already filters spam, and you only want SpamFoo to sort the mail that is left. - The message is already known not to be spam, and you want to file it. - For a spam verdict, or a spam verdict and Email Classification together, use [Classify a Message](https://help.spamfoo.com/api/classify) instead. Requests run on SpamFoo Client's background queue, separate from the queue that `/classify-stream` uses. The license must include Email Classification. When SpamFoo Client has not yet confirmed the license, the request still runs. When its saved license information has expired, the request still runs in embedded mode, and returns `403` in standalone mode. SpamFoo Client records each result for the admin console. The same message for the same `X-User-ID` is recorded only once in a five-minute window, but it is still classified every time. ## Request The body is the raw message (raw .eml content), sent unchanged. See [Send the Message in the Request Body](https://help.spamfoo.com/api#request-style). | Header | Required | Type | Description | | --- | --- | --- | --- | | `Content-Type` | Recommended | string | `message/rfc822` | | `X-SpamFoo-Proxy-Auth` | In standalone mode, or when not loopback | string | The proxy secret. See [Authenticate Requests](https://help.spamfoo.com/api#authentication). | | `X-User-ID` | No | string | The recipient mailbox, usually a full email address. When sent, the user's own classification corrections can set the result. | | `X-Sender-IP` | No | IPv4 or IPv6 | IP address of the server that delivered the message. Only recorded with the result for the admin console. If omitted, SpamFoo Client reads it from the message headers. | | `X-Sender-Email` | No | email address | Envelope sender. Only recorded with the result for the admin console. If omitted, SpamFoo Client reads it from the message headers. | | `X-SpamFoo-Log-ID` | No | string, up to 64 characters | Your own ID for the message, written to the classification log. Letters, digits, and `. _ : -` only; other values are ignored. | | `X-SpamFoo-Priority` | No | `bulk` | Marks work that isn't live delivery, such as sorting a mailbox's old mail. See [Mark Bulk Work](https://help.spamfoo.com/api/classify#priority). | | `X-SpamFoo-Deadline-Ms` | No | integer, 1 to 600000 | How many milliseconds you'll wait for the result. When the background queue can't meet it, the request gets `503` with `"code": "deadline_exceeded"` instead of waiting. See [Set a Deadline](https://help.spamfoo.com/api/classify#deadline). | This endpoint ignores `X-SpamFoo-Classify` and `X-Recipient-Email`. ## Response A successful request returns `200` with a JSON object: ``` { "classification": "promotions", "confidence": 0.91, "classificationProbabilities": { "primary": 0.04, "transactions": 0.02, "updates": 0.03, "promotions": 0.91 }, "personalizationSource": null, "elapsedMs": 41 } ``` | Field | Type | Description | | --- | --- | --- | | `classification` | string | `primary`, `transactions`, `updates`, or `promotions`. SpamFoo Client picks `primary` when the model is not confident, or when `primary` scored close to the top classification. | | `confidence` | number | The highest probability the model gave any classification, from 0 to 1. When SpamFoo Client falls back to `primary`, this can be another classification's probability. `1` when the user's corrections decided. | | `classificationProbabilities` | object | Probability for each of the four classifications, keyed by name. | | `personalizationSource` | string or null | `tabs_knn` when the user's past classification corrections set the result. `null` when the global model decided. | | `elapsedMs` | integer | Time SpamFoo Client spent classifying, in milliseconds. Time spent waiting in the queue is not included. | ## Errors | Status | Body | Cause | | --- | --- | --- | | `400` | `{"error": "No email content provided"}` | The body was empty. | | `400` | `{"error": "X-SpamFoo-Deadline-Ms must be ..."}` | `X-SpamFoo-Deadline-Ms` isn't a whole number from 1 to 600000, or was sent more than once. | | `401` | `{"error": "unauthorized"}` | The proxy secret was missing or wrong, and the request needed it. | | `403` | `{"error": "feature_not_licensed", "features": ["classification"]}` | The license does not include Email Classification, or, in standalone mode, the license has expired. | | `413` | `{"error": "request_body_too_large", "maxBytes": ...}` | The message is over the body size limit. | | `429` | `{"error": "...", "code": "auth_blocked"}` | Too many wrong secrets came from this address. See [Repeated Wrong Secrets](https://help.spamfoo.com/api#blocked). | | `503` | `{"error": "server_busy", "code": "server_busy", ...}` or `{"error": "Classification queue timeout", "code": "queue_timeout", ...}` | SpamFoo Client is busy. See [Handle Busy Responses](https://help.spamfoo.com/api#busy). | | `503` | `{"error": "Classification deadline exceeded", "code": "deadline_exceeded", ...}` | Only with `X-SpamFoo-Deadline-Ms`. The deadline couldn't be met. See [Set a Deadline](https://help.spamfoo.com/api/classify#deadline). | ## Example ### curl ``` curl -X POST http://localhost:16253/tabs/classify-stream \ -H "Content-Type: message/rfc822" \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "X-User-ID: user@example.com" \ --data-binary @message.eml ``` ### C# ``` using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") }; await using var eml = File.OpenRead("message.eml"); var content = new StreamContent(eml); content.Headers.ContentType = new MediaTypeHeaderValue("message/rfc822"); using var request = new HttpRequestMessage(HttpMethod.Post, "/tabs/classify-stream") { Content = content }; request.Headers.Add("X-SpamFoo-Proxy-Auth", Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET")); request.Headers.Add("X-User-ID", "user@example.com"); using var response = await http.SendAsync(request); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(); var classification = result.GetProperty("classification").GetString(); var confidence = result.GetProperty("confidence").GetDouble(); Console.WriteLine($"classification={classification} confidence={confidence:F2}"); ``` ### Python ``` import os import requests headers = { "Content-Type": "message/rfc822", "X-SpamFoo-Proxy-Auth": os.environ.get("SPAMFOO_PROXY_SECRET", ""), "X-User-ID": "user@example.com", } with open("message.eml", "rb") as eml: response = requests.post( "http://localhost:16253/tabs/classify-stream", data=eml, headers=headers, timeout=60, ) response.raise_for_status() result = response.json() print(f"classification={result['classification']} confidence={result['confidence']:.2f}") ``` *** Source: https://help.spamfoo.com/api/feedback Last updated: 2026-10-27 # Report Spam or Not Spam Tell SpamFoo Client that a user marked a message as spam, or rescued it from junk as not spam. SpamFoo Client learns from the correction for that user and may report it to SpamFoo to improve the global models. `POST /feedback-stream` ## When to Use It - A user moves a message into the junk folder, or clicks a "Mark as spam" control. - A user moves a message out of the junk folder, or clicks a "Not spam" control. - To report that a message belongs in a different Email Classification, use [Report a Classification Fix](https://help.spamfoo.com/api/feedback-tabs) instead. What SpamFoo Client does with a correction: - **Learns for the user:** it stores an embedding of the message (a list of numbers that represents its content, not the text) in the user's correction memory. Later messages from the same sender, or very similar messages, are judged with that correction in mind. See [How Personalization Works](https://help.spamfoo.com/guides/personalization). - **Keeps a local copy:** it saves a compressed copy of the corrected message on the SpamFoo Client server, so the correction can be rebuilt when the embedding model changes. - **Reports model mistakes:** when the correction disagrees with what the global models decided, SpamFoo Client queues an anonymized correction for SpamFoo. The user ID is hashed first. It skips the report when the user's own past corrections decided the message, or when the global models already agreed. See [How Corrections Are Used](https://help.spamfoo.com/guides/corrections). - **Feeds reputation:** when SpamFoo Client reports to SpamFoo, the correction also counts toward the reputation of the IP address in `X-Sender-IP` and, when the message passed DMARC or aligned SPF, the sender's domain. Send the message the user corrected, as it is stored in their mailbox. This endpoint has no license check. ## Request The body is the raw message (raw .eml content), sent unchanged. See [Send the Message in the Request Body](https://help.spamfoo.com/api#request-style). | Header | Required | Type | Description | | --- | --- | --- | --- | | `Content-Type` | Recommended | string | `message/rfc822` | | `X-SpamFoo-Proxy-Auth` | Yes | string | The proxy secret, required even from loopback. See [Authenticate Requests](https://help.spamfoo.com/api#authentication). | | `X-SpamFoo-Priority` | No | `bulk` | Send `bulk` for corrections that aren't from a user acting right now, such as a batch import. They run on the background queue with their own limit, so they can't slow down live mail. Without it, the correction is handled right away. | | `X-User-ID` | Yes | string | The user who made the correction. Use the same value you send as `X-User-ID` when classifying. | | `X-Is-Spam` | Yes | `true` or `false` | `true` when the user marked the message as spam. `false` when the user marked it as not spam. | | `X-Original-Is-Spam` | No | `true` or `false` | The `isSpam` value SpamFoo returned when the message arrived. If you omit it, SpamFoo Client scores the message again at correction time and uses that verdict instead, which can differ from the original one. | | `X-Sender-IP` | No | IPv4 or IPv6 | IP address of the server that delivered the message. Used for authentication checks and IP reputation. | | `X-Sender-Email` | No | email address | Envelope sender (SMTP `MAIL FROM`). Used for authentication checks. | | `X-Send-Email-Text` | No | `true` or `false` | Send `true` only when the user agreed to share message content. The correction report to SpamFoo then includes the message text, with email addresses, phone numbers, card numbers, and similar details replaced by placeholders, plus phishing details built from the message's text, headers, and links. Default `false`. | | `X-Subject-Strip-Prefixes` | No | string, repeatable | A literal tag your mail server adds to subjects, such as `[SPAM]`. SpamFoo Client removes it before it analyzes or reports the subject. Send the header once per tag. A single JSON array of strings, such as `["[SPAM]", "[EXTERNAL]"]`, is also accepted. | | `X-SpamFoo-Dont-Report` | No | `true` or `false` | Send `true` when the admin has turned off reporting to SpamFoo. The correction still trains the user's local memory, but nothing about it is sent to SpamFoo. Default `false`. Any other value is rejected with `400 Bad Request`. | | `X-SpamFoo-Event-Id` | No | string, up to 128 characters | Your own ID for this correction, so a retry isn't applied twice. See [Send a Correction More Than Once](https://help.spamfoo.com/api/feedback#duplicates). | ## Response A successful request returns `200`: ``` { "status": "personalization_only", "userId": "user@example.com" } ``` | Field | Type | Description | | --- | --- | --- | | `status` | string | Always `personalization_only` on success. It does not tell you whether a report was queued for SpamFoo. | | `userId` | string | The `X-User-ID` you sent. | ### Send a Correction More Than Once If your integration can send the same correction twice, such as a retry after a lost answer, add `X-SpamFoo-Event-Id` with an ID that stays the same across retries. - The ID is 1 to 128 printable ASCII characters with no spaces. An ID that doesn't fit is ignored, and the correction is applied normally. - A repeat of an ID that was already applied for the same user on this endpoint returns `200` with `{"status": "duplicate", "duplicate": true, "userId": "user@example.com"}` and changes nothing. - An ID counts as used only after its correction succeeds, so you can retry a failed attempt with the same ID. - SpamFoo Client remembers IDs in memory for 24 hours. A restart forgets them. - Without the header, every correction is applied, because a user can correct the same message more than once. ## Errors | Status | Body | Cause | | --- | --- | --- | | `400` | `{"error": "X-User-ID header is required"}` | `X-User-ID` was missing or blank. | | `400` | `{"error": "X-Is-Spam header is required (true/false)"}` | `X-Is-Spam` was missing or not `true` or `false`. | | `400` | `{"error": "No email content provided"}` | The body was empty. | | `400` | `{"error": "Email could not be parsed"}` | The body is not a readable message. | | `401` | Empty | The proxy secret was missing or wrong. | | `413` | `{"error": "request_body_too_large", "maxBytes": ...}` | The message is over the body size limit. | | `429` | `{"error": "...", "code": "auth_blocked"}` | Too many wrong secrets came from this address. See [Repeated Wrong Secrets](https://help.spamfoo.com/api#blocked). | | `503` | `{"error": "server_busy", "code": "server_busy", ...}` or `{"error": "Classification queue timeout", "code": "queue_timeout", ...}` | Only with `X-SpamFoo-Priority: bulk`. SpamFoo Client is busy. Wait the seconds in `Retry-After` and send it again. | ## Example This example reports that a user rescued a message from junk, and that SpamFoo had originally marked it as spam. ### curl ``` curl -X POST http://localhost:16253/feedback-stream \ -H "Content-Type: message/rfc822" \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "X-User-ID: user@example.com" \ -H "X-Is-Spam: false" \ -H "X-Original-Is-Spam: true" \ -H "X-Sender-IP: 203.0.113.50" \ -H "X-Sender-Email: sender@example.org" \ --data-binary @message.eml ``` ### C# ``` using System.Net.Http.Headers; var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET") ?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET."); using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") }; await using var eml = File.OpenRead("message.eml"); var content = new StreamContent(eml); content.Headers.ContentType = new MediaTypeHeaderValue("message/rfc822"); using var request = new HttpRequestMessage(HttpMethod.Post, "/feedback-stream") { Content = content }; request.Headers.Add("X-SpamFoo-Proxy-Auth", secret); request.Headers.Add("X-User-ID", "user@example.com"); request.Headers.Add("X-Is-Spam", "false"); request.Headers.Add("X-Original-Is-Spam", "true"); request.Headers.Add("X-Sender-IP", "203.0.113.50"); request.Headers.Add("X-Sender-Email", "sender@example.org"); using var response = await http.SendAsync(request); Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}"); ``` ### Python ``` import os import requests headers = { "Content-Type": "message/rfc822", "X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"], "X-User-ID": "user@example.com", "X-Is-Spam": "false", "X-Original-Is-Spam": "true", "X-Sender-IP": "203.0.113.50", "X-Sender-Email": "sender@example.org", } with open("message.eml", "rb") as eml: response = requests.post( "http://localhost:16253/feedback-stream", data=eml, headers=headers, timeout=60, ) print(response.status_code, response.text) ``` *** Source: https://help.spamfoo.com/api/feedback-tabs Last updated: 2026-10-27 # Report a Classification Fix Tell SpamFoo Client that a user moved a message to a different Email Classification, for example from Promotions to Primary. SpamFoo Client learns from the fix for that user and may report it to SpamFoo to improve the global model. `POST /tabs/feedback-stream` ## When to Use It - A user drags a message to another classification tab or folder. - A user picks a new classification for a message from a menu. - To report spam or not spam, use [Report Spam or Not Spam](https://help.spamfoo.com/api/feedback) instead. What SpamFoo Client does with a fix: - **Learns for the user:** it stores an embedding of the message (a list of numbers that represents its content) with the new classification in the user's classification memory. Later messages from the same sender, or very similar messages, can get that classification. See [How Personalization Works](https://help.spamfoo.com/guides/personalization). - **Keeps a local copy:** it saves a compressed copy of the message on the SpamFoo Client server, so the fix can be rebuilt when the embedding model changes. - **Reports model mistakes:** when the new classification differs from the original one, SpamFoo Client queues an anonymized correction for SpamFoo. The user ID is hashed first. It skips the report when the original and new classifications match, or when the user's own past fixes decided the original classification. See [How Corrections Are Used](https://help.spamfoo.com/guides/corrections). The message must have a From address, because SpamFoo Client links the fix to the sender. This endpoint has no license check. ## Request The body is the raw message (raw .eml content), sent unchanged. See [Send the Message in the Request Body](https://help.spamfoo.com/api#request-style). | Header | Required | Type | Description | | --- | --- | --- | --- | | `Content-Type` | Recommended | string | `message/rfc822` | | `X-SpamFoo-Proxy-Auth` | Yes | string | The proxy secret, required even from loopback. See [Authenticate Requests](https://help.spamfoo.com/api#authentication). | | `X-SpamFoo-Priority` | No | `bulk` | Send `bulk` for corrections that aren't from a user acting right now, such as a batch import. They run on the background queue with their own limit, so they can't slow down live mail. Without it, the correction is handled right away. | | `X-User-ID` | Yes | string | The user who made the fix. Use the same value you send as `X-User-ID` when classifying. | | `X-Classification` | Yes | string | The classification the user chose: `primary`, `transactions`, `updates`, or `promotions`. Case-insensitive. | | `X-Original-Classification` | No | string | The `classification` SpamFoo returned when the message arrived, one of the same four values. If you omit it, SpamFoo Client uses the model's top classification at fix time. With any other value, the fix is still learned for the user but not reported to SpamFoo. | | `X-Send-Email-Text` | No | `true` or `false` | Send `true` only when the user agreed to share message content. The report to SpamFoo then includes the message text, with email addresses, phone numbers, card numbers, and similar details replaced by placeholders, plus phishing details built from the message's text, headers, and links. Default `false`. | | `X-Subject-Strip-Prefixes` | No | string, repeatable | A literal tag your mail server adds to subjects, such as `[SPAM]`. SpamFoo Client removes it before it analyzes or reports the subject. Send the header once per tag, or send one JSON array of strings. | | `X-SpamFoo-Event-Id` | No | string, up to 128 characters | Your own ID for this fix, so a retry isn't applied twice. See [Send a Fix More Than Once](https://help.spamfoo.com/api/feedback-tabs#duplicates). | ## Response A successful request returns `200`: ``` { "status": "success", "userId": "user@example.com", "classification": "primary" } ``` | Field | Type | Description | | --- | --- | --- | | `status` | string | Always `success` on success. It does not tell you whether a report was queued for SpamFoo. | | `userId` | string | The `X-User-ID` you sent. | | `classification` | string | The `X-Classification` value, exactly as you sent it. | ### Send a Fix More Than Once If your integration can send the same fix twice, such as a retry after a lost answer, add `X-SpamFoo-Event-Id` with an ID that stays the same across retries. - The ID is 1 to 128 printable ASCII characters with no spaces. An ID that doesn't fit is ignored, and the fix is applied normally. - A repeat of an ID that was already applied for the same user on this endpoint returns `200` with `{"status": "duplicate", "duplicate": true, "userId": "user@example.com"}` and changes nothing. That answer has no `classification` field. - An ID counts as used only after its fix succeeds, so you can retry a failed attempt with the same ID. - SpamFoo Client remembers IDs in memory for 24 hours. A restart forgets them. - Without the header, every fix is applied, because a user can fix the same message more than once. ## Errors | Status | Body | Cause | | --- | --- | --- | | `400` | `{"error": "X-User-ID header is required"}` | `X-User-ID` was missing or blank. | | `400` | `{"error": "X-Classification header is required"}` | `X-Classification` was missing or blank. | | `400` | `{"error": "Invalid classification. Valid: primary, transactions, updates, promotions"}` | `X-Classification` is not one of the four classifications. | | `400` | `{"error": "No email content provided"}` | The body was empty. | | `400` | `{"error": "Email could not be parsed"}` | The body is not a readable message. | | `400` | `{"error": "Could not extract sender email from message"}` | The message has no usable From address. | | `401` | Empty | The proxy secret was missing or wrong. | | `413` | `{"error": "request_body_too_large", "maxBytes": ...}` | The message is over the body size limit. | | `429` | `{"error": "...", "code": "auth_blocked"}` | Too many wrong secrets came from this address. See [Repeated Wrong Secrets](https://help.spamfoo.com/api#blocked). | | `503` | `{"error": "server_busy", "code": "server_busy", ...}` or `{"error": "Classification queue timeout", "code": "queue_timeout", ...}` | Only with `X-SpamFoo-Priority: bulk`. SpamFoo Client is busy. Wait the seconds in `Retry-After` and send it again. | ## Example This example reports that a user moved a message from Promotions to Primary. ### curl ``` curl -X POST http://localhost:16253/tabs/feedback-stream \ -H "Content-Type: message/rfc822" \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "X-User-ID: user@example.com" \ -H "X-Classification: primary" \ -H "X-Original-Classification: promotions" \ --data-binary @message.eml ``` ### C# ``` using System.Net.Http.Headers; var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET") ?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET."); using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") }; await using var eml = File.OpenRead("message.eml"); var content = new StreamContent(eml); content.Headers.ContentType = new MediaTypeHeaderValue("message/rfc822"); using var request = new HttpRequestMessage(HttpMethod.Post, "/tabs/feedback-stream") { Content = content }; request.Headers.Add("X-SpamFoo-Proxy-Auth", secret); request.Headers.Add("X-User-ID", "user@example.com"); request.Headers.Add("X-Classification", "primary"); request.Headers.Add("X-Original-Classification", "promotions"); using var response = await http.SendAsync(request); Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}"); ``` ### Python ``` import os import requests headers = { "Content-Type": "message/rfc822", "X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"], "X-User-ID": "user@example.com", "X-Classification": "primary", "X-Original-Classification": "promotions", } with open("message.eml", "rb") as eml: response = requests.post( "http://localhost:16253/tabs/feedback-stream", data=eml, headers=headers, timeout=60, ) print(response.status_code, response.text) ``` *** Source: https://help.spamfoo.com/api/personal-data Last updated: 2026-10-27 # Manage Personal Data Delete a user, or count or delete the corrections SpamFoo Client has stored for one user. Delete the user when a mailbox is removed. Delete only the corrections when a user asks to reset their personalization. SpamFoo Client keeps two separate memories for each user. - **Spam memory:** filled by [Report Spam or Not Spam](https://help.spamfoo.com/api/feedback). - **Classification memory:** filled by [Report a Classification Fix](https://help.spamfoo.com/api/feedback-tabs). All of these endpoints require the proxy secret, even from loopback, and have no license check. The user ID must match the `X-User-ID` value you send when classifying. Case doesn't matter. ## Delete a User `DELETE /integration/users/{userId}` Deletes everything SpamFoo Client keeps for one user: both memories and their rules, the user's folder, the copies of messages the user corrected, the user's classification history and mailbox statistics in the admin console, and the user's own allow and block rules. Put the user ID in the path, URL-encoded, for example `/integration/users/user%40example.com`. There is no body. Calling it for a user that has nothing stored still returns `200`. If you push an [address map](https://help.spamfoo.com/api/address-map), leave the user out of your next push too. If mail for the same address is classified later, SpamFoo Client starts that user again from nothing. A successful request returns `200` with what was removed: ``` { "userId": "user@example.com", "folderDeleted": true, "archivedCorrectionsDeleted": 2, "classificationsDeleted": 140, "mailboxStatsDeleted": 1, "overridesDeleted": 0 } ``` ## Delete Spam Corrections `DELETE /personalization/data` Deletes the user's spam memory, plus any per-user spam rules stored on disk. After this call, the user's past spam and not-spam corrections no longer affect their mail. ## Delete Classification Corrections `DELETE /personalization/tabs/data` Deletes the user's classification memory, plus any per-user classification rules stored on disk. To remove all of a user's corrections, call both `DELETE` endpoints. ### Delete Request Both `DELETE` endpoints take the same JSON body and need `Content-Type: application/json`. ``` { "userId": "user@example.com" } ``` | Field | Required | Type | Description | | --- | --- | --- | --- | | `userId` | Yes | string | The user whose data to delete. | ### Delete Response A successful request returns `200`. The message says `tabs personalization` for the classification endpoint. ``` { "status": "success", "message": "All personalization data cleared for user user@example.com" } ``` ### What Is Not Deleted The two correction `DELETE` calls remove only the two memories and any rule files. They do not remove the items below. [Delete a User](https://help.spamfoo.com/api/personal-data#delete-user) removes the first two. - The compressed copies of corrected messages that SpamFoo Client keeps on its server. - Classification history and stored message content shown in the admin console. - Anonymized corrections already queued or sent to SpamFoo. These carry a hashed user ID, not the address. ## Count Spam Corrections `GET /personalization/knn/stats?userId={userId}` Returns how many entries are in the user's spam memory. ``` { "userId": "user@example.com", "totalEntries": 37, "userCorrections": "N/A", "autoEntries": "N/A" } ``` | Field | Type | Description | | --- | --- | --- | | `userId` | string | The user you asked about. | | `totalEntries` | integer | Number of entries in the user's spam memory. | | `userCorrections`, `autoEntries` | string | Always `"N/A"`. SpamFoo Client does not report this split. | ## Count Classification Corrections `GET /personalization/tabs/knn/stats?userId={userId}` Returns how many entries are in the user's classification memory, grouped by sender. ``` { "userId": "user@example.com", "totalEntries": 5, "entriesBySender": { "news@example.org": 3, "billing@example.net": 2 } } ``` | Field | Type | Description | | --- | --- | --- | | `userId` | string | The user you asked about. | | `totalEntries` | integer | Number of entries in the user's classification memory. | | `entriesBySender` | object | Number of entries for each sender address. | ### Stats Request | Query Parameter | Required | Type | Description | | --- | --- | --- | --- | | `userId` | Yes | string | The user to count. URL-encode it. | ## Errors | Status | Body | Cause | | --- | --- | --- | | `400` | `{"error": "userId is required"}` | `DELETE`: `userId` was missing or blank. | | `400` | `{"error": "userId query parameter is required"}` | `GET`: `userId` was blank. | | `400` | Empty | `GET`: the `userId` parameter was left out. `DELETE`: the body was empty or was not valid JSON. | | `401` | Empty | The proxy secret was missing or wrong. | | `415` | Empty | A correction `DELETE` did not use `Content-Type: application/json`. | ## Example This example counts a user's corrections, then deletes both memories. ### curl ``` curl -G http://localhost:16253/personalization/knn/stats \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ --data-urlencode "userId=user@example.com" curl -G http://localhost:16253/personalization/tabs/knn/stats \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ --data-urlencode "userId=user@example.com" curl -X DELETE http://localhost:16253/personalization/data \ -H "Content-Type: application/json" \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -d '{"userId": "user@example.com"}' curl -X DELETE http://localhost:16253/personalization/tabs/data \ -H "Content-Type: application/json" \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -d '{"userId": "user@example.com"}' ``` ### C# ``` using System.Net.Http.Json; using System.Text.Json; var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET") ?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET."); using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") }; http.DefaultRequestHeaders.Add("X-SpamFoo-Proxy-Auth", secret); var userId = "user@example.com"; var query = $"?userId={Uri.EscapeDataString(userId)}"; var spamStats = await http.GetFromJsonAsync("/personalization/knn/stats" + query); var tabsStats = await http.GetFromJsonAsync("/personalization/tabs/knn/stats" + query); Console.WriteLine($"spam entries: {spamStats.GetProperty("totalEntries").GetInt32()}"); Console.WriteLine($"classification entries: {tabsStats.GetProperty("totalEntries").GetInt32()}"); foreach (var path in new[] { "/personalization/data", "/personalization/tabs/data" }) { using var request = new HttpRequestMessage(HttpMethod.Delete, path) { Content = JsonContent.Create(new { userId }) }; using var response = await http.SendAsync(request); response.EnsureSuccessStatusCode(); } ``` ### Python ``` import os import requests SPAMFOO_URL = "http://localhost:16253" HEADERS = {"X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"]} user_id = "user@example.com" for path in ("/personalization/knn/stats", "/personalization/tabs/knn/stats"): response = requests.get( f"{SPAMFOO_URL}{path}", params={"userId": user_id}, headers=HEADERS, timeout=30, ) response.raise_for_status() print(path, response.json()["totalEntries"]) for path in ("/personalization/data", "/personalization/tabs/data"): response = requests.delete( f"{SPAMFOO_URL}{path}", json={"userId": user_id}, headers=HEADERS, timeout=30, ) response.raise_for_status() ``` *** Source: https://help.spamfoo.com/api/health Last updated: 2026-10-27 # Check Health Check that SpamFoo Client is up, whether it's ready to classify mail, and how busy its classification queues are. Use these endpoints for service monitors, container health checks, load balancers, and tuning queue limits. - [`GET /health/live`](https://help.spamfoo.com/api/health#live): is the process running? - [`GET /health/ready`](https://help.spamfoo.com/api/health#ready): can it classify mail right now? - [`GET /health`](https://help.spamfoo.com/api/health#health): queue and intake counts. None of them need the proxy secret or check the license. ## Check That It Is Running `GET /health/live` Returns `200` with `{"status":"live"}` whenever the process is running and answering. The Docker image's health check uses it. If the request can't connect, SpamFoo Client is down. ## Check That It Is Ready `GET /health/ready` Returns `200` when SpamFoo Client can classify mail: ``` {"status": "ready", "contract": 1} ``` Otherwise it returns `503` with a `Retry-After: 5` header and the reasons: ``` { "status": "not_ready", "code": "not_ready", "reasons": ["models_loading", "not_activated"], "contract": 1 } ``` | Reason | Meaning | | --- | --- | | `not_activated` | No license key is set up. | | `models_loading` | The models haven't finished downloading. Classification requests wait for them. | | `shutting_down` | SpamFoo Client is stopping, for example to install an update. `code` is then `shutting_down` instead of `not_ready`. | Use it to decide whether to send mail, or to take SpamFoo Client out of a load balancer. In standalone mode, when SpamFoo Client is asked to stop, it keeps answering for a few seconds with `shutting_down` before it closes, so callers notice before connections are refused. ## Check Queue Load `GET /health` ### When to Use It - Confirm that SpamFoo Client is accepting requests before you send mail to it. - Watch for `shutting_down` so a load balancer or mail server can stop sending requests while SpamFoo Client restarts for an update. - Track queue counters to decide whether to raise or lower SpamFoo Client's concurrency and intake limits. The endpoint needs no proxy secret and has no license check. It returns `200` whenever SpamFoo Client is running, including while it prepares to restart, so check the `status` field rather than the status code. A request that cannot connect, or times out, means the service is down. > **Note** `healthy` means the service is running and not waiting to restart. It does not check whether the models have finished downloading or loading. Use [`/health/ready`](https://help.spamfoo.com/api/health#ready) for that. In standalone mode, a caller that isn't on loopback and doesn't send the proxy secret gets only `{"status": "healthy", "service": "SpamFoo Client"}`, without the queue counts. ### Request No headers, query parameters, or body. ### Response ``` { "status": "healthy", "service": "SpamFoo Client", "shuttingDown": false, "activeRequests": 1, "modelReloading": false, "spoolQueue": { "active": 1, "waiting": 0, "maxConcurrency": 2, "totalProcessed": 18234, "totalTimedOut": 0 }, "backgroundQueue": { "active": 0, "waiting": 0, "maxConcurrency": 1, "totalProcessed": 912, "totalTimedOut": 0 }, "classifyIntake": { "inFlight": 1, "maxInFlight": 1024, "peakInFlight": 9, "totalRejected": 0 }, "bulkIntake": { "inFlight": 0, "maxInFlight": 1, "peakInFlight": 1, "totalRejected": 0 }, "authHeaderRaises": { "live_compared": 18234, "header_only": 0, ... } } ``` | Field | Type | Description | | --- | --- | --- | | `status` | string | `healthy`, or `shutting_down` after SpamFoo Client has started to restart for an update. It finishes the requests in progress before it restarts. | | `service` | string | `SpamFoo Client`. | | `shuttingDown` | boolean | `true` when `status` is `shutting_down`. | | `activeRequests` | integer | Classification and embedding requests in progress right now. | | `modelReloading` | boolean | `true` while SpamFoo Client pauses its queues to swap in updated models. New requests wait until the swap finishes. | | `spoolQueue` | object | The real-time queue used by `/classify-stream`. | | `backgroundQueue` | object | The queue used by `/tabs/classify-stream` and SpamFoo Client's own background work. | | `classifyIntake` | object | The limit on how many classification requests can hold a message in memory at once. | | `bulkIntake` | object | The same counts for requests sent with `X-SpamFoo-Priority: bulk`, which have their own limit. See [Mark Bulk Work](https://help.spamfoo.com/api/classify#priority). | | `authHeaderRaises` | object | Counters SpamFoo uses to study how email authentication results are checked. You can ignore them. | #### Queue Fields `spoolQueue` and `backgroundQueue` have the same fields. Counters start at zero when SpamFoo Client starts. | Field | Type | Description | | --- | --- | --- | | `active` | integer | Jobs running now. | | `waiting` | integer | Jobs waiting for a free slot. | | `maxConcurrency` | integer | How many jobs can run at once. | | `totalProcessed` | integer | Jobs finished since start. | | `totalTimedOut` | integer | Jobs that waited too long and returned `503` with `Classification queue timeout`. | #### Intake Fields `classifyIntake` and `bulkIntake` have the same fields. | Field | Type | Description | | --- | --- | --- | | `inFlight` | integer | Requests to `/classify-stream` and `/tabs/classify-stream` in progress now. | | `maxInFlight` | integer | The limit. For `classifyIntake` it's set by `SPAMFOO_MAX_CLASSIFY_INFLIGHT`. For `bulkIntake` it's one less than Max Concurrency, and at least 1. Requests over it return `503` with `server_busy`. | | `peakInFlight` | integer | The highest `inFlight` value since start. Use it to pick a realistic limit for your traffic. | | `totalRejected` | integer | Requests turned away with `server_busy` since start. | ## Errors `/health` and `/health/live` have no error responses of their own. `/health/ready` returns `503` when SpamFoo Client isn't ready. If SpamFoo Client is not running, the connection is refused. In standalone mode, an address blocked for wrong secrets gets `429` on these endpoints too. See [Repeated Wrong Secrets](https://help.spamfoo.com/api#blocked). ## Example ### curl ``` curl -s http://localhost:16253/health ``` ### C# ``` using System.Net.Http.Json; using System.Text.Json; using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253"), Timeout = TimeSpan.FromSeconds(5) }; try { var health = await http.GetFromJsonAsync("/health"); var status = health.GetProperty("status").GetString(); var waiting = health.GetProperty("spoolQueue").GetProperty("waiting").GetInt32(); Console.WriteLine($"status={status} waiting={waiting}"); } catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException) { Console.WriteLine("SpamFoo Client is not reachable."); } ``` ### Python ``` import requests try: health = requests.get("http://localhost:16253/health", timeout=5).json() print(f"status={health['status']} waiting={health['spoolQueue']['waiting']}") except requests.RequestException: print("SpamFoo Client is not reachable.") ``` *** Source: https://help.spamfoo.com/api/settings Last updated: 2026-10-27 # Read and Change Settings Read SpamFoo Client's saved settings, or change them from a script. These endpoints change the same settings as the **System > Settings** page in the admin console, plus a few that aren't on it. - Both endpoints require the proxy secret, even from loopback, and have no license check. See [Authenticate Requests](https://help.spamfoo.com/api#authentication). - A request with the proxy secret is treated as a system administrator. Do not send `X-User-ID` on these requests. With it, the request is treated as a dashboard user and returns `403`. - For the keys you can send, see [Settings You Can Change](https://help.spamfoo.com/api/settings#setting-keys). ## Read Settings `GET /admin/api/settings` Returns every setting saved in SpamFoo Client's local database, grouped by the part of the key before the first dot. A key with no dot is grouped under `General`. ``` { "Backup": [ { "key": "Backup.HourOfDay", "value": "2", "valueType": "Int", "updatedUtc": "2026-10-01T09:15:42.1234567Z", "updatedBy": "local" } ], "Retention": [ { "key": "Retention.DayAggregatesDays", "value": "30", "valueType": "String", "updatedUtc": "2026-10-07T14:02:11.7654321Z", "updatedBy": "local" } ] } ``` | Field | Type | Description | | --- | --- | --- | | `key` | string | The setting's key, such as `Queue.MaxConcurrency`. | | `value` | string | The saved value. Numbers and true or false values are returned as text. | | `valueType` | string | `String`, `Int`, `Bool`, or `Json`. Values saved through `PUT` are stored as `String`, whatever they hold. | | `updatedUtc` | string | When the value was last saved, in UTC. | | `updatedBy` | string | Who saved it. Changes from the Settings page and this API show `local`. | - **Missing keys:** a setting that has never been saved isn't listed, and SpamFoo Client uses its default. Some defaults are saved when SpamFoo Client first starts, so those are listed. - **Internal keys:** the response can include keys SpamFoo Client manages itself, such as the `Migration` group. Do not change them. ## Change Settings `PUT /admin/api/settings` Saves one or more settings. Send a JSON object whose names are setting keys and whose values are strings. The request needs `Content-Type: application/json`. ``` { "Retention.DayAggregatesDays": "30", "Backup.Enabled": "true" } ``` - **Send every value as a string,** such as `"30"` or `"true"`. A JSON number or `true` without quotes returns `400`. - **Keys you leave out don't change.** You only need to send the settings you're changing. - **All or nothing:** if any value fails a check, the request returns `400` and nothing is saved. - **Few values are checked.** SpamFoo Client checks only the values listed under [Errors](https://help.spamfoo.com/api/settings#errors). Other values are saved as sent, so check the allowed values in [Settings You Can Change](https://help.spamfoo.com/api/settings#setting-keys) first. - **Key names aren't checked.** A misspelled key is saved and then ignored. Read the settings back to confirm a change. - **When changes apply:** saved values are used right away, with no restart. They win over environment variables and command-line options, except that `SPAMFOO_GPU` still overrides `Features.GpuAcceleration`. ### Change Response A successful request returns `200` with the number of keys saved. ``` { "updated": 2 } ``` ## Settings You Can Change Send these keys to `PUT /admin/api/settings`. When a setting is also on **System > Settings** in the admin console, its **Settings page** line shows the card and field where it appears. ### Scan Queue These settings control where messages wait to be scanned. Mail being delivered waits in the spool queue and gets first claim on the scanning slots. Requests to [Classify Without Spam Checks](https://help.spamfoo.com/api/classify-tabs) wait in the background queue. #### Max Concurrency `Queue.MaxConcurrency` How many messages are scanned at once, shared by both queues. Each message being scanned can keep one processor core fully busy. A higher value lets a busy server keep up, but uses more memory and processor time. On servers with few processor cores, `1` can work better. - Type: Whole number, 1 to 32 - Default: `2` - Settings page: **Classification > Max Concurrency** #### Queue Timeout `Queue.TimeoutSeconds` How long a message can wait for a slot before SpamFoo Client returns an error (HTTP `503`), in seconds. Both queues use it unless they have their own limit. - Type: Whole number of seconds - Default: `30` - Settings page: **Classification > Queue Timeout** #### Spool Queue Timeout `Queue.SpoolTimeoutSeconds` A separate wait limit for the spool queue, in seconds. When it isn't set, the spool queue uses `Queue.TimeoutSeconds`. - Type: Whole number of seconds #### Background Queue Slots `Queue.BackgroundMaxConcurrency` The most slots the background queue can use. It can never take every slot, so mail being delivered always has room. - Type: Whole number, at least 1 - Default: one less than `Queue.MaxConcurrency`, at least 1 #### Background Queue Timeout `Queue.BackgroundTimeoutSeconds` A separate wait limit for the background queue, in seconds. When it isn't set, the background queue uses `Queue.TimeoutSeconds` if it has been saved, or 120 seconds. - Type: Whole number of seconds ### Spam Filtering #### Spam Sensitivity `Spam.Sensitivity` How readily SpamFoo Client marks a message as spam. `Low` (**Conservative** on the Settings page) marks fewer legitimate messages as spam but lets more spam through. `High` (**Aggressive**) catches the most spam but may mark more legitimate messages. `Medium` (**Recommended**) suits most servers. The setting also decides which spam levels count as spam; see [Spam Levels](https://help.spamfoo.com/api/classify#spam-levels). A change applies to the next message without a restart. - Type: `Low`, `Medium` or `High` (any letter case) - Default: `Medium` - Settings page: **Spam Filtering > Spam Sensitivity** ### Model Processing #### Enable Speedup Optimizations `Features.OnnxOptimizations` Lets SpamFoo Client use more processor and memory to scan each message faster. Turn it on only if the server is dedicated to SpamFoo Client or has a lot of unused processor and memory. See [Speedup Optimizations](https://help.spamfoo.com/guides/configure#speedup-optimizations). - Type: `true` or `false` - Default: `false` - Settings page: **Classification > Enable Speedup Optimizations** #### GPU Acceleration `Features.GpuAcceleration` Runs the text model on an NVIDIA GPU. When the `SPAMFOO_GPU` environment variable is set, it overrides this setting. See [Turn On GPU Acceleration](https://help.spamfoo.com/guides/gpu-acceleration). - Type: `true` or `false` - Default: `false` - Settings page: **Hardware > GPU Acceleration** ### Dashboard Display #### Default Time Range `UI.DefaultTimeRange` The time range dashboard pages show when they open. - Type: `1h`, `6h`, `24h`, `7d`, or `30d` - Default: `24h` - Settings page: **Display > Default Time Range** ### Scan History and Message Content #### Store Scan History `Retention.RawEventsEnabled` Keeps a record of each scanned message. Turning it off deletes the stored records at the next daily cleanup. - Type: `true` or `false` - Default: `true` - Settings page: **Scan History > Store Scan History** #### Retention Period `Retention.RawEventsDays` How many days scan records are kept. Log files are deleted after the same number of days. - Type: Whole number of days, 1 to 365 - Default: `7` - Settings page: **Scan History > Retention Period** #### Store Message Content `Retention.MessageContentEnabled` Keeps the subject and body of scanned messages, with known sensitive patterns removed, so they can be read on the Messages page. - Type: `true` or `false` - Default: `true` - Settings page: **Message Content > Store Message Content** #### Maximum Stored Messages `Retention.MessageContentMaxMessages` How many messages keep their content. The oldest content is cleared first. - Type: Whole number, 1 to 100,000 - Default: `10000` - Settings page: **Message Content > Maximum Stored Messages** #### Share Correction Content `Training.ShareCorrectionContent` Includes the stored message content when someone reports a wrong result from the SpamFoo dashboard. Needs `Retention.MessageContentEnabled`. When it's on, users who report a message see a notice that a sanitized copy is sent to SpamFoo. Make sure sharing message content fits your organization's privacy policy. - Type: `true` or `false` - Default: `false` - Settings page: **Message Content > Share Correction Content** ### Mail Totals SpamFoo Client keeps per-minute, per-hour, and per-day mail totals for the dashboard charts. Only this API can change how long they are kept. #### Per-Minute Totals `Retention.MinuteAggregatesHours` How many hours to keep per-minute totals. - Type: Whole number of hours - Default: `24` #### Per-Hour Totals `Retention.HourAggregatesDays` How many days to keep per-hour totals. - Type: Whole number of days - Default: `7` #### Per-Day Totals `Retention.DayAggregatesDays` How many days to keep per-day totals. Lower it to keep fewer daily totals. - Type: Whole number of days - Default: `90` ### Backups #### Back Up Databases Nightly `Backup.Enabled` Copies the local databases into the `backups` folder each night. - Type: `true` or `false` - Default: `true` - Settings page: **Database Backups > Back Up Databases Nightly** #### Backup Time `Backup.HourOfDay` The hour the nightly backup runs, in server time. - Type: Whole number, 0 to 23 - Default: `2` (2 AM) - Settings page: **Database Backups > Backup Time** #### Backups to Keep `Backup.RetentionNights` How many nightly backups are kept. Older ones are deleted. - Type: Whole number, 1 to 365 - Default: `7` - Settings page: **Database Backups > Backups to Keep** ## Errors | Status | Body | Cause | | --- | --- | --- | | `400` | `{"error": "The following settings keys are reserved and cannot be modified through this endpoint: ..."}` | `PUT`: a key starts with `License.`, `Cache.`, or `Server.`, or is another key SpamFoo Client manages itself. | | `400` | `{"error": "Retention.MessageContentEnabled must be true or false."}` | `PUT`: the value wasn't `true` or `false`. | | `400` | `{"error": "Retention.MessageContentMaxMessages must be between 1 and 100000."}` | `PUT`: the value wasn't a whole number in that range. | | `400` | `{"error": "Spam.Sensitivity must be Low, Medium or High."}` | `PUT`: the value wasn't one of those three words. | | `400` | `{"error": "Backup.HourOfDay must be between 0 and 23."}` | `PUT`: the value wasn't a whole number in that range. | | `400` | `{"error": "Backup.RetentionNights must be between 1 and 365."}` | `PUT`: the value wasn't a whole number in that range. | | `400` | Empty | `PUT`: the body was empty, wasn't valid JSON, or had a value that wasn't a string. | | `401` | Empty | The proxy secret was missing or wrong. | | `403` | Empty | The request included `X-User-ID`, so it was treated as a dashboard user without system administrator access. | | `415` | Empty | `PUT`: the request did not use `Content-Type: application/json`. | ## Example This example keeps 30 days of per-day totals, then reads the settings back to confirm the change. ### curl ``` curl -X PUT http://localhost:16253/admin/api/settings \ -H "Content-Type: application/json" \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -d '{"Retention.DayAggregatesDays": "30"}' curl http://localhost:16253/admin/api/settings \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" ``` ### C# ``` using System.Net.Http.Json; using System.Text.Json; var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET") ?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET."); using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") }; http.DefaultRequestHeaders.Add("X-SpamFoo-Proxy-Auth", secret); var changes = new Dictionary { ["Retention.DayAggregatesDays"] = "30" }; using var response = await http.PutAsJsonAsync("/admin/api/settings", changes); response.EnsureSuccessStatusCode(); var settings = await http.GetFromJsonAsync>("/admin/api/settings"); var saved = settings!["Retention"] .First(s => s.GetProperty("key").GetString() == "Retention.DayAggregatesDays"); Console.WriteLine($"Per-day totals kept: {saved.GetProperty("value").GetString()} days"); ``` ### Python ``` import os import requests SPAMFOO_URL = "http://localhost:16253" HEADERS = {"X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"]} response = requests.put( f"{SPAMFOO_URL}/admin/api/settings", json={"Retention.DayAggregatesDays": "30"}, headers=HEADERS, timeout=30, ) response.raise_for_status() response = requests.get(f"{SPAMFOO_URL}/admin/api/settings", headers=HEADERS, timeout=30) response.raise_for_status() saved = next( s for s in response.json()["Retention"] if s["key"] == "Retention.DayAggregatesDays" ) print(f"Per-day totals kept: {saved['value']} days") ``` *** Source: https://help.spamfoo.com/api/integration-info Last updated: 2026-10-27 # Get Integration Info Read the version, mode, license status, and limits of a SpamFoo Client. A mail server can call it when it connects, to check the proxy secret and adjust to the client it found. `GET /integration/info` ## When to Use It - Check that your proxy secret works before you send mail. - Find the largest message SpamFoo Client accepts, so you can skip larger ones. - Find out which optional features this SpamFoo Client supports, from `capabilities`. - See whether the license is active, in a grace period, or not set up. ## Request No body or query parameters. Send the proxy secret in `X-SpamFoo-Proxy-Auth`, even from loopback. See [Authenticate Requests](https://help.spamfoo.com/api#authentication). ## Response ``` { "contract": 1, "clientVersion": "1.2.3", "mode": "standalone", "installKind": "package", "license": { "token": "...", "reason": null, "planCode": "...", "planName": "...", "status": "Active", "expiresAt": "2027-10-01T00:00:00+00:00", "tokenExpiresAt": "2026-10-28T14:00:00+00:00", "graceEndsAt": null, "lastHandshakeAt": "2026-10-13T14:00:00+00:00" }, "maxBodyBytes": 67108864, "publicUrlSet": false, "autoSendTraining": false, "capabilities": ["boundary", "auth-results", "priority", "caller-identity", "login-links", "address-map"] } ``` | Field | Type | Description | | --- | --- | --- | | `contract` | integer | The API version. See [Versioning](https://help.spamfoo.com/api#versioning). | | `clientVersion` | string | The SpamFoo Client version. | | `mode` | string | `standalone` or `embedded`. See [Mode](https://help.spamfoo.com/guides/configure#mode). | | `installKind` | string | `package`, `container`, `portable` (an archive), or `sidecar`. Embedded mode always reports `sidecar`. | | `license` | object | The result of SpamFoo Client's last license check. See [License Fields](https://help.spamfoo.com/api/integration-info#license). | | `maxBodyBytes` | integer | The largest message SpamFoo Client accepts, in bytes. Larger messages get `413`. | | `publicUrlSet` | boolean | `true` when an administrator set `SPAMFOO_PUBLIC_URL`, so sign-in links point to a fixed address. | | `autoSendTraining` | boolean | Whether automatic training reports are turned on. See [Automatic Training Reports](https://help.spamfoo.com/guides/corrections#automatic-reports). | | `capabilities` | array of strings | Optional features this SpamFoo Client supports: `boundary` and `auth-results` ([Share Your Authentication Results](https://help.spamfoo.com/api/classify#boundary)), `priority` ([Mark Bulk Work](https://help.spamfoo.com/api/classify#priority)), `caller-identity` (the `X-SpamFoo-Caller` header), `login-links` ([Create a Sign-In Link](https://help.spamfoo.com/api/login-link)), and `address-map` ([Push an Address Map](https://help.spamfoo.com/api/address-map)). | ### License Fields SpamFoo Client keeps the result of its last license check in memory. After a restart, the fields are empty until the next check succeeds. | Field | Type | Description | | --- | --- | --- | | `token` | string or null | The signed license token from SpamFoo's last license check. It doesn't contain the license key. | | `reason` | string or null | Why `token` is empty: `not_activated` (no license key) or `no_handshake_yet` (the license hasn't been checked since SpamFoo Client started). `null` when there is a token. | | `planCode`, `planName` | string or null | The license's plan. | | `status` | string or null | The license status, such as `Active`, `Trial`, or `Grace` (expired, but still working for a short time). | | `expiresAt` | date and time or null | When the license expires. | | `tokenExpiresAt` | date and time or null | When the token expires. In standalone mode, SpamFoo Client stops classifying mail at this time unless a new license check succeeds first. | | `graceEndsAt` | date and time or null | When the grace period ends, while the license is in one. | | `lastHandshakeAt` | date and time or null | When SpamFoo Client last checked the license with SpamFoo. | Every field except `token`, `reason`, and `lastHandshakeAt` is `null` when `token` is `null`. ## Errors | Status | Body | Cause | | --- | --- | --- | | `401` | Empty | The proxy secret was missing or wrong. | | `429` | `{"error": "...", "code": "auth_blocked"}` | Standalone mode only. Too many wrong secrets came from this address. See [Repeated Wrong Secrets](https://help.spamfoo.com/api#blocked). | ## Example ### curl ``` curl -s http://localhost:16253/integration/info \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" ``` ### C# ``` using System.Net.Http.Json; using System.Text.Json; var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET") ?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET."); using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") }; http.DefaultRequestHeaders.Add("X-SpamFoo-Proxy-Auth", secret); var info = await http.GetFromJsonAsync("/integration/info"); Console.WriteLine($"mode={info.GetProperty("mode")} maxBodyBytes={info.GetProperty("maxBodyBytes")}"); ``` ### Python ``` import os import requests response = requests.get( "http://localhost:16253/integration/info", headers={"X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"]}, timeout=10, ) response.raise_for_status() info = response.json() print(f"mode={info['mode']} maxBodyBytes={info['maxBodyBytes']}") ``` *** Source: https://help.spamfoo.com/api/address-map Last updated: 2026-10-27 # Push an Address Map An address map tells SpamFoo Client which mailboxes your mail server has and which addresses deliver to each one: users, their other addresses, alias domains, and catch-all addresses. SpamFoo Client then files mail sent to an alias under the user who owns it, so each user's corrections and rules apply to all of their addresses. You can also give it a CardDAV address book for each domain to use for phishing protection. `PUT /integration/address-map` `GET /integration/address-map` ## When to Use It - Your users receive mail at more than one address, through aliases, alias domains, or `+tag` addresses, and you want one set of corrections and rules per person. - A domain has a catch-all address that delivers to one mailbox. - You want SpamFoo Client to protect the people in a domain's address book against impersonation. Push the whole address map each time it changes, or on a schedule. Each push replaces the one before it. ## How Addresses Are Matched to Users On every request that sends the proxy secret, SpamFoo Client looks up the `X-User-ID` header in the address map and replaces it with the owning user's `id`. It tries these in order and uses the first match: 1. The exact address, as a user's `id` or one of their `addresses`. 2. The same address with any `+tag` removed. 3. The same local part at the primary domain, when the address is at one of that domain's `aliases`. 4. The domain's `catchAll` user. This step is used only when the last push had `"complete": true`, and never for a list address. An address with no match is left as it is. Only the `X-User-ID` header is matched. A `userId` in a JSON body or query string is not. ## Request Send the proxy secret in `X-SpamFoo-Proxy-Auth`, even from loopback. Send the body as JSON with `Content-Type: application/json`. The body can be up to 32 MiB. ``` { "source": "mymailserver", "complete": true, "domains": [ { "name": "example.com", "aliases": ["example.net"], "catchAll": "info@example.com" } ], "users": [ { "id": "pat@example.com", "addresses": ["patricia@example.com"], "displayName": "Pat Lee" }, { "id": "info@example.com" } ], "lists": [ { "address": "sales@example.com", "targets": ["pat@example.com"] } ], "sources": [ { "kind": "carddav", "domain": "example.com", "url": "https://mail.example.com/carddav/gal/", "username": "gal-reader", "password": "..." } ] } ``` | Field | Type | Description | | --- | --- | --- | | `source` | string | A label for what sent the address map, up to 256 characters. It's returned in the summary. | | `complete` | boolean | `true` when the push lists every address on the mail server. Catch-all matching is used only then. Defaults to `false`. | | `domains` | array | Each domain has a `name`, optional `aliases` (other domain names that deliver to it), and an optional `catchAll`: the `id` or an address of a user in the same push. | | `users` | array | Each user has an `id`, the address you send as `X-User-ID`, and optional `addresses`: the user's other addresses. Users can also have `displayName`, `kind`, `active`, `role` (`user`, `domainAdmin`, or `systemAdmin`), and `adminDomains`. SpamFoo Client stores and counts these, but they don't change results. | | `lists` | array | Mailing lists, each with an `address` and its `targets`. A list address can't also be a user's address. Lists are stored and counted, but they don't change results. | | `sources` | array | CardDAV address books, one per domain. See [Address Book Sources](https://help.spamfoo.com/api/address-map#sources). | ### Rules for Entries - Addresses and domains are compared without regard to letter case. International domain names can be sent in either form. - An address can belong to only one user, and an alias domain to only one domain. An alias can't also be listed as a domain. - An entry that breaks a rule is skipped and listed in `rejected` in the response. The rest of the push is still saved. A bad optional field on a user falls back to its default, and the user is kept. - The whole push is refused with `400` if the JSON doesn't match the format, such as `"active": "yes"` instead of `true`, or if it has more than 50,000 domains, 200,000 users, 50,000 lists, 1,000 sources, or 500,000 entries in total. - Each domain or user can have up to 1,000 aliases, addresses, or admin domains, and each list up to 10,000 targets. Entries past the limit are skipped and reported. ### Address Book Sources A source gives SpamFoo Client a CardDAV address book for one domain. SpamFoo Client reads it about once an hour and adds its contacts to the domain's protected identities, so mail that pretends to come from those people is caught. See [Protect Against Phishing](https://help.spamfoo.com/guides/phishing-protection). | Field | Description | | --- | --- | | `kind` | Always `carddav`. | | `domain` | The domain the address book belongs to. | | `url` | The address book's address, up to 2048 characters. It must use `https://`, or `http://` to the same machine. | | `username`, `password` | The account SpamFoo Client signs in with. The password is required the first time you send a domain. After that you can leave it out to keep the saved one. Passwords are stored encrypted. | | `tlsPinSha256` | Optional, for a server with a self-signed certificate. The SHA-256 hash of the server's public key (its SubjectPublicKeyInfo), as 64 hex digits or base64. When it's set, SpamFoo Client accepts the server only if the key matches, and skips the usual certificate checks. Needs an `https://` address. | When a later push leaves out a domain's source, SpamFoo Client stops reading that address book. ## Response `PUT` saves the address map and returns a summary. `GET` returns the same summary for the address map now in use, with `version` `0` before the first push. ``` { "version": 3, "updatedAt": "2026-10-13T14:00:00+00:00", "source": "mymailserver", "complete": true, "counts": { "domains": 1, "users": 2, "activeUsers": 2, "addresses": 1, "lists": 1, "sources": 1, "rejected": 1 }, "rejected": [ { "value": "bob@other.example", "reason": "..." } ] } ``` - `rejected` lists up to 1,000 skipped entries, each with the value and the reason. `counts.rejected` is the total. - Rejected sources never echo the address book's URL or password. - The address map is saved as `address-map.json` in the data directory, so it survives a restart. ## Errors | Status | Body | Cause | | --- | --- | --- | | `400` | `{"error": "..."}` | The body is missing, isn't valid JSON for this endpoint, or is over a count limit. Nothing was changed. | | `401` | Empty | The proxy secret was missing or wrong. | | `413` | `{"error": "request_body_too_large", "maxBytes": 33554432}` | The body is over 32 MiB. | | `415` | `{"error": "Content-Type must be application/json"}` | The request wasn't sent as JSON. | | `429` | `{"error": "...", "code": "auth_blocked"}` | Too many wrong secrets came from this address. See [Repeated Wrong Secrets](https://help.spamfoo.com/api#blocked). | | `500` | `{"error": "the address map could not be saved"}` | Saving failed. The previous address map stays in use. | ## Example ### curl ``` curl -s -X PUT http://localhost:16253/integration/address-map \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "Content-Type: application/json" \ --data-binary @address-map.json ``` ### C# ``` using System.Net.Http.Json; using System.Text.Json; var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET") ?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET."); using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") }; http.DefaultRequestHeaders.Add("X-SpamFoo-Proxy-Auth", secret); var addressMap = new { source = "mymailserver", complete = true, domains = new[] { new { name = "example.com", aliases = new[] { "example.net" } } }, users = new[] { new { id = "pat@example.com", addresses = new[] { "patricia@example.com" } } } }; using var response = await http.PutAsJsonAsync("/integration/address-map", addressMap); response.EnsureSuccessStatusCode(); var summary = await response.Content.ReadFromJsonAsync(); Console.WriteLine($"rejected={summary.GetProperty("counts").GetProperty("rejected")}"); ``` ### Python ``` import os import requests address_map = { "source": "mymailserver", "complete": True, "domains": [{"name": "example.com", "aliases": ["example.net"]}], "users": [{"id": "pat@example.com", "addresses": ["patricia@example.com"]}], } response = requests.put( "http://localhost:16253/integration/address-map", headers={"X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"]}, json=address_map, timeout=60, ) response.raise_for_status() print("rejected:", response.json()["counts"]["rejected"]) ``` *** Source: https://help.spamfoo.com/api/login-link Last updated: 2026-10-27 # Create a Sign-In Link Make a one-time link that signs someone in to the SpamFoo Dashboard with the role you choose. A mail server or control panel can use it to send its users to the dashboard without sharing the proxy secret. `POST /generate-login-link` ## When to Use It - Add a "SpamFoo Dashboard" link to your own webmail or control panel, and make a fresh sign-in link each time a user clicks it. - Give a domain administrator access to the domains they manage, and nothing else. To make a link by hand on the server, use [`spamfoo-client login-link`](https://help.spamfoo.com/guides/commands#login-link) instead. For what each role sees, see [Manage Sign-In and Roles](https://help.spamfoo.com/guides/roles). ## Request Send the current proxy secret in `X-SpamFoo-Proxy-Auth`, even from loopback. Send a JSON body with `Content-Type: application/json`. ``` { "role": "DomainAdmin", "domains": ["example.com", "example.org"], "email": "admin@example.com", "expiresInMinutes": 15, "returnPath": "/admin/" } ``` | Field | Required | Type | Description | | --- | --- | --- | --- | | `role` | Yes | string | `SystemAdmin`, `DomainAdmin`, or `User`. Letter case doesn't matter. | | `domain` or `domains` | For `DomainAdmin` | string, or array of strings | The domain, or up to 50 domains, a Domain Admin can manage. Send one of the two fields, not both. Don't send either for the other roles. | | `email` | For `User` | email address | The mailbox a User sees. For a Domain Admin, also shows that mailbox's own pages. Optional for `SystemAdmin` and `DomainAdmin`. | | `expiresInMinutes` | No | integer | How long the link can be used, from 1 to 1440. The default is 15. The sign-in it creates lasts 24 hours either way. | | `returnPath` | No | string | A dashboard page to open after sign-in. It must be `/admin` or start with `/admin/`, and be at most 1024 characters. | Domains and email addresses are stored in lowercase, and international domain names in their Unicode form, so `Example.COM` and `example.com` are the same domain. ## Response ``` { "url": "http://spamfoo.example.com:16253/admin/login?t=...", "expiresAt": "2026-10-13T14:15:00+00:00" } ``` - `url` starts with `SPAMFOO_PUBLIC_URL` in standalone mode when it's set. Otherwise it uses the address your request was sent to, so call SpamFoo Client at the address the user's browser will use. - Opening the link shows a **Sign in to SpamFoo** page. The link is used up only when the person clicks **Continue**, so a mail scanner that opens links in advance doesn't use it. - Each link works once. Changing the proxy secret stops every unused link. > **Warning** Treat a sign-in link like a password until it's used or expires. Send it only to the person it's for, over HTTPS where you can. ## Errors | Status | Body | Cause | | --- | --- | --- | | `400` | `{"error": "..."}` | A field is missing or invalid, such as `role must be SystemAdmin, DomainAdmin or User.` or `expiresInMinutes must be between 1 and 1440.` The message names the field. | | `401` | Empty | The proxy secret was missing or wrong. | | `429` | `{"error": "...", "code": "auth_blocked"}` | Standalone mode only. Too many wrong secrets came from this address. See [Repeated Wrong Secrets](https://help.spamfoo.com/api#blocked). | ## Example ### curl ``` curl -s -X POST http://localhost:16253/generate-login-link \ -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \ -H "Content-Type: application/json" \ -d '{"role":"User","email":"user@example.com"}' ``` ### C# ``` using System.Net.Http.Json; var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET") ?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET."); using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") }; http.DefaultRequestHeaders.Add("X-SpamFoo-Proxy-Auth", secret); using var response = await http.PostAsJsonAsync("/generate-login-link", new { role = "User", email = "user@example.com" }); response.EnsureSuccessStatusCode(); var link = await response.Content.ReadFromJsonAsync(); Console.WriteLine(link!.Url); record LoginLink(string Url, DateTimeOffset ExpiresAt); ``` ### Python ``` import os import requests response = requests.post( "http://localhost:16253/generate-login-link", headers={"X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"]}, json={"role": "User", "email": "user@example.com"}, timeout=10, ) response.raise_for_status() print(response.json()["url"]) ``` *** Source: https://help.spamfoo.com/ai-assistants Last updated: 2026-10-09 # Use SpamFoo Help With AI Assistants SpamFoo Help has a public MCP server. **MCP** (Model Context Protocol) is a standard way for an AI assistant to use outside tools. With this server, an assistant can search these help pages and read them, so its answers about SpamFoo come from the current help instead of memory. - The server is read-only. It cannot change anything. - It needs no sign-in or API key. - It only returns help content. It never has access to your mail server or your SpamFoo data. - Requests are limited per IP address, so a script that sends a burst of requests may be told to slow down. ## Server Address The server uses streamable HTTP at this address: ``` https://help.spamfoo.com/mcp ``` ## Add the Server to Your Assistant ### Claude Add a custom connector in Claude's connector settings and enter the server address above. Leave any sign-in fields empty. ### Claude Code Run this command in a terminal: ``` claude mcp add --transport http spamfoo-help https://help.spamfoo.com/mcp ``` ### Other MCP Clients Most MCP clients that support remote servers accept the address above. Many read a JSON configuration like this one. The file name and the exact keys differ between clients, so check your client's documentation. ``` { "mcpServers": { "spamfoo-help": { "url": "https://help.spamfoo.com/mcp" } } } ``` ## Available Tools The server offers these tools. Every tool is read-only. - `search`: Search the SpamFoo Help Center and return matching pages. - `fetch`: Read a help page or one section of it. - `search_help`: Search the SpamFoo Help Center with keywords, exact names (such as SPAMFOO_BIND_ADDRESS or Queue.MaxConcurrency) or a question in plain words. - `get_page`: Read one help page, or one section of it, as Markdown. - `list_pages`: List every help page grouped by section, with a one-sentence summary and URL for each. - `find_fix`: Describe a symptom or error and get the best three matching troubleshooting entries, each with its causes and fixes in full. - `lookup_setting`: Find a SpamFoo Client setting by environment variable, command-line option, settings key or name, and return its description and default. - `lookup_endpoint`: List the local HTTP API endpoints of SpamFoo Client, or find one by route or method and read its API Reference page. The same tools are also available over plain HTTP. See the [OpenAPI description](https://help.spamfoo.com/agent-api/openapi.json). ## Markdown Pages If you would rather give an assistant a page directly, every help page is also available as Markdown. Add `.md` to the page address, or use `/index.md` for the home page. For example, `https://help.spamfoo.com/guides/install.md` is the Markdown version of the install guide. - [llms.txt](https://help.spamfoo.com/llms.txt) lists every page with a one-sentence summary. - [llms-full.txt](https://help.spamfoo.com/llms-full.txt) has the Markdown of every page in one file.