[{"data":1,"prerenderedAt":931},["ShallowReactive",2],{"navigation_docs":3,"-spam":32,"-spam-surround":928},[4,8,12,16,20,24,28],{"title":5,"path":6,"stem":7},"Set up your server","\u002Fsetup","1.setup",{"title":9,"path":10,"stem":11},"Addresses and domains","\u002Faddresses","2.addresses",{"title":13,"path":14,"stem":15},"Sign-in and discovery","\u002Fsign-in","3.sign-in",{"title":17,"path":18,"stem":19},"Mail apps","\u002Fproxy","4.proxy",{"title":21,"path":22,"stem":23},"Spam","\u002Fspam","5.spam",{"title":25,"path":26,"stem":27},"Configuration","\u002Fconfiguration","6.configuration",{"title":29,"path":30,"stem":31},"Troubleshooting","\u002Ftroubleshooting","7.troubleshooting",{"id":33,"title":21,"body":34,"description":82,"extension":922,"links":923,"meta":924,"navigation":925,"path":22,"seo":926,"stem":23,"__hash__":927},"docs\u002F5.spam.md",{"type":35,"value":36,"toc":901},"minimark",[37,41,69,76,92,99,102,107,114,128,132,135,150,220,223,227,230,253,259,265,321,332,337,340,349,356,360,369,372,376,383,392,395,399,405,409,418,427,436,445,448,457,460,469,472,476,498,504,508,518,584,595,599,618,659,662,665,669,672,679,686,703,714,865,877,881,897],[38,39,40],"p",{},"Mailopus files each inbound message in Inbox or Junk. A message goes to Junk when any one of these says so:",[42,43,44,57,63],"ol",{},[45,46,47,51,52,56],"li",{},[48,49,50],"strong",{},"DMARC failure."," The Authentication-Results header from Email Routing says ",[53,54,55],"code",{},"dmarc=fail",". Always on.",[45,58,59,62],{},[48,60,61],{},"A blocked sender."," You blocked the From address or its domain. Always on.",[45,64,65,68],{},[48,66,67],{},"A spam rule."," A rule matched the message's headers. Rules come from a bound spam Worker, from remote classifiers you list, or from rules compiled into your server. A fresh server has none of these, so this step is off until you add one.",[38,70,71,72,75],{},"Junk mail gets no push notification, and its remote images stay blocked when you open it. A message filed by a rule keeps the rule's name in the ",[53,73,74],{},"spam_rule"," column in D1. The UI doesn't show it yet, so finding out why a message landed in Junk takes a query:",[77,78,83],"pre",{"className":79,"code":80,"language":81,"meta":82,"style":82},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npx wrangler d1 execute mailopus --remote --command \"SELECT received_at, subject, spam_rule FROM emails WHERE spam_rule IS NOT NULL ORDER BY received_at DESC LIMIT 20\"\n","sh","",[53,84,85],{"__ignoreMap":82},[86,87,90],"span",{"class":88,"line":89},"line",1,[86,91,80],{},[38,93,94,95,98],{},"Blocked senders show up there as ",[53,96,97],{},"blocked-sender",". A DMARC failure has no rule name.",[38,100,101],{},"Everything on this path fails open. If a lookup, a rule or a classifier throws or times out, the message goes to Inbox. A filter that can lose mail is worse than no filter.",[103,104,106],"h2",{"id":105},"blocked-senders","Blocked senders",[38,108,109,110,113],{},"Open a message, then use its menu to block the sender's address or domain. New mail from a blocked sender goes to Junk. Settings > Mail > Blocked senders lists them and has ",[48,111,112],{},"Unblock"," for each.",[38,115,116,117,120,121,123,124,127],{},"A pattern like ",[53,118,119],{},"@example.com"," covers that host and every subdomain, so ",[53,122,119],{}," also blocks ",[53,125,126],{},"news.example.com",". Blocks are per account and apply only to mail that arrives after you block.",[103,129,131],{"id":130},"what-a-rule-sees","What a rule sees",[38,133,134],{},"For each inbound message the Worker builds:",[136,137,138,144],"ul",{},[45,139,140,143],{},[48,141,142],{},"The header block."," Every header as received, unfolded, with names in their original case. Never the body.",[45,145,146,149],{},[48,147,148],{},"Three facts about the sender",", each left out when the lookup fails:",[151,152,153,169],"table",{},[154,155,156],"thead",{},[157,158,159,163,166],"tr",{},[160,161,162],"th",{},"Fact",[160,164,165],{},"What it means",[160,167,168],{},"How it's found",[170,171,172,186,203],"tbody",{},[157,173,174,180,183],{},[175,176,177],"td",{},[53,178,179],{},"firstContact",[175,181,182],{},"You have never sent mail to the From address.",[175,184,185],{},"Your account's correspondents in D1",[157,187,188,193,196],{},[175,189,190],{},[53,191,192],{},"senderHasRdns",[175,194,195],{},"The connecting IP has a PTR record. IPv4 only.",[175,197,198,199,202],{},"DNS-over-HTTPS to ",[53,200,201],{},"cloudflare-dns.com",", 2 second timeout, cached 7 days",[157,204,205,210,213],{},[175,206,207],{},[53,208,209],{},"senderDomainAgeDays",[175,211,212],{},"Days since the From domain was registered",[175,214,215,216,219],{},"RDAP through ",[53,217,218],{},"www.rdap.net",", 3 second timeout, cached forever once known",[38,221,222],{},"A rule must treat a missing fact as \"don't know\". It may never fire because a fact is absent.",[103,224,226],{"id":225},"the-order-rules-run-in","The order rules run in",[38,228,229],{},"The first verdict wins:",[42,231,232,239,246],{},[45,233,234,235,238],{},"The ",[53,236,237],{},"SPAM"," service binding, if your Worker has one. If it throws, the built-in rules run instead.",[45,240,241,242,245],{},"Otherwise the built-in rules in ",[53,243,244],{},"server\u002Flib\u002Fspam\u002Frules.ts",". This repo ships that list empty.",[45,247,248,249,252],{},"Each remote classifier in the ",[53,250,251],{},"SPAM_CLASSIFIERS"," secret, in order, but only if steps 1 and 2 found nothing.",[103,254,256,257],{"id":255},"remote-classifiers-in-spam_classifiers","Remote classifiers in ",[53,258,251],{},[38,260,261,262,264],{},"This is the simplest way to add rules. ",[53,263,251],{}," is a Worker secret holding a JSON list of classifiers:",[77,266,270],{"className":267,"code":268,"language":269,"meta":82,"style":82},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[{ \"url\": \"https:\u002F\u002Fclassifier.example.com\u002Fv1\u002Fcheck\", \"key\": \"...\" }]\n","json",[53,271,272],{"__ignoreMap":82},[86,273,274,278,281,285,288,291,293,297,299,302,304,307,309,311,313,316,318],{"class":88,"line":89},[86,275,277],{"class":276},"sMK4o","[{",[86,279,280],{"class":276}," \"",[86,282,284],{"class":283},"spNyl","url",[86,286,287],{"class":276},"\"",[86,289,290],{"class":276},":",[86,292,280],{"class":276},[86,294,296],{"class":295},"sfazB","https:\u002F\u002Fclassifier.example.com\u002Fv1\u002Fcheck",[86,298,287],{"class":276},[86,300,301],{"class":276},",",[86,303,280],{"class":276},[86,305,306],{"class":283},"key",[86,308,287],{"class":276},[86,310,290],{"class":276},[86,312,280],{"class":276},[86,314,315],{"class":295},"...",[86,317,287],{"class":276},[86,319,320],{"class":276}," }]\n",[38,322,323,324,327,328,331],{},"Each entry needs an ",[53,325,326],{},"https:\u002F\u002F"," URL and a key. The Worker asks each in order and stops at the first rule name. A classifier that answers anything but ",[53,329,330],{},"200",", or takes longer than 1.5 seconds, counts as no verdict. A value that isn't valid JSON counts as an empty list and logs a warning. It never stops delivery.",[333,334,336],"h3",{"id":335},"set-it","Set it",[38,338,339],{},"From your clone:",[77,341,343],{"className":79,"code":342,"language":81,"meta":82,"style":82},"echo '[{\"url\":\"https:\u002F\u002Fclassifier.example.com\u002Fv1\u002Fcheck\",\"key\":\"\u003Ckey>\"}]' | npx wrangler secret put SPAM_CLASSIFIERS\n",[53,344,345],{"__ignoreMap":82},[86,346,347],{"class":88,"line":89},[86,348,342],{},[38,350,351,352,355],{},"Keep the key out of your shell history. One way is to put the JSON in a file outside the repo and run ",[53,353,354],{},"npx wrangler secret put SPAM_CLASSIFIERS \u003C that-file",".",[333,357,359],{"id":358},"turn-it-off","Turn it off",[77,361,363],{"className":79,"code":362,"language":81,"meta":82,"style":82},"npx wrangler secret delete SPAM_CLASSIFIERS\n",[53,364,365],{"__ignoreMap":82},[86,366,367],{"class":88,"line":89},[86,368,362],{},[38,370,371],{},"To drop one classifier, put the secret again without it.",[333,373,375],{"id":374},"the-hosted-rules","The hosted rules",[38,377,378,379,382],{},"app.mailopus.net's own rules are private. You can use them as a remote classifier at ",[53,380,381],{},"https:\u002F\u002Fspam.mailopus.net\u002Fv1\u002Fcheck",". Keys are issued by hand for now. Ask for one, then:",[77,384,386],{"className":79,"code":385,"language":81,"meta":82,"style":82},"echo '[{\"url\":\"https:\u002F\u002Fspam.mailopus.net\u002Fv1\u002Fcheck\",\"key\":\"msk_...\"}]' | npx wrangler secret put SPAM_CLASSIFIERS\n",[53,387,388],{"__ignoreMap":82},[86,389,390],{"class":88,"line":89},[86,391,385],{},[38,393,394],{},"With it on, your Worker sends spam.mailopus.net each inbound message's header block and the three facts. That includes From, To, Subject and the Received chain. It never includes the body. The service answers with a rule name or nothing and stores neither.",[103,396,398],{"id":397},"your-own-classifier","Your own classifier",[38,400,401,404],{},[53,402,403],{},"examples\u002Fspam-classifier"," is a starting point: a Worker with three example rules and tests. It needs Node.js 23.6 or later.",[333,406,408],{"id":407},"deploy-it","Deploy it",[77,410,412],{"className":79,"code":411,"language":81,"meta":82,"style":82},"cd examples\u002Fspam-classifier\n",[53,413,414],{"__ignoreMap":82},[86,415,416],{"class":88,"line":89},[86,417,411],{},[77,419,421],{"className":79,"code":420,"language":81,"meta":82,"style":82},"pnpm install\n",[53,422,423],{"__ignoreMap":82},[86,424,425],{"class":88,"line":89},[86,426,420],{},[77,428,430],{"className":79,"code":429,"language":81,"meta":82,"style":82},"pnpm check\n",[53,431,432],{"__ignoreMap":82},[86,433,434],{"class":88,"line":89},[86,435,429],{},[77,437,439],{"className":79,"code":438,"language":81,"meta":82,"style":82},"openssl rand -base64 32 | npx wrangler secret put CLASSIFIER_KEY\n",[53,440,441],{"__ignoreMap":82},[86,442,443],{"class":88,"line":89},[86,444,438],{},[38,446,447],{},"That last command sets the key without printing it, so generate it into a file first if you need a copy for the next step. Then deploy:",[77,449,451],{"className":79,"code":450,"language":81,"meta":82,"style":82},"pnpm run deploy\n",[53,452,453],{"__ignoreMap":82},[86,454,455],{"class":88,"line":89},[86,456,450],{},[38,458,459],{},"Add it to your Mailopus Worker, from the repo root:",[77,461,463],{"className":79,"code":462,"language":81,"meta":82,"style":82},"echo '[{\"url\":\"https:\u002F\u002Fmailopus-spam-classifier.\u003Cyour subdomain>.workers.dev\u002Fv1\u002Fcheck\",\"key\":\"\u003Cthe key>\"}]' | npx wrangler secret put SPAM_CLASSIFIERS\n",[53,464,465],{"__ignoreMap":82},[86,466,467],{"class":88,"line":89},[86,468,462],{},[38,470,471],{},"To use both your classifier and the hosted rules, list both. The first one in the list is asked first.",[333,473,475],{"id":474},"try-it","Try it",[77,477,479],{"className":79,"code":478,"language":81,"meta":82,"style":82},"curl -s https:\u002F\u002Fmailopus-spam-classifier.\u003Cyour subdomain>.workers.dev\u002Fv1\u002Fcheck \\\n  -H \"authorization: Bearer \u003Cthe key>\" -H \"content-type: application\u002Fjson\" \\\n  -d '{\"headers\":\"X-Mailer: PHPMailer 6.9\\r\\nFrom: a@b.example\\r\\n\",\"ctx\":{\"firstContact\":true}}'\n",[53,480,481,486,492],{"__ignoreMap":82},[86,482,483],{"class":88,"line":89},[86,484,485],{},"curl -s https:\u002F\u002Fmailopus-spam-classifier.\u003Cyour subdomain>.workers.dev\u002Fv1\u002Fcheck \\\n",[86,487,489],{"class":88,"line":488},2,[86,490,491],{},"  -H \"authorization: Bearer \u003Cthe key>\" -H \"content-type: application\u002Fjson\" \\\n",[86,493,495],{"class":88,"line":494},3,[86,496,497],{},"  -d '{\"headers\":\"X-Mailer: PHPMailer 6.9\\r\\nFrom: a@b.example\\r\\n\",\"ctx\":{\"firstContact\":true}}'\n",[38,499,500,501,355],{},"It should answer ",[53,502,503],{},"{\"rule\":\"bulk-library-first-contact\"}",[333,505,507],{"id":506},"the-protocol","The protocol",[38,509,510,513,514,517],{},[53,511,512],{},"POST \u002Fv1\u002Fcheck"," with ",[53,515,516],{},"Authorization: Bearer \u003Ckey>"," and this body:",[151,519,520,533],{},[154,521,522],{},[157,523,524,527,530],{},[160,525,526],{},"Field",[160,528,529],{},"Type",[160,531,532],{},"Meaning",[170,534,535,548,561,572],{},[157,536,537,542,545],{},[175,538,539],{},[53,540,541],{},"headers",[175,543,544],{},"string",[175,546,547],{},"The header block, up to 256 KB",[157,549,550,555,558],{},[175,551,552],{},[53,553,554],{},"ctx.firstContact",[175,556,557],{},"boolean, optional",[175,559,560],{},"See the facts table above",[157,562,563,568,570],{},[175,564,565],{},[53,566,567],{},"ctx.senderHasRdns",[175,569,557],{},[175,571],{},[157,573,574,579,582],{},[175,575,576],{},[53,577,578],{},"ctx.senderDomainAgeDays",[175,580,581],{},"number, optional",[175,583],{},[38,585,586,587,590,591,594],{},"Answer ",[53,588,589],{},"200 {\"rule\": \"\u003Cname>\"}"," or ",[53,592,593],{},"200 {\"rule\": null}",". Rule names are lowercase with dashes, at most 64 characters. Mailopus stores the name with the message.",[333,596,598],{"id":597},"write-rules","Write rules",[38,600,601,602,605,606,609,610,617],{},"Rules live in ",[53,603,604],{},"examples\u002Fspam-classifier\u002Fsrc\u002Frules.ts",". Each one is a list of regexes that must all match the headers, plus an optional ",[53,607,608],{},"needs"," check on the facts. ",[611,612,616],"a",{"href":613,"rel":614},"https:\u002F\u002Fgithub.com\u002Fmailopus\u002Fserver\u002Fblob\u002Fmain\u002Fexamples\u002Fspam-classifier\u002FAGENTS.md",[615],"nofollow","AGENTS.md"," has the full steps, and it's written so a coding agent can follow it too. The short version:",[42,619,620,631,638,645],{},[45,621,622,623,626,627,630],{},"Save a header block that shows the problem as ",[53,624,625],{},"tests\u002Ffixtures\u002F\u003Cname>.txt",". Headers only. Replace real addresses, names and IPs with ",[53,628,629],{},"example"," domains and documentation IPs.",[45,632,633,634,637],{},"Add cases to ",[53,635,636],{},"tests\u002Ffixtures\u002Fexpected.json",", including a near miss that must not match.",[45,639,640,641,644],{},"Run ",[53,642,643],{},"pnpm test"," and watch the new case fail.",[45,646,647,648,651,652,655,656,355],{},"Change ",[53,649,650],{},"src\u002Frules.ts"," until ",[53,653,654],{},"pnpm check"," passes, then ",[53,657,658],{},"pnpm run deploy",[38,660,661],{},"Match the sender's tooling and infrastructure, such as mailer headers, Message-ID formats and Received chains, not the wording. Wording is cheap to change. Pair any common signal with a second one or with a fact. The tests also fail any regex that takes over 50 ms on a 200 KB header line.",[38,663,664],{},"The classifier must keep nothing. No D1, KV, R2 or logging of headers, since they're other people's mail metadata.",[333,666,668],{"id":667},"training","Training",[38,670,671],{},"There's no learning step. Moving a message into or out of Junk doesn't teach any classifier. You train by adding fixtures and rules as above. Blocked senders are the per-account feedback that works today.",[103,673,675,676,678],{"id":674},"a-spam-worker-on-the-spam-binding","A spam Worker on the ",[53,677,237],{}," binding",[38,680,681,682,685],{},"app.mailopus.net binds a private Worker, ",[53,683,684],{},"mailopus-spam",", this way. A binding skips the HTTP round trip and the 1.5 second timeout, and a rule change deploys in seconds without rebuilding Mailopus.",[38,687,688,689,692,693,695,696,699,700,290],{},"The customer template, ",[53,690,691],{},"wrangler.jsonc",", has no ",[53,694,237],{}," binding. You can add one for your own Worker. It must export an entrypoint class with a ",[53,697,698],{},"check(headers, ctx)"," method that returns a rule name or ",[53,701,702],{},"null",[77,704,708],{"className":705,"code":706,"language":707,"meta":82,"style":82},"language-jsonc shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\"services\": [{ \"binding\": \"SPAM\", \"service\": \"\u003Cyour spam worker>\", \"entrypoint\": \"SpamFilter\" }]\n","jsonc",[53,709,710],{"__ignoreMap":82},[86,711,712],{"class":88,"line":89},[86,713,706],{},[77,715,719],{"className":716,"code":717,"language":718,"meta":82,"style":82},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { WorkerEntrypoint } from \"cloudflare:workers\";\n\nexport class SpamFilter extends WorkerEntrypoint {\n    async check(headers: string, ctx: { firstContact?: boolean; senderHasRdns?: boolean; senderDomainAgeDays?: number }) {\n        return null; \u002F\u002F or a rule name\n    }\n}\n","ts",[53,720,721,750,756,776,840,853,859],{"__ignoreMap":82},[86,722,723,727,730,734,737,740,742,745,747],{"class":88,"line":89},[86,724,726],{"class":725},"s7zQu","import",[86,728,729],{"class":276}," {",[86,731,733],{"class":732},"sTEyZ"," WorkerEntrypoint",[86,735,736],{"class":276}," }",[86,738,739],{"class":725}," from",[86,741,280],{"class":276},[86,743,744],{"class":295},"cloudflare:workers",[86,746,287],{"class":276},[86,748,749],{"class":276},";\n",[86,751,752],{"class":88,"line":488},[86,753,755],{"emptyLinePlaceholder":754},true,"\n",[86,757,758,761,764,768,771,773],{"class":88,"line":494},[86,759,760],{"class":725},"export",[86,762,763],{"class":283}," class",[86,765,767],{"class":766},"sBMFI"," SpamFilter",[86,769,770],{"class":283}," extends",[86,772,733],{"class":766},[86,774,775],{"class":276}," {\n",[86,777,779,782,786,789,792,794,797,799,802,804,806,809,812,815,818,821,823,825,827,830,832,835,838],{"class":88,"line":778},4,[86,780,781],{"class":283},"    async",[86,783,785],{"class":784},"swJcz"," check",[86,787,788],{"class":276},"(",[86,790,541],{"class":791},"sHdIc",[86,793,290],{"class":276},[86,795,796],{"class":766}," string",[86,798,301],{"class":276},[86,800,801],{"class":791}," ctx",[86,803,290],{"class":276},[86,805,729],{"class":276},[86,807,808],{"class":784}," firstContact",[86,810,811],{"class":276},"?:",[86,813,814],{"class":766}," boolean",[86,816,817],{"class":276},";",[86,819,820],{"class":784}," senderHasRdns",[86,822,811],{"class":276},[86,824,814],{"class":766},[86,826,817],{"class":276},[86,828,829],{"class":784}," senderDomainAgeDays",[86,831,811],{"class":276},[86,833,834],{"class":766}," number",[86,836,837],{"class":276}," })",[86,839,775],{"class":276},[86,841,843,846,849],{"class":88,"line":842},5,[86,844,845],{"class":725},"        return",[86,847,848],{"class":276}," null;",[86,850,852],{"class":851},"sHwdD"," \u002F\u002F or a rule name\n",[86,854,856],{"class":88,"line":855},6,[86,857,858],{"class":276},"    }\n",[86,860,862],{"class":88,"line":861},7,[86,863,864],{"class":276},"}\n",[38,866,867,869,870,872,873,876],{},[53,868,403],{}," speaks HTTP, not this interface, so it needs a small wrapper class to work as a binding. I haven't tested that path for customers. A remote classifier in ",[53,871,251],{}," is the supported route. To turn the binding off, remove the ",[53,874,875],{},"services"," entry and deploy.",[103,878,880],{"id":879},"rules-built-into-your-server","Rules built into your server",[38,882,883,886,887,889,890,893,894,896],{},[53,884,885],{},"RULES"," in ",[53,888,244],{}," is an empty list, and ",[53,891,892],{},"match()"," there runs it when no ",[53,895,237],{}," binding exists. You can add rules to it in your copy of the repo, in the same shape as the example classifier's. They then run on every message with no network call. The cost is a fork to maintain, and every rule change needs a full redeploy.",[898,899,900],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":82,"searchDepth":488,"depth":488,"links":902},[903,904,905,906,912,919,921],{"id":105,"depth":488,"text":106},{"id":130,"depth":488,"text":131},{"id":225,"depth":488,"text":226},{"id":255,"depth":488,"text":907,"children":908},"Remote classifiers in SPAM_CLASSIFIERS",[909,910,911],{"id":335,"depth":494,"text":336},{"id":358,"depth":494,"text":359},{"id":374,"depth":494,"text":375},{"id":397,"depth":488,"text":398,"children":913},[914,915,916,917,918],{"id":407,"depth":494,"text":408},{"id":474,"depth":494,"text":475},{"id":506,"depth":494,"text":507},{"id":597,"depth":494,"text":598},{"id":667,"depth":494,"text":668},{"id":674,"depth":488,"text":920},"A spam Worker on the SPAM binding",{"id":879,"depth":488,"text":880},"md",null,{},{"title":21},{"title":21,"description":82},"2yf2l78R42a5a9YvFTl3sCAHLMeLHHbFIq_gqeIlj9I",[929,930],{"title":17,"path":18,"stem":19,"description":82,"children":-1},{"title":25,"path":26,"stem":27,"description":82,"children":-1},1794228095132]