[{"data":1,"prerenderedAt":694},["ShallowReactive",2],{"navigation_docs":3,"-troubleshooting":32,"-troubleshooting-surround":692},[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":29,"body":34,"description":47,"extension":686,"links":687,"meta":688,"navigation":689,"path":30,"seo":690,"stem":31,"__hash__":691},"docs\u002F7.troubleshooting.md",{"type":35,"value":36,"toc":676},"minimark",[37,41,58,69,76,85,94,99,125,131,140,146,159,172,176,182,191,200,210,219,234,242,248,271,284,295,301,307,322,328,338,342,360,366,388,401,407,421,441,445,451,461,473,479,482,499,504,545,550,568,578,584,588,594,600,606,610,627,646,663,672],[38,39,40],"p",{},"Start with the logs. From your clone:",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npx wrangler tail\n","sh","",[49,50,51],"code",{"__ignoreMap":47},[52,53,56],"span",{"class":54,"line":55},"line",1,[52,57,45],{},[38,59,60,61,64,65,68],{},"It streams every request and every inbound message live. Older logs are in the dashboard under Workers & Pages > mailopus > Logs. Each received message logs ",[49,62,63],{},"email: stored",". Each refused one logs ",[49,66,67],{},"email: no account for recipient"," with the address.",[38,70,71,72,75],{},"For D1, ",[49,73,74],{},"npx wrangler d1 execute mailopus --remote --command \"\u003CSQL>\""," runs a query against your live database. A few that come up below:",[42,77,79],{"className":44,"code":78,"language":46,"meta":47,"style":47},"npx wrangler d1 execute mailopus --remote --command \"SELECT address, account_id FROM addresses\"\n",[49,80,81],{"__ignoreMap":47},[52,82,83],{"class":54,"line":55},[52,84,78],{},[42,86,88],{"className":44,"code":87,"language":46,"meta":47,"style":47},"npx wrangler d1 execute mailopus --remote --command \"SELECT email, name FROM identities\"\n",[49,89,90],{"__ignoreMap":47},[52,91,92],{"class":54,"line":55},[52,93,87],{},[95,96,98],"h2",{"id":97},"deploying","Deploying",[38,100,101,108,109,112,113,116,117,120,121,124],{},[102,103,104,107],"strong",{},[49,105,106],{},"pnpm run deploy"," fails with an authentication error."," Run ",[49,110,111],{},"npx wrangler login",", or set ",[49,114,115],{},"CLOUDFLARE_API_TOKEN"," to a token that can edit Workers, D1, R2 and Queues. Check the account with ",[49,118,119],{},"npx wrangler whoami"," and pass its id as ",[49,122,123],{},"CLOUDFLARE_ACCOUNT_ID",".",[38,126,127,130],{},[102,128,129],{},"The deploy worked but the app shows errors about missing tables."," Migrations didn't run. Run them yourself:",[42,132,134],{"className":44,"code":133,"language":46,"meta":47,"style":47},"npx wrangler d1 migrations apply DB --remote\n",[49,135,136],{"__ignoreMap":47},[52,137,138],{"class":54,"line":55},[52,139,133],{},[38,141,142,143,145],{},"A Deploy-button setup whose deploy command isn't ",[49,144,106],{}," skips migrations on every update. Fix the command in the dashboard under the Worker's Settings > Build.",[38,147,148,158],{},[102,149,150,153,154,157],{},[49,151,152],{},"node scripts\u002F..."," fails with a syntax error on a ",[49,155,156],{},".ts"," import."," Your Node.js is too old to strip types. Use 22.18 or later.",[38,160,161,164,165,168,169,124],{},[102,162,163],{},"A script says \"no account sends as ...\" or can't find the database."," It's looking at ",[49,166,167],{},"mailopus-db",", app.mailopus.net's database name. Prefix the command with ",[49,170,171],{},"MAILOPUS_DB=mailopus",[95,173,175],{"id":174},"claiming-and-signing-in","Claiming and signing in",[38,177,178,181],{},[102,179,180],{},"The claim page says \"This server already has an owner.\""," Someone claimed it, possibly you on an earlier try. Check:",[42,183,185],{"className":44,"code":184,"language":46,"meta":47,"style":47},"npx wrangler d1 execute mailopus --remote --command \"SELECT key, value FROM instance_settings WHERE key = 'claimed'\"\n",[49,186,187],{"__ignoreMap":47},[52,188,189],{"class":54,"line":55},[52,190,184],{},[38,192,193,194,199],{},"If it was you, sign in instead. If you lost the passkey, see ",[195,196,198],"a",{"href":197},"\u002Fsign-in#lost-every-passkey","Lost every passkey",". If it wasn't you, someone had your claim code. Delete the Worker, its D1 database and its R2 bucket, then deploy again and keep the new code to yourself.",[38,201,202,205,206,209],{},[102,203,204],{},"The claim page or sign-in talks to app.mailopus.net instead of your Worker."," The browser didn't find a usable ",[49,207,208],{},"_mailopus"," TXT record. Check it the same way the browser does:",[42,211,213],{"className":44,"code":212,"language":46,"meta":47,"style":47},"curl -s -H 'accept: application\u002Fdns-json' 'https:\u002F\u002Fcloudflare-dns.com\u002Fdns-query?name=_mailopus.example.com&type=TXT'\n",[49,214,215],{"__ignoreMap":47},[52,216,217],{"class":54,"line":55},[52,218,212],{},[38,220,221,222,225,226,229,230,233],{},"The answer must hold an ",[49,223,224],{},"https:\u002F\u002F"," URL. Common mistakes are a record on the wrong name, such as ",[49,227,228],{},"_mailopus.example.com.example.com",", a value with a path, or ",[49,231,232],{},"http:\u002F\u002F",". A new record can take a few minutes. To claim before DNS catches up, put your Worker in the link:",[42,235,240],{"className":236,"code":238,"language":239},[237],"language-text","https:\u002F\u002Fapp.mailopus.net\u002Fauth\u002Fclaim#server=https:\u002F\u002Fmailopus.\u003Cyour subdomain>.workers.dev\n","text",[49,241,238],{"__ignoreMap":47},[38,243,244,247],{},[102,245,246],{},"Sign-in says \"Sign-in failed.\""," In order of likelihood:",[249,250,251,255,258],"ul",{},[252,253,254],"li",{},"The TXT record is missing or wrong, as above, so the page asked app.mailopus.net.",[252,256,257],{},"You picked a passkey made for a different Mailopus server.",[252,259,260,263,264,267,268,270],{},[49,261,262],{},"APP_ORIGIN"," or ",[49,265,266],{},"RP_ID"," changed since you made the passkey. A passkey works only with the ",[49,269,266],{}," it was made for.",[38,272,273,276,277,279,280,283],{},[102,274,275],{},"The claim page says \"That claim code is wrong.\""," Use the code ",[49,278,106],{}," printed on your first deploy. Lost it? Set a new one from your clone with ",[49,281,282],{},"npx wrangler secret put CLAIM_CODE",", then claim with that.",[38,285,286,289,290,292,293,124],{},[102,287,288],{},"The claim page says \"This server has no claim code yet.\""," The first deploy didn't get as far as setting it, or the server was deployed without ",[49,291,106],{},". Set one with ",[49,294,282],{},[38,296,297,300],{},[102,298,299],{},"\"Too many attempts. Wait a minute.\""," Each IP gets 20 sign-in, claim or invite calls a minute. Wait a minute.",[38,302,303,306],{},[102,304,305],{},"\"Cancelled.\""," The browser's passkey prompt was closed or timed out. Try again.",[38,308,309,312,313,316,317,321],{},[102,310,311],{},"The invite page says \"That did not work. The invite may be used or expired.\""," Invite links work once and for 24 hours. Make a new one. Also check that the link has ",[49,314,315],{},"&server="," with your Worker, as in ",[195,318,320],{"href":319},"\u002Fsign-in#add-a-passkey-or-another-device","Add a passkey",". Without it the invite goes to app.mailopus.net.",[38,323,324,327],{},[102,325,326],{},"Signed out after a while."," Sessions last 30 days. Sign in again. Clearing the site's data for app.mailopus.net also removes the stored session token.",[38,329,330,333,334,337],{},[102,331,332],{},"A white screen after an update."," Reload. The app's HTML is sent ",[49,335,336],{},"no-store",", so a reload always gets the current version.",[95,339,341],{"id":340},"receiving","Receiving",[38,343,344,347,348,351,352,355,356,124],{},[102,345,346],{},"Senders get a bounce saying \"Address not found\"."," The Worker has no route for that address. Run the ",[49,349,350],{},"addresses"," query above. Either add the address in Settings > Addresses, use ",[102,353,354],{},"Receive mail"," on it, or turn on the domain's catch-all as in ",[195,357,359],{"href":358},"\u002Faddresses#the-catch-all-route","Addresses",[38,361,362,365],{},[102,363,364],{},"Nothing arrives, and nothing shows in the logs."," Mail isn't reaching the Worker:",[249,367,368,371,381],{},[252,369,370],{},"Email Routing is off for the domain, or its MX records are missing or point somewhere else. The Email Routing page shows the state of its DNS records.",[252,372,373,374,377,378,124],{},"The catch-all rule is disabled, or its action isn't ",[102,375,376],{},"Send to a Worker"," with ",[49,379,380],{},"mailopus",[252,382,383,384,124],{},"The zone is in a different Cloudflare account from the Worker. See ",[195,385,387],{"href":386},"\u002Faddresses#domains-in-another-cloudflare-account","Domains in another account",[38,389,390,393,394,397,398,400],{},[102,391,392],{},"Mail arrives in the wrong account."," An exact route beats a ",[49,395,396],{},"+"," alias, and both beat a domain catch-all. Check which account each matching route points at with the ",[49,399,350],{}," query.",[38,402,403,406],{},[102,404,405],{},"Good mail goes to Junk."," Three possible reasons:",[249,408,409,412,415],{},[252,410,411],{},"The sender's DMARC failed. That's common with forwarded mail and badly set up mailing lists. Ask the sender to fix their SPF and DKIM.",[252,413,414],{},"You blocked the sender or their domain. Check Settings > Mail > Blocked senders.",[252,416,417,418,420],{},"A spam rule matched. Find which one with the query in ",[195,419,21],{"href":22},", then fix or drop the rule.",[38,422,423,426,427,429,430,263,433,436,437,440],{},[102,424,425],{},"Spam isn't filtered."," A fresh server has no spam rules. See ",[195,428,21],{"href":22}," to add a classifier. If you did add one, look for ",[49,431,432],{},"spam: classifier answered",[49,434,435],{},"spam: classifier failed"," in the logs. A 401 means a wrong key, and a timeout means it took over 1.5 seconds. ",[49,438,439],{},"spam: SPAM_CLASSIFIERS is not valid JSON"," means the secret needs setting again.",[95,442,444],{"id":443},"sending","Sending",[38,446,447,450],{},[102,448,449],{},"Sending fails for every recipient."," Email Sending isn't set up or verified for the From domain. Open Email > Email Sending and check the domain's status and DNS records.",[38,452,453,456,457,124],{},[102,454,455],{},"Can't pick an address in the From menu."," Mailopus only sends as identities. Add the address in Settings > Addresses, or for a new domain follow ",[195,458,460],{"href":459},"\u002Faddresses#more-domains","More domains",[38,462,463,466,467,470,471,124],{},[102,464,465],{},"Settings won't add an address: \"isn't set up for Mailopus yet\"."," The domain has no address in your account yet. Add the first one with ",[49,468,469],{},"scripts\u002Fadd-identity.mjs",", as in ",[195,472,460],{"href":459},[38,474,475,478],{},[102,476,477],{},"\"That's the last address you send as.\""," Every account needs one address to send as. Add another before you remove this one.",[95,480,17],{"id":481},"mail-apps",[38,483,484,487,488,491,492,495,496,124],{},[102,485,486],{},"The app password section says there's no IMAP proxy."," ",[49,489,490],{},"IMAP_HOST"," isn't set. Add it to ",[49,493,494],{},"vars"," and deploy, as in ",[195,497,17],{"href":498},"\u002Fproxy#_5-show-the-hosts-in-settings",[38,500,501],{},[102,502,503],{},"A mail app can't connect at all.",[249,505,506,517,520,530],{},[252,507,508,509,512,513,516],{},"The DNS records for ",[49,510,511],{},"imap."," and ",[49,514,515],{},"smtp."," must be DNS only, the grey cloud. Cloudflare's proxy doesn't carry mail ports.",[252,518,519],{},"The server's firewall must allow 993, 465 and 587.",[252,521,522,525,526,529],{},[49,523,524],{},"IMAP_BIND"," must be ",[49,527,528],{},"0.0.0.0"," on a server. The default listens on localhost only.",[252,531,532,533,536,537,540,541,544],{},"From the server, ",[49,534,535],{},"docker compose logs --tail 100"," shows TLS or certificate errors. With ",[49,538,539],{},"TLS_MODE=acme",", check that ",[49,542,543],{},"CF_API_TOKEN"," can edit DNS on the zone.",[38,546,547],{},[102,548,549],{},"The app connects but sign-in fails.",[249,551,552,555,562,565],{},[252,553,554],{},"The username must be your full email address, and the password an app password, not your passkey.",[252,556,557,558,561],{},"Make sure the proxy's ",[49,559,560],{},"MAILOPUS_URL"," is your Worker, not app.mailopus.net.",[252,563,564],{},"Five failures in 15 minutes lock the IP out of the proxy for 15 minutes. Wait it out.",[252,566,567],{},"A log line saying the server lacks IMAP support means your Worker is older than the proxy. Update and deploy the Worker.",[38,569,570,573,574,577],{},[102,571,572],{},"A mail app sees only some mail."," The app password is limited to some addresses. Settings > App passwords shows what each one sees. Make a new one with ",[102,575,576],{},"All mail"," if needed.",[38,579,580,583],{},[102,581,582],{},"Can't create or rename folders from the mail app."," The proxy doesn't support that yet.",[95,585,587],{"id":586},"notifications","Notifications",[38,589,590,593],{},[102,591,592],{},"The Turn on button is missing on iPhone."," Add Mailopus to the home screen with Share > Add to Home Screen and open it from there. Safari only allows notifications for home-screen apps.",[38,595,596,599],{},[102,597,598],{},"\"Notifications are blocked in this browser's site settings.\""," Allow notifications for app.mailopus.net in the browser's site settings, then reload.",[38,601,602,605],{},[102,603,604],{},"Notifications stopped after moving servers."," The VAPID keys changed. Turn notifications off and on again on each device.",[95,607,609],{"id":608},"gmail-import","Gmail import",[38,611,612,622,623,626],{},[102,613,614,617,618,621],{},[49,615,616],{},"status"," shows ",[49,619,620],{},"unauthorized_client"," as the last error."," Domain-wide delegation for ",[49,624,625],{},"gmail.readonly"," isn't granted to the service account's client ID yet, or Google hasn't applied it. It can take a few minutes after you add it.",[38,628,629,634,635,638,639,642,643,124],{},[102,630,631,124],{},[49,632,633],{},"secret GOOGLE_SA_WORK is not set"," The secret name doesn't match the name passed to ",[49,636,637],{},"import.mjs start",". The script uppercases it, so ",[49,640,641],{},"work"," reads ",[49,644,645],{},"GOOGLE_SA_WORK",[38,647,648,654,655,658,659,662],{},[102,649,650,651,124],{},"A job stays ",[49,652,653],{},"pending"," The cron starts jobs every 5 minutes. If it's still pending after 10 minutes, check that the ",[49,656,657],{},"crons"," trigger is in ",[49,660,661],{},"wrangler.jsonc"," and that the Worker deployed with it.",[38,664,665,487,668,671],{},[102,666,667],{},"Some messages failed.",[49,669,670],{},"rescan \u003Cjob id>"," walks the mailbox again and fetches only what's missing.",[673,674,675],"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);}",{"title":47,"searchDepth":677,"depth":677,"links":678},2,[679,680,681,682,683,684,685],{"id":97,"depth":677,"text":98},{"id":174,"depth":677,"text":175},{"id":340,"depth":677,"text":341},{"id":443,"depth":677,"text":444},{"id":481,"depth":677,"text":17},{"id":586,"depth":677,"text":587},{"id":608,"depth":677,"text":609},"md",null,{},{"title":29},{"title":29,"description":47},"nsi7QJZm7sbzuNOcxprT7EHtqFnDZI_46SJfdCa6oE8",[693,687],{"title":25,"path":26,"stem":27,"description":47,"children":-1},1794228095132]