[{"data":1,"prerenderedAt":1513},["ShallowReactive",2],{"footer-articles":3,"/blog/web-workers-a-second-thread-for-your-javascript":1451,"related-/blog/web-workers-a-second-thread-for-your-javascript":1471},[4,51,85,116],{"id":5,"title":6,"body":7,"category":40,"date":41,"description":13,"draft":42,"extension":43,"image":44,"meta":45,"navigation":46,"path":47,"seo":48,"stem":49,"tighten":46,"__hash__":50},"blog/blog/web-workers-a-second-thread-for-your-javascript.md","Web Workers: A Second Thread for Your JavaScript",{"type":8,"value":9,"toc":36},"minimark",[10,14,25,28],[11,12,13],"p",{},"JavaScript runs on a single thread. Give it a heavy task, like resizing a large photo before uploading it, and the page freezes until it finishes.",[11,15,16,17,24],{},"Web Workers solve this by running that work on a second thread. In this article for ",[18,19,23],"a",{"href":20,"rel":21},"https://tighten.com",[22],"nofollow","Tighten",", I cover how they work, their two main limitations, how they differ from Service Workers, and how to use one to resize and compress images in the browser before they reach your server.",[11,26,27],{},"Read the full article here:",[29,30,33],"article-preview",{"image":31,"link":32,"title":6},"https://tighten.com/assets/images/insights/web-workers-a-second-thread-for-your-javascript-preview.jpg","https://tighten.com/insights/web-workers-a-second-thread-for-your-javascript/",[11,34,35],{},"Suppose you have a web app that lets users upload photos. Cool! But there's a catch: photos straight from a phone camera can easily be around 8 MB each. On a weak mobile connection, uploading the full-size file can be painfully slow for them, and expensive for you.",{"title":37,"searchDepth":38,"depth":38,"links":39},"",2,[],"JavaScript","2026-09-21",false,"md","/img/blog/web-workers-a-second-thread-for-your-javascript.png",{},true,"/blog/web-workers-a-second-thread-for-your-javascript",{"title":6,"description":13},"blog/web-workers-a-second-thread-for-your-javascript","y3SUXoJPmVOBMKdgNEzLNSaJPaSGkhKMQueHzuujzl0",{"id":52,"title":53,"body":54,"category":77,"date":78,"description":58,"draft":42,"extension":43,"image":79,"meta":80,"navigation":46,"path":81,"seo":82,"stem":83,"tighten":46,"__hash__":84},"blog/blog/run-ai-in-the-browser-a-practical-guide-to-transformers-js.md","Run AI in the Browser: A Practical Guide to Transformers.js",{"type":8,"value":55,"toc":75},[56,59,66,68],[11,57,58],{},"Transformers.js lets you run open source AI models directly in the browser. No API keys, no server round trips, and it keeps working offline once the model is cached.",[11,60,61,62,65],{},"I wrote a guide for ",[18,63,23],{"href":20,"rel":64},[22]," on how it works, which browsers support it, what to expect from small models, and how to build a grammar checker that runs entirely on the user's device.",[11,67,27],{},[29,69,72],{"image":70,"link":71,"title":53},"https://tighten.com/assets/images/insights/run-ai-in-the-browser-a-practical-guide-to-transformers-js-preview.jpg","https://tighten.com/insights/run-ai-in-the-browser-a-practical-guide-to-transformers-js/",[11,73,74],{},"Imagine you need to integrate AI into your web app. Let's say you want to quickly translate text. Preferably, you'd like to use something free and open source (you don't want any surprise bills at the end of the month). One more thing: people use your app while traveling, so it must continue working when they have poor connections or no connection at all.",{"title":37,"searchDepth":38,"depth":38,"links":76},[],"AI","2026-09-03","/img/blog/run-ai-in-the-browser-a-practical-guide-to-transformers-js.png",{},"/blog/run-ai-in-the-browser-a-practical-guide-to-transformers-js",{"title":53,"description":58},"blog/run-ai-in-the-browser-a-practical-guide-to-transformers-js","x_41mO5AOnw12R9hYDKPz_VfUPUqdnopJv_EEqtOxCM",{"id":86,"title":87,"body":88,"category":77,"date":109,"description":92,"draft":42,"extension":43,"image":110,"meta":111,"navigation":46,"path":112,"seo":113,"stem":114,"tighten":46,"__hash__":115},"blog/blog/build-your-own-ai-powered-slack-bot-with-the-laravel-ai-sdk.md","Build Your Own AI-Powered Slack Bot with the Laravel AI SDK",{"type":8,"value":89,"toc":107},[90,93,96,103],[11,91,92],{},"You can build your own AI teammate and talk to it via Slack! 🤖",[11,94,95],{},"Sure, you can use a service, but building your own means you own the memory, choose the model, and can tweak it to fit your needs.",[11,97,98,99,102],{},"Learn how in my latest article for ",[18,100,23],{"href":20,"rel":101},[22]," (Spoiler alert: Laravel AI SDK 🔥)",[29,104],{"image":105,"link":106,"title":87},"https://tighten.com/assets/images/insights/build-your-own-ai-powered-slack-bot-with-the-laravel-ai-sdk-preview.jpg","https://tighten.com/insights/build-your-own-ai-powered-slack-bot-with-the-laravel-ai-sdk/",{"title":37,"searchDepth":38,"depth":38,"links":108},[],"2026-08-26","/img/blog/build-your-own-ai-powered-slack-bot-with-the-laravel-ai-sdk.png",{},"/blog/build-your-own-ai-powered-slack-bot-with-the-laravel-ai-sdk",{"title":87,"description":92},"blog/build-your-own-ai-powered-slack-bot-with-the-laravel-ai-sdk","7tTbzmSWADzFKiLWDjyF_KR0vwbNimwP82vZeL1fVFE",{"id":117,"title":118,"body":119,"category":77,"date":1443,"description":1444,"draft":42,"extension":43,"image":1445,"meta":1446,"navigation":46,"path":1447,"seo":1448,"stem":1449,"tighten":42,"__hash__":1450},"blog/blog/climail-the-email-client-for-your-agent.md","climail: The Email Client for Your Agent",{"type":8,"value":120,"toc":1427},[121,131,151,154,163,166,171,182,187,193,248,257,261,264,278,300,304,307,322,577,584,599,603,617,620,624,627,641,644,881,885,888,1130,1140,1156,1160,1166,1254,1260,1275,1278,1300,1310,1331,1335,1338,1341,1344,1353,1360,1363,1369,1385,1389,1392,1410,1414,1420,1423],[11,122,123],{},[124,125,126,130],"strong",{},[127,128,129],"code",{},"climail"," is a clanker-friendly command line email client.",[132,133,134,138,145,148],"ul",{},[135,136,137],"li",{},"📬 Read, write & organize emails",[135,139,140,141,144],{},"⚡ Run instantly with ",[127,142,143],{},"npx"," (no install)",[135,146,147],{},"🔐 IMAP + SMTP",[135,149,150],{},"💌 Gmail ready",[11,152,153],{},"Built for agents 🤖 (and humans, too!)",[11,155,156],{},[157,158],"video",{"src":159,"poster":160,"controls":46,"playsInline":46,"preload":161,"style":162},"https://github.com/user-attachments/assets/192c5f2f-4456-49e3-9851-2b9b5c797a44","/img/blog/give-your-agent-an-inbox-with-climail.png","metadata","width:100%;border:0;border-radius:16px;margin:1.5rem 0;display:block",[11,164,165],{},"It's a minimalist email client that lives in your terminal. Every command hands back clean JSON, so an AI agent can read your inbox as easily as you can.",[167,168,170],"h2",{"id":169},"features","Features",[11,172,173,174,176,177,181],{},"There isn't much to ",[127,175,129],{},", and that's on purpose. It does a handful of things, and it tries to do each of them in the ",[178,179,180],"em",{},"least"," surprising way possible.",[183,184,186],"h3",{"id":185},"_1-just-npx","1) Just npx",[11,188,189,190,192],{},"There's nothing to install and no process to keep alive. It's just an ",[127,191,143],{}," command that uses IMAP and SMTP under the hood.",[194,195,199],"pre",{"className":196,"code":197,"language":198,"meta":37,"style":37},"language-bash shiki shiki-themes monokai","npx climail list --unread\nnpx climail mark 167 read\nnpx climail archive 167\n","bash",[127,200,201,220,235],{"__ignoreMap":37},[202,203,206,209,213,216],"span",{"class":204,"line":205},"line",1,[202,207,143],{"class":208},"sHkqI",[202,210,212],{"class":211},"s_Ekj"," climail",[202,214,215],{"class":211}," list",[202,217,219],{"class":218},"s7s5_"," --unread\n",[202,221,222,224,226,229,232],{"class":204,"line":38},[202,223,143],{"class":208},[202,225,212],{"class":211},[202,227,228],{"class":211}," mark",[202,230,231],{"class":218}," 167",[202,233,234],{"class":211}," read\n",[202,236,238,240,242,245],{"class":204,"line":237},3,[202,239,143],{"class":208},[202,241,212],{"class":211},[202,243,244],{"class":211}," archive",[202,246,247],{"class":218}," 167\n",[11,249,250,251,253,254,256],{},"If you can run ",[127,252,143],{},", you can run ",[127,255,129],{},": on your laptop, on a server, everywhere.",[183,258,260],{"id":259},"_2-configure-it-in-a-minute","2) Configure It in a Minute",[11,262,263],{},"Run the setup wizard and answer a few questions:",[194,265,267],{"className":196,"code":266,"language":198,"meta":37,"style":37},"npx climail init\n",[127,268,269],{"__ignoreMap":37},[202,270,271,273,275],{"class":204,"line":205},[202,272,143],{"class":208},[202,274,212],{"class":211},[202,276,277],{"class":211}," init\n",[11,279,280,281,284,285,288,289,291,292,295,296,299],{},"It asks where to save the config: ",[124,282,283],{},"global"," (",[127,286,287],{},"~/.config/climail.conf",", which lets ",[127,290,129],{}," work from any directory, no per-project setup) or ",[124,293,294],{},"local"," (a ",[127,297,298],{},"climail.conf"," in the current directory, handy when a project has its own mailbox). Pick global and you're done. We'll look at the file itself further down.",[183,301,303],{"id":302},"_3-all-commands-return-json","3) All Commands Return JSON",[11,305,306],{},"Every command prints clean JSON to stdout, so there's nothing to scrape and no columns to line up. Ask for your unread mail:",[194,308,310],{"className":196,"code":309,"language":198,"meta":37,"style":37},"npx climail list --unread\n",[127,311,312],{"__ignoreMap":37},[202,313,314,316,318,320],{"class":204,"line":205},[202,315,143],{"class":208},[202,317,212],{"class":211},[202,319,215],{"class":211},[202,321,219],{"class":218},[194,323,327],{"className":324,"code":325,"language":326,"meta":37,"style":37},"language-json shiki shiki-themes monokai","{\n  \"ok\": true,\n  \"mailbox\": \"INBOX\",\n  \"total\": 2481,\n  \"returned\": 2,\n  \"messages\": [\n    {\n      \"seq\": 2480,\n      \"uid\": 166,\n      \"date\": \"2026-07-12T09:03:11.000Z\",\n      \"from\": \"newsletter@somesaas.io\",\n      \"subject\": \"🚀 10 game-changing tips\",\n      \"unread\": true\n    },\n    {\n      \"seq\": 2481,\n      \"uid\": 167,\n      \"date\": \"2026-07-12T09:41:55.000Z\",\n      \"from\": \"sarah@acme.co\",\n      \"subject\": \"Export button throws a 500\",\n      \"unread\": true\n    }\n  ]\n}\n","json",[127,328,329,335,350,363,376,389,398,404,417,430,443,456,469,480,486,491,502,514,526,538,550,559,565,571],{"__ignoreMap":37},[202,330,331],{"class":204,"line":205},[202,332,334],{"class":333},"sCdxs","{\n",[202,336,337,341,344,347],{"class":204,"line":38},[202,338,340],{"class":339},"sOx1s","  \"ok\"",[202,342,343],{"class":333},": ",[202,345,346],{"class":218},"true",[202,348,349],{"class":333},",\n",[202,351,352,355,357,361],{"class":204,"line":237},[202,353,354],{"class":339},"  \"mailbox\"",[202,356,343],{"class":333},[202,358,360],{"class":359},"susgL","\"INBOX\"",[202,362,349],{"class":333},[202,364,366,369,371,374],{"class":204,"line":365},4,[202,367,368],{"class":339},"  \"total\"",[202,370,343],{"class":333},[202,372,373],{"class":218},"2481",[202,375,349],{"class":333},[202,377,379,382,384,387],{"class":204,"line":378},5,[202,380,381],{"class":339},"  \"returned\"",[202,383,343],{"class":333},[202,385,386],{"class":218},"2",[202,388,349],{"class":333},[202,390,392,395],{"class":204,"line":391},6,[202,393,394],{"class":339},"  \"messages\"",[202,396,397],{"class":333},": [\n",[202,399,401],{"class":204,"line":400},7,[202,402,403],{"class":333},"    {\n",[202,405,407,410,412,415],{"class":204,"line":406},8,[202,408,409],{"class":339},"      \"seq\"",[202,411,343],{"class":333},[202,413,414],{"class":218},"2480",[202,416,349],{"class":333},[202,418,420,423,425,428],{"class":204,"line":419},9,[202,421,422],{"class":339},"      \"uid\"",[202,424,343],{"class":333},[202,426,427],{"class":218},"166",[202,429,349],{"class":333},[202,431,433,436,438,441],{"class":204,"line":432},10,[202,434,435],{"class":339},"      \"date\"",[202,437,343],{"class":333},[202,439,440],{"class":359},"\"2026-07-12T09:03:11.000Z\"",[202,442,349],{"class":333},[202,444,446,449,451,454],{"class":204,"line":445},11,[202,447,448],{"class":339},"      \"from\"",[202,450,343],{"class":333},[202,452,453],{"class":359},"\"newsletter@somesaas.io\"",[202,455,349],{"class":333},[202,457,459,462,464,467],{"class":204,"line":458},12,[202,460,461],{"class":339},"      \"subject\"",[202,463,343],{"class":333},[202,465,466],{"class":359},"\"🚀 10 game-changing tips\"",[202,468,349],{"class":333},[202,470,472,475,477],{"class":204,"line":471},13,[202,473,474],{"class":339},"      \"unread\"",[202,476,343],{"class":333},[202,478,479],{"class":218},"true\n",[202,481,483],{"class":204,"line":482},14,[202,484,485],{"class":333},"    },\n",[202,487,489],{"class":204,"line":488},15,[202,490,403],{"class":333},[202,492,494,496,498,500],{"class":204,"line":493},16,[202,495,409],{"class":339},[202,497,343],{"class":333},[202,499,373],{"class":218},[202,501,349],{"class":333},[202,503,505,507,509,512],{"class":204,"line":504},17,[202,506,422],{"class":339},[202,508,343],{"class":333},[202,510,511],{"class":218},"167",[202,513,349],{"class":333},[202,515,517,519,521,524],{"class":204,"line":516},18,[202,518,435],{"class":339},[202,520,343],{"class":333},[202,522,523],{"class":359},"\"2026-07-12T09:41:55.000Z\"",[202,525,349],{"class":333},[202,527,529,531,533,536],{"class":204,"line":528},19,[202,530,448],{"class":339},[202,532,343],{"class":333},[202,534,535],{"class":359},"\"sarah@acme.co\"",[202,537,349],{"class":333},[202,539,541,543,545,548],{"class":204,"line":540},20,[202,542,461],{"class":339},[202,544,343],{"class":333},[202,546,547],{"class":359},"\"Export button throws a 500\"",[202,549,349],{"class":333},[202,551,553,555,557],{"class":204,"line":552},21,[202,554,474],{"class":339},[202,556,343],{"class":333},[202,558,479],{"class":218},[202,560,562],{"class":204,"line":561},22,[202,563,564],{"class":333},"    }\n",[202,566,568],{"class":204,"line":567},23,[202,569,570],{"class":333},"  ]\n",[202,572,574],{"class":204,"line":573},24,[202,575,576],{"class":333},"}\n",[11,578,579,580,583],{},"Every message carries a ",[127,581,582],{},"uid",", and that little number is the ID you pass to every other command.",[11,585,586,587,590,591,594,595,598],{},"It's the same idea as ",[127,588,589],{},"gh"," or ",[127,592,593],{},"kubectl -o json",": a small tool that pipes cleanly into the next thing, whether that next thing is ",[127,596,597],{},"jq"," or an agent.",[183,600,602],{"id":601},"_4-gmail-ready-out-of-the-box","4) Gmail-Ready, Out of the Box",[11,604,605,606,608,609,612,613,616],{},"Labels, archiving, and stars are the operations you actually perform on a Gmail inbox, so ",[127,607,129],{}," speaks them natively. Labeling applies a real Gmail label, archiving drops the ",[127,610,611],{},"Inbox"," label instead of trashing the message (exactly like the archive button in the web UI), and starring toggles ",[127,614,615],{},"\\Flagged",".",[11,618,619],{},"But if you are not using a Google email, don't worry: any email works, as long as you have the IMAP and SMTP credentials.",[167,621,623],{"id":622},"usage","Usage",[11,625,626],{},"Every interaction follows the same shape:",[194,628,630],{"className":196,"code":629,"language":198,"meta":37,"style":37},"npx climail ⟪command⟫\n",[127,631,632],{"__ignoreMap":37},[202,633,634,636,638],{"class":204,"line":205},[202,635,143],{"class":208},[202,637,212],{"class":211},[202,639,640],{"class":211}," ⟪command⟫\n",[11,642,643],{},"Here's the full set of commands. It looks like a lot, but you'll live in five or six of them and reach for the rest when you need them.",[645,646,647,660],"table",{},[648,649,650],"thead",{},[651,652,653,657],"tr",{},[654,655,656],"th",{},"Command",[654,658,659],{},"What it does",[661,662,663,676,696,733,747,762,772,803,819,833,848,861,871],"tbody",{},[651,664,665,671],{},[666,667,668],"td",{},[127,669,670],{},"init",[666,672,673,674,616],{},"Setup wizard for your IMAP credentials. Writes ",[127,675,287],{},[651,677,678,683],{},[666,679,680],{},[127,681,682],{},"list",[666,684,685,686,689,690,689,693,616],{},"List recent messages as JSON, each with its UID. Flags: ",[127,687,688],{},"--count \u003Cn>",", ",[127,691,692],{},"--unread",[127,694,695],{},"--mailbox \u003Cname>",[651,697,698,703],{},[666,699,700],{},[127,701,702],{},"search",[666,704,705,706,689,709,689,712,689,715,689,718,689,721,689,724,689,727,689,729,689,731,616],{},"Search a mailbox. Flags: ",[127,707,708],{},"--from",[127,710,711],{},"--to",[127,713,714],{},"--subject",[127,716,717],{},"--body",[127,719,720],{},"--text",[127,722,723],{},"--since",[127,725,726],{},"--before",[127,728,692],{},[127,730,688],{},[127,732,695],{},[651,734,735,740],{},[666,736,737],{},[127,738,739],{},"labels",[666,741,742,743,746],{},"List available labels/folders. Flag: ",[127,744,745],{},"--counts"," adds message counts per label.",[651,748,749,754],{},[666,750,751],{},[127,752,753],{},"read \u003Cuid>",[666,755,756,757,689,760,616],{},"Fetch one message: parsed text, HTML, and attachment metadata. Flags: ",[127,758,759],{},"--save-attachments \u003Cdir>",[127,761,695],{},[651,763,764,769],{},[666,765,766],{},[127,767,768],{},"label \u003Cuid> \u003Cname>",[666,770,771],{},"Apply a Gmail label, creating it if needed.",[651,773,774,779],{},[666,775,776],{},[127,777,778],{},"mark \u003Cuid> \u003Caction>",[666,780,781,782,785,786,689,789,785,792,795,796,785,799,802],{},"Set flags: ",[127,783,784],{},"read","/",[127,787,788],{},"unread",[127,790,791],{},"flag",[127,793,794],{},"unflag"," (aliases ",[127,797,798],{},"star",[127,800,801],{},"unstar",").",[651,804,805,810],{},[666,806,807],{},[127,808,809],{},"draft-reply \u003Cuid>",[666,811,812,813,689,815,818],{},"Stage a threaded reply in Drafts. Flags: ",[127,814,717],{},[127,816,817],{},"--all",". Nothing is sent.",[651,820,821,826],{},[666,822,823],{},[127,824,825],{},"send",[666,827,828,829,832],{},"Send over SMTP (needs ",[127,830,831],{},"smtp.host","): a new email, a threaded reply, or an existing draft.",[651,834,835,840],{},[666,836,837],{},[127,838,839],{},"forward \u003Cuid>",[666,841,842,843,845,846,616],{},"Forward a message over SMTP. Flag: ",[127,844,711],{}," (required), ",[127,847,717],{},[651,849,850,855],{},[666,851,852],{},[127,853,854],{},"delete \u003Cuid>",[666,856,857,858,616],{},"Delete a message (Trash on Gmail). Flag: ",[127,859,860],{},"--from \u003Cmailbox>",[651,862,863,868],{},[666,864,865],{},[127,866,867],{},"archive \u003Cuid>",[666,869,870],{},"Archive a message (drops the Inbox label on Gmail).",[651,872,873,878],{},[666,874,875],{},[127,876,877],{},"move \u003Cuid> \u003Cmailbox>",[666,879,880],{},"Move a message to another mailbox.",[183,882,884],{"id":883},"examples","Examples",[11,886,887],{},"Reading the table is one thing, but the commands click faster when you see them lined up. Here's a full triage session, top to bottom:",[194,889,891],{"className":196,"code":890,"language":198,"meta":37,"style":37},"npx climail init\nnpx climail list --unread\nnpx climail search --from boss@work.com --since 2026-06-01 --unread\nnpx climail labels --counts\nnpx climail read 167 --save-attachments ./att\nnpx climail mark 167 read\nnpx climail list --mailbox \"[Gmail]/All Mail\" --unread\nnpx climail mark 167 read --mailbox \"[Gmail]/All Mail\"\nnpx climail label 167 Triaged\nnpx climail draft-reply 167 --all --body \"Thanks, will take a look.\"\nnpx climail send --to them@example.com --reply-to 167 --all --body \"On it.\"\nnpx climail send --to a@b.com --subject \"Report\" --body \"Attached.\" --attach ./report.pdf\nnpx climail forward 167 --to colleague@work.com --body \"FYI\"\nnpx climail archive 167\n",[127,892,893,901,911,934,946,963,975,991,1008,1022,1042,1069,1099,1120],{"__ignoreMap":37},[202,894,895,897,899],{"class":204,"line":205},[202,896,143],{"class":208},[202,898,212],{"class":211},[202,900,277],{"class":211},[202,902,903,905,907,909],{"class":204,"line":38},[202,904,143],{"class":208},[202,906,212],{"class":211},[202,908,215],{"class":211},[202,910,219],{"class":218},[202,912,913,915,917,920,923,926,929,932],{"class":204,"line":237},[202,914,143],{"class":208},[202,916,212],{"class":211},[202,918,919],{"class":211}," search",[202,921,922],{"class":218}," --from",[202,924,925],{"class":211}," boss@work.com",[202,927,928],{"class":218}," --since",[202,930,931],{"class":211}," 2026-06-01",[202,933,219],{"class":218},[202,935,936,938,940,943],{"class":204,"line":365},[202,937,143],{"class":208},[202,939,212],{"class":211},[202,941,942],{"class":211}," labels",[202,944,945],{"class":218}," --counts\n",[202,947,948,950,952,955,957,960],{"class":204,"line":378},[202,949,143],{"class":208},[202,951,212],{"class":211},[202,953,954],{"class":211}," read",[202,956,231],{"class":218},[202,958,959],{"class":218}," --save-attachments",[202,961,962],{"class":211}," ./att\n",[202,964,965,967,969,971,973],{"class":204,"line":391},[202,966,143],{"class":208},[202,968,212],{"class":211},[202,970,228],{"class":211},[202,972,231],{"class":218},[202,974,234],{"class":211},[202,976,977,979,981,983,986,989],{"class":204,"line":400},[202,978,143],{"class":208},[202,980,212],{"class":211},[202,982,215],{"class":211},[202,984,985],{"class":218}," --mailbox",[202,987,988],{"class":211}," \"[Gmail]/All Mail\"",[202,990,219],{"class":218},[202,992,993,995,997,999,1001,1003,1005],{"class":204,"line":406},[202,994,143],{"class":208},[202,996,212],{"class":211},[202,998,228],{"class":211},[202,1000,231],{"class":218},[202,1002,954],{"class":211},[202,1004,985],{"class":218},[202,1006,1007],{"class":211}," \"[Gmail]/All Mail\"\n",[202,1009,1010,1012,1014,1017,1019],{"class":204,"line":419},[202,1011,143],{"class":208},[202,1013,212],{"class":211},[202,1015,1016],{"class":211}," label",[202,1018,231],{"class":218},[202,1020,1021],{"class":211}," Triaged\n",[202,1023,1024,1026,1028,1031,1033,1036,1039],{"class":204,"line":432},[202,1025,143],{"class":208},[202,1027,212],{"class":211},[202,1029,1030],{"class":211}," draft-reply",[202,1032,231],{"class":218},[202,1034,1035],{"class":218}," --all",[202,1037,1038],{"class":218}," --body",[202,1040,1041],{"class":211}," \"Thanks, will take a look.\"\n",[202,1043,1044,1046,1048,1051,1054,1057,1060,1062,1064,1066],{"class":204,"line":445},[202,1045,143],{"class":208},[202,1047,212],{"class":211},[202,1049,1050],{"class":211}," send",[202,1052,1053],{"class":218}," --to",[202,1055,1056],{"class":211}," them@example.com",[202,1058,1059],{"class":218}," --reply-to",[202,1061,231],{"class":218},[202,1063,1035],{"class":218},[202,1065,1038],{"class":218},[202,1067,1068],{"class":211}," \"On it.\"\n",[202,1070,1071,1073,1075,1077,1079,1082,1085,1088,1090,1093,1096],{"class":204,"line":458},[202,1072,143],{"class":208},[202,1074,212],{"class":211},[202,1076,1050],{"class":211},[202,1078,1053],{"class":218},[202,1080,1081],{"class":211}," a@b.com",[202,1083,1084],{"class":218}," --subject",[202,1086,1087],{"class":211}," \"Report\"",[202,1089,1038],{"class":218},[202,1091,1092],{"class":211}," \"Attached.\"",[202,1094,1095],{"class":218}," --attach",[202,1097,1098],{"class":211}," ./report.pdf\n",[202,1100,1101,1103,1105,1108,1110,1112,1115,1117],{"class":204,"line":471},[202,1102,143],{"class":208},[202,1104,212],{"class":211},[202,1106,1107],{"class":211}," forward",[202,1109,231],{"class":218},[202,1111,1053],{"class":218},[202,1113,1114],{"class":211}," colleague@work.com",[202,1116,1038],{"class":218},[202,1118,1119],{"class":211}," \"FYI\"\n",[202,1121,1122,1124,1126,1128],{"class":204,"line":482},[202,1123,143],{"class":208},[202,1125,212],{"class":211},[202,1127,244],{"class":211},[202,1129,247],{"class":218},[11,1131,1132,1133,1136,1137,1139],{},"Notice that ",[127,1134,1135],{},"draft-reply"," and ",[127,1138,825],{}," are two different commands:",[132,1141,1142,1151],{},[135,1143,1144,1146,1147,1150],{},[127,1145,1135],{}," writes a proper threaded reply, correct subject and headers, straight into your ",[124,1148,1149],{},"Drafts"," folder. Nothing leaves your account.",[135,1152,1153,1155],{},[127,1154,825],{}," is the one that actually puts mail on the wire, and it only works once you've configured SMTP. Reading your inbox and emailing people on your behalf are two very different levels of trust, so they're two different commands you opt into separately.",[167,1157,1159],{"id":1158},"configuration","Configuration",[11,1161,1162,1165],{},[127,1163,1164],{},"npx climail init"," writes the config for you, but it's a plain text file, so you can just as easily write it yourself:",[194,1167,1171],{"className":1168,"code":1169,"filename":287,"language":1170,"meta":37,"style":37},"language-dotenv shiki shiki-themes monokai","imap.host=imap.gmail.com\nimap.port=993\nimap.secure=true\nimap.username=you@gmail.com\nimap.password=\"app password here\"\n\n# Optional: only needed for the `send` command\nsmtp.host=smtp.gmail.com\nsmtp.port=465\n","dotenv",[127,1172,1173,1185,1195,1204,1214,1224,1229,1235,1244],{"__ignoreMap":37},[202,1174,1175,1178,1182],{"class":204,"line":205},[202,1176,1177],{"class":333},"imap.host",[202,1179,1181],{"class":1180},"s8I7P","=",[202,1183,1184],{"class":333},"imap.gmail.com\n",[202,1186,1187,1190,1192],{"class":204,"line":38},[202,1188,1189],{"class":333},"imap.port",[202,1191,1181],{"class":1180},[202,1193,1194],{"class":333},"993\n",[202,1196,1197,1200,1202],{"class":204,"line":237},[202,1198,1199],{"class":333},"imap.secure",[202,1201,1181],{"class":1180},[202,1203,479],{"class":333},[202,1205,1206,1209,1211],{"class":204,"line":365},[202,1207,1208],{"class":333},"imap.username",[202,1210,1181],{"class":1180},[202,1212,1213],{"class":333},"you@gmail.com\n",[202,1215,1216,1219,1221],{"class":204,"line":378},[202,1217,1218],{"class":333},"imap.password",[202,1220,1181],{"class":1180},[202,1222,1223],{"class":211},"\"app password here\"\n",[202,1225,1226],{"class":204,"line":391},[202,1227,1228],{"emptyLinePlaceholder":46},"\n",[202,1230,1231],{"class":204,"line":400},[202,1232,1234],{"class":1233},"snpHw","# Optional: only needed for the `send` command\n",[202,1236,1237,1239,1241],{"class":204,"line":406},[202,1238,831],{"class":333},[202,1240,1181],{"class":1180},[202,1242,1243],{"class":333},"smtp.gmail.com\n",[202,1245,1246,1249,1251],{"class":204,"line":419},[202,1247,1248],{"class":333},"smtp.port",[202,1250,1181],{"class":1180},[202,1252,1253],{"class":333},"465\n",[11,1255,1256,1257,1259],{},"The IMAP block is all you need to start reading. The SMTP block is optional, and its absence is exactly why ",[127,1258,825],{}," stays locked until you deliberately add it.",[1261,1262,1265],"callout",{"color":1263,"icon":1264},"#057ccd","fa6-solid:circle-info",[11,1266,1267,1268,1271,1272,1274],{},"Gmail won't let a raw IMAP client sign in with your normal account password. You need an ",[124,1269,1270],{},"app password",", a 16-character token you generate in your Google account settings. Drop that into ",[127,1273,1218],{}," and you're in.",[11,1276,1277],{},"Always keep your config file out of version control, since it holds your credentials.",[11,1279,1280,1282,1283,1286,1287,1289,1290,1292,1293,1296,1297,1299],{},[127,1281,670],{}," lets you choose where this file lives. ",[124,1284,1285],{},"Global"," puts it at ",[127,1288,287],{},", following the XDG convention, so ",[127,1291,129],{}," works from any directory. ",[124,1294,1295],{},"Local"," drops a ",[127,1298,298],{}," in the current directory, which takes precedence over the global one when you run commands from there, perfect for a project with its own inbox.",[11,1301,1302,1303,1306,1307,1309],{},"If you manage more than one email address, you can also point any command at a specific file with ",[127,1304,1305],{},"--config"," (which skips the prompt in ",[127,1308,670],{}," too):",[194,1311,1313],{"className":196,"code":1312,"language":198,"meta":37,"style":37},"npx climail list --config ~/work/mail.conf --unread\n",[127,1314,1315],{"__ignoreMap":37},[202,1316,1317,1319,1321,1323,1326,1329],{"class":204,"line":205},[202,1318,143],{"class":208},[202,1320,212],{"class":211},[202,1322,215],{"class":211},[202,1324,1325],{"class":218}," --config",[202,1327,1328],{"class":211}," ~/work/mail.conf",[202,1330,219],{"class":218},[167,1332,1334],{"id":1333},"an-email-client-for-agents","An Email Client for Agents",[11,1336,1337],{},"So that's the tool. I created it to give my agent an easy way of interacting with an inbox.",[11,1339,1340],{},"These days our agents can automate an incredible amount of our daily work. They write code, review pull requests, update documentation, and poke at APIs through a handful of little CLIs. Yet for many of us, email is still one of the last manual parts of the workflow. And that's odd, because email is exactly the kind of tedious, repetitive triage I'd love to hand off.",[11,1342,1343],{},"The problem was never the agent, it was the interface. A full mail client is a GUI, useless to a clanker. A raw IMAP library means the agent has to write code every time it wants to ask a simple question. Neither one lets you hand the agent a command and get back something it can read.",[11,1345,1346,1347,1349,1350,1352],{},"That's the gap ",[127,1348,129],{}," fills, and it's why the two boring-sounding features up top are actually the important ones: zero-install ",[127,1351,143],{}," and JSON on stdout aren't just convenient for you at the terminal. They're the exact two things an agent needs to use a tool with no help from anyone.",[11,1354,1355,1356,1359],{},"Once your agent runs ",[127,1357,1358],{},"npx climail",", it learns how to use it just by reading the help text. No MCP server to stand up, no SDK to wire in.",[11,1361,1362],{},"You can say something like:",[1364,1365,1366],"blockquote",{},[11,1367,1368],{},"Go through my unread mail. Label anything from a customer as \"Triaged\", and for the ones reporting a bug, draft a short reply letting them know we're on it.",[11,1370,1371,1372,1374,1375,1377,1378,1381,1382,1384],{},"And it will just... do it, by chaining the commands you saw above: ",[127,1373,702],{}," to find them, ",[127,1376,784],{}," to understand each one, ",[127,1379,1380],{},"label"," to file them, ",[127,1383,1135],{}," to stage the answers, checking the JSON at every step before taking the next.",[183,1386,1388],{"id":1387},"the-skill","The Skill",[11,1390,1391],{},"Do you like agent skills? We got you:",[194,1393,1395],{"className":196,"code":1394,"language":198,"meta":37,"style":37},"npx skills add nicodevs/climail\n",[127,1396,1397],{"__ignoreMap":37},[202,1398,1399,1401,1404,1407],{"class":204,"line":205},[202,1400,143],{"class":208},[202,1402,1403],{"class":211}," skills",[202,1405,1406],{"class":211}," add",[202,1408,1409],{"class":211}," nicodevs/climail\n",[167,1411,1413],{"id":1412},"in-closing","In Closing",[11,1415,1416,1417,1419],{},"I hope you enjoy ",[127,1418,129],{},". If you have any comments or questions, feel free to create an issue. And throw in a star on GitHub, if you want to help!",[11,1421,1422],{},"See you next time.",[1424,1425,1426],"style",{},"html pre.shiki code .sHkqI, html code.shiki .sHkqI{--shiki-default:#A6E22E}html pre.shiki code .s_Ekj, html code.shiki .s_Ekj{--shiki-default:#E6DB74}html pre.shiki code .s7s5_, html code.shiki .s7s5_{--shiki-default:#AE81FF}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 pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sOx1s, html code.shiki .sOx1s{--shiki-default:#66D9EF;--shiki-default-font-style:italic}html pre.shiki code .susgL, html code.shiki .susgL{--shiki-default:#CFCFC2}html pre.shiki code .s8I7P, html code.shiki .s8I7P{--shiki-default:#F92672}html pre.shiki code .snpHw, html code.shiki .snpHw{--shiki-default:#88846F}",{"title":37,"searchDepth":38,"depth":38,"links":1428},[1429,1435,1438,1439,1442],{"id":169,"depth":38,"text":170,"children":1430},[1431,1432,1433,1434],{"id":185,"depth":237,"text":186},{"id":259,"depth":237,"text":260},{"id":302,"depth":237,"text":303},{"id":601,"depth":237,"text":602},{"id":622,"depth":38,"text":623,"children":1436},[1437],{"id":883,"depth":237,"text":884},{"id":1158,"depth":38,"text":1159},{"id":1333,"depth":38,"text":1334,"children":1440},[1441],{"id":1387,"depth":237,"text":1388},{"id":1412,"depth":38,"text":1413},"2026-07-15","A clanker-friendly command line email client. Read, write, and organize your inbox from the terminal, with clean JSON that any AI agent can pick up.","/img/blog/climail-the-email-client-for-your-agent.png",{},"/blog/climail-the-email-client-for-your-agent",{"title":118,"description":1444},"blog/climail-the-email-client-for-your-agent","jZNqP6GquUg4sN1I-ki5zP8-RDPfr-xmKo1gaEXateg",{"id":5,"title":6,"body":1452,"category":40,"date":41,"description":13,"draft":42,"extension":43,"image":44,"meta":1469,"navigation":46,"path":47,"seo":1470,"stem":49,"tighten":46,"__hash__":50},{"type":8,"value":1453,"toc":1467},[1454,1456,1461,1463],[11,1455,13],{},[11,1457,16,1458,24],{},[18,1459,23],{"href":20,"rel":1460},[22],[11,1462,27],{},[29,1464,1465],{"image":31,"link":32,"title":6},[11,1466,35],{},{"title":37,"searchDepth":38,"depth":38,"links":1468},[],{},{"title":6,"description":13},[1472],{"id":1473,"title":1474,"body":1475,"category":40,"date":1506,"description":1479,"draft":42,"extension":43,"image":1507,"meta":1508,"navigation":46,"path":1509,"seo":1510,"stem":1511,"tighten":46,"__hash__":1512},"blog/blog/new-ish-vanilla-javascript-features-you-might-have-missed.md","17 New(ish) Vanilla JavaScript Features You Might Have Missed",{"type":8,"value":1476,"toc":1504},[1477,1480,1487,1490],[11,1478,1479],{},"Ahhh, that feeling when you discover JavaScript native features that let you say goodbye to bloated npm packages!",[11,1481,1482,1483,1486],{},"In this article I wrote for ",[18,1484,23],{"href":20,"rel":1485},[22],", we explore many new additions to the core language and some not as new but that might have flown under the radar.",[11,1488,1489],{},"Check it out!",[29,1491,1494,1501],{"image":1492,"link":1493,"title":1474},"https://tighten.com/assets/images/insights/new-ish-vanilla-javascript-features-you-might-have-missed-preview.jpeg","https://tighten.com/insights/new-ish-vanilla-javascript-features-you-might-have-missed/",[11,1495,1496,1497,1500],{},"JavaScript: the language many love to hate. Yeah, we've all seen the memes about its quirks and limitations. Maybe that's why we slap a whole typing system on top of it. And add one, two, or... ",[178,1498,1499],{},"ten thousand"," npm packages into our apps.",[11,1502,1503],{},"But today, we're doing something different. We're exploring some exciting new features that have landed in the language over the past years. Because, sometimes, vanilla is exactly what you need.",{"title":37,"searchDepth":38,"depth":38,"links":1505},[],"2025-11-05","/img/blog/new-ish-vanilla-javascript-features-you-might-have-missed.png",{},"/blog/new-ish-vanilla-javascript-features-you-might-have-missed",{"title":1474,"description":1479},"blog/new-ish-vanilla-javascript-features-you-might-have-missed","yUCYZYaxQsequCyY1VaK9RPb80ZGzD2_x2VX_scOegI",1790452840361]