[{"data":1,"prerenderedAt":3095},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Fguides\u002Fnodemailer\u002F":121,"\u002Fdocs\u002Fguides\u002Fnodemailer\u002F-surround":3090},[4,8,22,26,43,104],{"title":5,"path":6,"stem":7},"Getting Started","\u002Fdocs\u002Fgetting-started","1.docs\u002F1.getting-started",{"title":9,"path":10,"stem":11,"children":12,"page":21},"Setup","\u002Fdocs\u002Fsetup","1.docs\u002F2.setup",[13,17],{"title":14,"path":15,"stem":16},"Domain","\u002Fdocs\u002Fsetup\u002Fdomain","1.docs\u002F2.setup\u002F2.domain",{"title":18,"path":19,"stem":20},"Email","\u002Fdocs\u002Fsetup\u002Faccount","1.docs\u002F2.setup\u002F3.account",false,{"title":23,"path":24,"stem":25},"Email Testing API","\u002Fdocs\u002Fapi","1.docs\u002F3.api",{"title":27,"path":28,"stem":29,"children":30,"page":21},"Features","\u002Fdocs\u002Ffeatures","1.docs\u002F4.features",[31,35,39],{"title":32,"path":33,"stem":34},"Account","\u002Fdocs\u002Ffeatures\u002Faccount","1.docs\u002F4.features\u002F1.account",{"title":36,"path":37,"stem":38},"Mailbox","\u002Fdocs\u002Ffeatures\u002Fmailbox","1.docs\u002F4.features\u002F2.mailbox",{"title":40,"path":41,"stem":42},"Messages","\u002Fdocs\u002Ffeatures\u002Fmessage","1.docs\u002F4.features\u002F3.message",{"title":44,"path":45,"stem":46,"children":47,"page":21},"Guides","\u002Fdocs\u002Fguides","1.docs\u002F5.guides",[48,52,56,60,64,68,72,76,80,84,88,92,96,100],{"title":49,"path":50,"stem":51},"SMTP Test","\u002Fdocs\u002Fguides\u002Fsend-test-email","1.docs\u002F5.guides\u002F01.send-test-email",{"title":53,"path":54,"stem":55},"API Quickstart","\u002Fdocs\u002Fguides\u002Femail-testing-api","1.docs\u002F5.guides\u002F02.email-testing-api",{"title":57,"path":58,"stem":59},"CI","\u002Fdocs\u002Fguides\u002Fci","1.docs\u002F5.guides\u002F03.ci",{"title":61,"path":62,"stem":63},"Catch-All","\u002Fdocs\u002Fguides\u002Fcatch-all","1.docs\u002F5.guides\u002F04.catch-all",{"title":65,"path":66,"stem":67},"SPF, DKIM, DMARC","\u002Fdocs\u002Fguides\u002Fspf-dkim-dmarc","1.docs\u002F5.guides\u002F05.spf-dkim-dmarc",{"title":69,"path":70,"stem":71},"Playwright","\u002Fdocs\u002Fguides\u002Fe2e","1.docs\u002F5.guides\u002F06.e2e",{"title":73,"path":74,"stem":75},"Cypress","\u002Fdocs\u002Fguides\u002Fcypress","1.docs\u002F5.guides\u002F07.cypress",{"title":77,"path":78,"stem":79},"Email Clients","\u002Fdocs\u002Fguides\u002Femail-clients","1.docs\u002F5.guides\u002F08.email-clients",{"title":81,"path":82,"stem":83},"AI Agents","\u002Fdocs\u002Fguides\u002Fagents","1.docs\u002F5.guides\u002F09.agents",{"title":85,"path":86,"stem":87},"Local Development","\u002Fdocs\u002Fguides\u002Flocal-development","1.docs\u002F5.guides\u002F10.local-development",{"title":89,"path":90,"stem":91},"Size Limits","\u002Fdocs\u002Fguides\u002Femail-size-limits","1.docs\u002F5.guides\u002F11.email-size-limits",{"title":93,"path":94,"stem":95},"OTP & Magic Links","\u002Fdocs\u002Fguides\u002Fotp-magic-links","1.docs\u002F5.guides\u002F12.otp-magic-links",{"title":97,"path":98,"stem":99},"IMAP & POP3","\u002Fdocs\u002Fguides\u002Fimap-pop3","1.docs\u002F5.guides\u002F13.imap-pop3",{"title":101,"path":102,"stem":103},"Nodemailer","\u002Fdocs\u002Fguides\u002Fnodemailer","1.docs\u002F5.guides\u002F14.nodemailer",{"title":105,"path":106,"stem":107,"children":108,"page":21},"Alternatives","\u002Fdocs\u002Falternatives","1.docs\u002F6.alternatives",[109,113,117],{"title":110,"path":111,"stem":112},"Mailtrap","\u002Fdocs\u002Falternatives\u002Fmailtrap","1.docs\u002F6.alternatives\u002F1.mailtrap",{"title":114,"path":115,"stem":116},"Mailosaur","\u002Fdocs\u002Falternatives\u002Fmailosaur","1.docs\u002F6.alternatives\u002F2.mailosaur",{"title":118,"path":119,"stem":120},"Mailinator","\u002Fdocs\u002Falternatives\u002Fmailinator","1.docs\u002F6.alternatives\u002F3.mailinator",{"id":122,"title":123,"body":124,"description":3084,"extension":3085,"meta":3086,"navigation":3087,"path":102,"seo":3088,"stem":103,"__hash__":3089},"docs\u002F1.docs\u002F5.guides\u002F14.nodemailer.md","How to Test Nodemailer Emails with a Real SMTP Server",{"type":125,"value":126,"toc":3073},"minimark",[127,140,147,152,155,555,600,608,622,626,639,1467,1470,2065,2072,2132,2136,2479,2498,2502,2527,2708,2727,2731,2736,2759,2765,2769,2779,2868,2872,2974,2977,3017,3023,3027,3039,3043,3069],[128,129,130,131,135,136,139],"p",{},"There are three ways to test code that sends with Nodemailer. Mock ",[132,133,134],"code",{},"sendMail",", and you check that your code called it. Use ",[132,137,138],{},"jsonTransport",", and you check the message it built. Send through a real SMTP server that keeps the mail, and you check everything in between: the login, TLS, the MIME encoding, and what a recipient would receive.",[128,141,142,143,146],{},"This guide does the third, with ",[132,144,145],{},"send.smtp.dev"," as the server. It delivers only to accounts in your sandbox, so a test can't email a real person.",[148,149,151],"h2",{"id":150},"the-transport","The Transport",[128,153,154],{},"Read the settings from the environment, so tests, staging, and production differ only in their values:",[156,157,162],"pre",{"className":158,"code":159,"language":160,"meta":161,"style":161},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import nodemailer from 'nodemailer'\n\nexport const transport = nodemailer.createTransport({\n  host: process.env.SMTP_HOST,\n  port: Number(process.env.SMTP_PORT),\n  requireTLS: true,\n  auth: {\n    user: process.env.SMTP_USER,\n    pass: process.env.SMTP_PASSWORD,\n  },\n})\n\nexport function sendWelcome(user) {\n  const link = `https:\u002F\u002Fexample.com\u002Fconfirm\u002F${user.token}`\n  return transport.sendMail({\n    from: '\"Acme\" \u003Capp@test.example.com>',\n    to: user.email,\n    subject: 'Welcome to Acme',\n    text: `Confirm your address: ${link}`,\n    html: `\u003Cp>\u003Ca href=\"${link}\">Confirm your address\u003C\u002Fa>\u003C\u002Fp>`,\n  })\n}\n","js","",[132,163,164,191,198,230,256,281,295,306,327,348,354,363,368,390,421,438,456,474,491,514,541,549],{"__ignoreMap":161},[165,166,169,173,177,180,184,188],"span",{"class":167,"line":168},"line",1,[165,170,172],{"class":171},"s7zQu","import",[165,174,176],{"class":175},"sTEyZ"," nodemailer ",[165,178,179],{"class":171},"from",[165,181,183],{"class":182},"sMK4o"," '",[165,185,187],{"class":186},"sfazB","nodemailer",[165,189,190],{"class":182},"'\n",[165,192,194],{"class":167,"line":193},2,[165,195,197],{"emptyLinePlaceholder":196},true,"\n",[165,199,201,204,208,211,214,217,220,224,227],{"class":167,"line":200},3,[165,202,203],{"class":171},"export",[165,205,207],{"class":206},"spNyl"," const",[165,209,210],{"class":175}," transport ",[165,212,213],{"class":182},"=",[165,215,216],{"class":175}," nodemailer",[165,218,219],{"class":182},".",[165,221,223],{"class":222},"s2Zo4","createTransport",[165,225,226],{"class":175},"(",[165,228,229],{"class":182},"{\n",[165,231,233,237,240,243,245,248,250,253],{"class":167,"line":232},4,[165,234,236],{"class":235},"swJcz","  host",[165,238,239],{"class":182},":",[165,241,242],{"class":175}," process",[165,244,219],{"class":182},[165,246,247],{"class":175},"env",[165,249,219],{"class":182},[165,251,252],{"class":175},"SMTP_HOST",[165,254,255],{"class":182},",\n",[165,257,259,262,264,267,270,272,274,276,279],{"class":167,"line":258},5,[165,260,261],{"class":235},"  port",[165,263,239],{"class":182},[165,265,266],{"class":222}," Number",[165,268,269],{"class":175},"(process",[165,271,219],{"class":182},[165,273,247],{"class":175},[165,275,219],{"class":182},[165,277,278],{"class":175},"SMTP_PORT)",[165,280,255],{"class":182},[165,282,284,287,289,293],{"class":167,"line":283},6,[165,285,286],{"class":235},"  requireTLS",[165,288,239],{"class":182},[165,290,292],{"class":291},"sfNiH"," true",[165,294,255],{"class":182},[165,296,298,301,303],{"class":167,"line":297},7,[165,299,300],{"class":235},"  auth",[165,302,239],{"class":182},[165,304,305],{"class":182}," {\n",[165,307,309,312,314,316,318,320,322,325],{"class":167,"line":308},8,[165,310,311],{"class":235},"    user",[165,313,239],{"class":182},[165,315,242],{"class":175},[165,317,219],{"class":182},[165,319,247],{"class":175},[165,321,219],{"class":182},[165,323,324],{"class":175},"SMTP_USER",[165,326,255],{"class":182},[165,328,330,333,335,337,339,341,343,346],{"class":167,"line":329},9,[165,331,332],{"class":235},"    pass",[165,334,239],{"class":182},[165,336,242],{"class":175},[165,338,219],{"class":182},[165,340,247],{"class":175},[165,342,219],{"class":182},[165,344,345],{"class":175},"SMTP_PASSWORD",[165,347,255],{"class":182},[165,349,351],{"class":167,"line":350},10,[165,352,353],{"class":182},"  },\n",[165,355,357,360],{"class":167,"line":356},11,[165,358,359],{"class":182},"}",[165,361,362],{"class":175},")\n",[165,364,366],{"class":167,"line":365},12,[165,367,197],{"emptyLinePlaceholder":196},[165,369,371,373,376,379,381,385,388],{"class":167,"line":370},13,[165,372,203],{"class":171},[165,374,375],{"class":206}," function",[165,377,378],{"class":222}," sendWelcome",[165,380,226],{"class":182},[165,382,384],{"class":383},"sHdIc","user",[165,386,387],{"class":182},")",[165,389,305],{"class":182},[165,391,393,396,399,402,405,408,411,413,415,418],{"class":167,"line":392},14,[165,394,395],{"class":206},"  const",[165,397,398],{"class":175}," link",[165,400,401],{"class":182}," =",[165,403,404],{"class":182}," `",[165,406,407],{"class":186},"https:\u002F\u002Fexample.com\u002Fconfirm\u002F",[165,409,410],{"class":182},"${",[165,412,384],{"class":175},[165,414,219],{"class":182},[165,416,417],{"class":175},"token",[165,419,420],{"class":182},"}`\n",[165,422,424,427,430,432,434,436],{"class":167,"line":423},15,[165,425,426],{"class":171},"  return",[165,428,429],{"class":175}," transport",[165,431,219],{"class":182},[165,433,134],{"class":222},[165,435,226],{"class":235},[165,437,229],{"class":182},[165,439,441,444,446,448,451,454],{"class":167,"line":440},16,[165,442,443],{"class":235},"    from",[165,445,239],{"class":182},[165,447,183],{"class":182},[165,449,450],{"class":186},"\"Acme\" \u003Capp@test.example.com>",[165,452,453],{"class":182},"'",[165,455,255],{"class":182},[165,457,459,462,464,467,469,472],{"class":167,"line":458},17,[165,460,461],{"class":235},"    to",[165,463,239],{"class":182},[165,465,466],{"class":175}," user",[165,468,219],{"class":182},[165,470,471],{"class":175},"email",[165,473,255],{"class":182},[165,475,477,480,482,484,487,489],{"class":167,"line":476},18,[165,478,479],{"class":235},"    subject",[165,481,239],{"class":182},[165,483,183],{"class":182},[165,485,486],{"class":186},"Welcome to Acme",[165,488,453],{"class":182},[165,490,255],{"class":182},[165,492,494,497,499,501,504,506,509,512],{"class":167,"line":493},19,[165,495,496],{"class":235},"    text",[165,498,239],{"class":182},[165,500,404],{"class":182},[165,502,503],{"class":186},"Confirm your address: ",[165,505,410],{"class":182},[165,507,508],{"class":175},"link",[165,510,511],{"class":182},"}`",[165,513,255],{"class":182},[165,515,517,520,522,524,527,529,531,533,536,539],{"class":167,"line":516},20,[165,518,519],{"class":235},"    html",[165,521,239],{"class":182},[165,523,404],{"class":182},[165,525,526],{"class":186},"\u003Cp>\u003Ca href=\"",[165,528,410],{"class":182},[165,530,508],{"class":175},[165,532,359],{"class":182},[165,534,535],{"class":186},"\">Confirm your address\u003C\u002Fa>\u003C\u002Fp>",[165,537,538],{"class":182},"`",[165,540,255],{"class":182},[165,542,544,547],{"class":167,"line":543},21,[165,545,546],{"class":182},"  }",[165,548,362],{"class":235},[165,550,552],{"class":167,"line":551},22,[165,553,554],{"class":182},"}\n",[156,556,560],{"className":557,"code":558,"language":559,"meta":161,"style":161},"language-dotenv shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","SMTP_HOST=send.smtp.dev\nSMTP_PORT=587\nSMTP_USER=app@test.example.com\nSMTP_PASSWORD=your-account-password\n","dotenv",[132,561,562,571,582,591],{"__ignoreMap":161},[165,563,564,566,568],{"class":167,"line":168},[165,565,252],{"class":175},[165,567,213],{"class":182},[165,569,570],{"class":175},"send.smtp.dev\n",[165,572,573,576,578],{"class":167,"line":193},[165,574,575],{"class":175},"SMTP_PORT",[165,577,213],{"class":182},[165,579,581],{"class":580},"sbssI","587\n",[165,583,584,586,588],{"class":167,"line":200},[165,585,324],{"class":175},[165,587,213],{"class":182},[165,589,590],{"class":175},"app@test.example.com\n",[165,592,593,595,597],{"class":167,"line":232},[165,594,345],{"class":175},[165,596,213],{"class":182},[165,598,599],{"class":175},"your-account-password\n",[128,601,602,604,605,607],{},[132,603,324],{}," is a sandbox account's full address, and ",[132,606,345],{}," is that account's password.",[128,609,610,611,614,615,618,619,219],{},"Port 587 starts in plain text and upgrades with STARTTLS. ",[132,612,613],{},"requireTLS: true"," makes Nodemailer stop if the upgrade fails, instead of sending the password unencrypted. ",[132,616,617],{},"secure: true"," is for port 465, where TLS starts with the connection. On 587 it fails with ",[132,620,621],{},"wrong version number",[148,623,625],{"id":624},"send-and-read-it-back","Send and Read It Back",[128,627,628,629,634,635,638],{},"The test reads the message over the ",[630,631,633],"a",{"href":632},"\u002Fdocs\u002Fapi\u002F","REST API",". A small helper, ",[132,636,637],{},"mail.js",", finds an account's inbox and waits for a message to a given address:",[156,640,642],{"className":158,"code":641,"language":160,"meta":161,"style":161},"import { Buffer } from 'node:buffer'\n\nconst API = 'https:\u002F\u002Fapi.smtp.dev'\nconst headers = { 'X-API-KEY': process.env.SMTP_DEV_API_KEY }\n\nasync function request(path) {\n  const res = await fetch(new URL(path, API), { headers })\n  if (!res.ok)\n    throw new Error(`${path} returned ${res.status}`)\n  return res\n}\n\nexport const api = path => request(path).then(res => res.json())\nexport const download = path => request(path).then(res => res.arrayBuffer()).then(Buffer.from)\n\nexport async function inboxOf(address) {\n  const { member } = await api(`\u002Faccounts?address=${encodeURIComponent(address)}`)\n  const account = member.find(a => a.address === address)\n  const { mailboxes } = await api(`\u002Faccounts\u002F${account.id}`)\n  return `\u002Faccounts\u002F${account.id}\u002Fmailboxes\u002F${mailboxes.find(m => m.path === 'INBOX').id}\u002Fmessages`\n}\n\nexport async function waitForMessage(inbox, to, timeout = 30_000) {\n  const deadline = Date.now() + timeout\n  while (Date.now() \u003C deadline) {\n    const { member } = await api(inbox)\n    const found = member.find(m => m.to.some(t => t.address === to))\n    if (found)\n      return api(`${inbox}\u002F${found.id}`)\n    await new Promise(resolve => setTimeout(resolve, 1000))\n  }\n  throw new Error(`no message to ${to} after ${timeout}ms`)\n}\n",[132,643,644,667,671,688,721,725,744,790,811,847,854,858,862,905,956,960,981,1018,1055,1093,1162,1166,1170,1206,1233,1260,1284,1339,1352,1383,1416,1422,1462],{"__ignoreMap":161},[165,645,646,648,651,654,657,660,662,665],{"class":167,"line":168},[165,647,172],{"class":171},[165,649,650],{"class":182}," {",[165,652,653],{"class":175}," Buffer",[165,655,656],{"class":182}," }",[165,658,659],{"class":171}," from",[165,661,183],{"class":182},[165,663,664],{"class":186},"node:buffer",[165,666,190],{"class":182},[165,668,669],{"class":167,"line":193},[165,670,197],{"emptyLinePlaceholder":196},[165,672,673,676,679,681,683,686],{"class":167,"line":200},[165,674,675],{"class":206},"const",[165,677,678],{"class":175}," API ",[165,680,213],{"class":182},[165,682,183],{"class":182},[165,684,685],{"class":186},"https:\u002F\u002Fapi.smtp.dev",[165,687,190],{"class":182},[165,689,690,692,695,697,699,701,704,706,708,710,712,714,716,719],{"class":167,"line":232},[165,691,675],{"class":206},[165,693,694],{"class":175}," headers ",[165,696,213],{"class":182},[165,698,650],{"class":182},[165,700,183],{"class":182},[165,702,703],{"class":235},"X-API-KEY",[165,705,453],{"class":182},[165,707,239],{"class":182},[165,709,242],{"class":175},[165,711,219],{"class":182},[165,713,247],{"class":175},[165,715,219],{"class":182},[165,717,718],{"class":175},"SMTP_DEV_API_KEY ",[165,720,554],{"class":182},[165,722,723],{"class":167,"line":258},[165,724,197],{"emptyLinePlaceholder":196},[165,726,727,730,732,735,737,740,742],{"class":167,"line":283},[165,728,729],{"class":206},"async",[165,731,375],{"class":206},[165,733,734],{"class":222}," request",[165,736,226],{"class":182},[165,738,739],{"class":383},"path",[165,741,387],{"class":182},[165,743,305],{"class":182},[165,745,746,748,751,753,756,759,761,764,767,769,771,774,777,779,781,783,786,788],{"class":167,"line":297},[165,747,395],{"class":206},[165,749,750],{"class":175}," res",[165,752,401],{"class":182},[165,754,755],{"class":171}," await",[165,757,758],{"class":222}," fetch",[165,760,226],{"class":235},[165,762,763],{"class":182},"new",[165,765,766],{"class":222}," URL",[165,768,226],{"class":235},[165,770,739],{"class":175},[165,772,773],{"class":182},",",[165,775,776],{"class":175}," API",[165,778,387],{"class":235},[165,780,773],{"class":182},[165,782,650],{"class":182},[165,784,785],{"class":175}," headers",[165,787,656],{"class":182},[165,789,362],{"class":235},[165,791,792,795,798,801,804,806,809],{"class":167,"line":308},[165,793,794],{"class":171},"  if",[165,796,797],{"class":235}," (",[165,799,800],{"class":182},"!",[165,802,803],{"class":175},"res",[165,805,219],{"class":182},[165,807,808],{"class":175},"ok",[165,810,362],{"class":235},[165,812,813,816,819,822,824,827,829,831,834,836,838,840,843,845],{"class":167,"line":329},[165,814,815],{"class":171},"    throw",[165,817,818],{"class":182}," new",[165,820,821],{"class":222}," Error",[165,823,226],{"class":235},[165,825,826],{"class":182},"`${",[165,828,739],{"class":175},[165,830,359],{"class":182},[165,832,833],{"class":186}," returned ",[165,835,410],{"class":182},[165,837,803],{"class":175},[165,839,219],{"class":182},[165,841,842],{"class":175},"status",[165,844,511],{"class":182},[165,846,362],{"class":235},[165,848,849,851],{"class":167,"line":350},[165,850,426],{"class":171},[165,852,853],{"class":175}," res\n",[165,855,856],{"class":167,"line":356},[165,857,554],{"class":182},[165,859,860],{"class":167,"line":365},[165,861,197],{"emptyLinePlaceholder":196},[165,863,864,866,868,871,873,876,879,881,884,886,889,891,893,895,897,899,902],{"class":167,"line":370},[165,865,203],{"class":171},[165,867,207],{"class":206},[165,869,870],{"class":175}," api ",[165,872,213],{"class":182},[165,874,875],{"class":383}," path",[165,877,878],{"class":206}," =>",[165,880,734],{"class":222},[165,882,883],{"class":175},"(path)",[165,885,219],{"class":182},[165,887,888],{"class":222},"then",[165,890,226],{"class":175},[165,892,803],{"class":383},[165,894,878],{"class":206},[165,896,750],{"class":175},[165,898,219],{"class":182},[165,900,901],{"class":222},"json",[165,903,904],{"class":175},"())\n",[165,906,907,909,911,914,916,918,920,922,924,926,928,930,932,934,936,938,941,944,946,948,951,953],{"class":167,"line":392},[165,908,203],{"class":171},[165,910,207],{"class":206},[165,912,913],{"class":175}," download ",[165,915,213],{"class":182},[165,917,875],{"class":383},[165,919,878],{"class":206},[165,921,734],{"class":222},[165,923,883],{"class":175},[165,925,219],{"class":182},[165,927,888],{"class":222},[165,929,226],{"class":175},[165,931,803],{"class":383},[165,933,878],{"class":206},[165,935,750],{"class":175},[165,937,219],{"class":182},[165,939,940],{"class":222},"arrayBuffer",[165,942,943],{"class":175},"())",[165,945,219],{"class":182},[165,947,888],{"class":222},[165,949,950],{"class":175},"(Buffer",[165,952,219],{"class":182},[165,954,955],{"class":175},"from)\n",[165,957,958],{"class":167,"line":423},[165,959,197],{"emptyLinePlaceholder":196},[165,961,962,964,967,969,972,974,977,979],{"class":167,"line":440},[165,963,203],{"class":171},[165,965,966],{"class":206}," async",[165,968,375],{"class":206},[165,970,971],{"class":222}," inboxOf",[165,973,226],{"class":182},[165,975,976],{"class":383},"address",[165,978,387],{"class":182},[165,980,305],{"class":182},[165,982,983,985,987,990,992,994,996,999,1001,1003,1006,1008,1011,1014,1016],{"class":167,"line":458},[165,984,395],{"class":206},[165,986,650],{"class":182},[165,988,989],{"class":175}," member",[165,991,656],{"class":182},[165,993,401],{"class":182},[165,995,755],{"class":171},[165,997,998],{"class":222}," api",[165,1000,226],{"class":235},[165,1002,538],{"class":182},[165,1004,1005],{"class":186},"\u002Faccounts?address=",[165,1007,410],{"class":182},[165,1009,1010],{"class":222},"encodeURIComponent",[165,1012,1013],{"class":175},"(address)",[165,1015,511],{"class":182},[165,1017,362],{"class":235},[165,1019,1020,1022,1025,1027,1029,1031,1034,1036,1038,1040,1043,1045,1047,1050,1053],{"class":167,"line":476},[165,1021,395],{"class":206},[165,1023,1024],{"class":175}," account",[165,1026,401],{"class":182},[165,1028,989],{"class":175},[165,1030,219],{"class":182},[165,1032,1033],{"class":222},"find",[165,1035,226],{"class":235},[165,1037,630],{"class":383},[165,1039,878],{"class":206},[165,1041,1042],{"class":175}," a",[165,1044,219],{"class":182},[165,1046,976],{"class":175},[165,1048,1049],{"class":182}," ===",[165,1051,1052],{"class":175}," address",[165,1054,362],{"class":235},[165,1056,1057,1059,1061,1064,1066,1068,1070,1072,1074,1076,1079,1081,1084,1086,1089,1091],{"class":167,"line":493},[165,1058,395],{"class":206},[165,1060,650],{"class":182},[165,1062,1063],{"class":175}," mailboxes",[165,1065,656],{"class":182},[165,1067,401],{"class":182},[165,1069,755],{"class":171},[165,1071,998],{"class":222},[165,1073,226],{"class":235},[165,1075,538],{"class":182},[165,1077,1078],{"class":186},"\u002Faccounts\u002F",[165,1080,410],{"class":182},[165,1082,1083],{"class":175},"account",[165,1085,219],{"class":182},[165,1087,1088],{"class":175},"id",[165,1090,511],{"class":182},[165,1092,362],{"class":235},[165,1094,1095,1097,1099,1101,1103,1105,1107,1109,1111,1114,1116,1119,1121,1123,1125,1128,1130,1133,1135,1138,1141,1143,1146,1148,1150,1152,1154,1156,1159],{"class":167,"line":516},[165,1096,426],{"class":171},[165,1098,404],{"class":182},[165,1100,1078],{"class":186},[165,1102,410],{"class":182},[165,1104,1083],{"class":175},[165,1106,219],{"class":182},[165,1108,1088],{"class":175},[165,1110,359],{"class":182},[165,1112,1113],{"class":186},"\u002Fmailboxes\u002F",[165,1115,410],{"class":182},[165,1117,1118],{"class":175},"mailboxes",[165,1120,219],{"class":182},[165,1122,1033],{"class":222},[165,1124,226],{"class":175},[165,1126,1127],{"class":383},"m",[165,1129,878],{"class":206},[165,1131,1132],{"class":175}," m",[165,1134,219],{"class":182},[165,1136,1137],{"class":175},"path ",[165,1139,1140],{"class":182},"===",[165,1142,183],{"class":182},[165,1144,1145],{"class":186},"INBOX",[165,1147,453],{"class":182},[165,1149,387],{"class":175},[165,1151,219],{"class":182},[165,1153,1088],{"class":175},[165,1155,359],{"class":182},[165,1157,1158],{"class":186},"\u002Fmessages",[165,1160,1161],{"class":182},"`\n",[165,1163,1164],{"class":167,"line":543},[165,1165,554],{"class":182},[165,1167,1168],{"class":167,"line":551},[165,1169,197],{"emptyLinePlaceholder":196},[165,1171,1173,1175,1177,1179,1182,1184,1187,1189,1192,1194,1197,1199,1202,1204],{"class":167,"line":1172},23,[165,1174,203],{"class":171},[165,1176,966],{"class":206},[165,1178,375],{"class":206},[165,1180,1181],{"class":222}," waitForMessage",[165,1183,226],{"class":182},[165,1185,1186],{"class":383},"inbox",[165,1188,773],{"class":182},[165,1190,1191],{"class":383}," to",[165,1193,773],{"class":182},[165,1195,1196],{"class":383}," timeout",[165,1198,401],{"class":182},[165,1200,1201],{"class":580}," 30_000",[165,1203,387],{"class":182},[165,1205,305],{"class":182},[165,1207,1209,1211,1214,1216,1219,1221,1224,1227,1230],{"class":167,"line":1208},24,[165,1210,395],{"class":206},[165,1212,1213],{"class":175}," deadline",[165,1215,401],{"class":182},[165,1217,1218],{"class":175}," Date",[165,1220,219],{"class":182},[165,1222,1223],{"class":222},"now",[165,1225,1226],{"class":235},"() ",[165,1228,1229],{"class":182},"+",[165,1231,1232],{"class":175}," timeout\n",[165,1234,1236,1239,1241,1244,1246,1248,1250,1253,1255,1258],{"class":167,"line":1235},25,[165,1237,1238],{"class":171},"  while",[165,1240,797],{"class":235},[165,1242,1243],{"class":175},"Date",[165,1245,219],{"class":182},[165,1247,1223],{"class":222},[165,1249,1226],{"class":235},[165,1251,1252],{"class":182},"\u003C",[165,1254,1213],{"class":175},[165,1256,1257],{"class":235},") ",[165,1259,229],{"class":182},[165,1261,1263,1266,1268,1270,1272,1274,1276,1278,1280,1282],{"class":167,"line":1262},26,[165,1264,1265],{"class":206},"    const",[165,1267,650],{"class":182},[165,1269,989],{"class":175},[165,1271,656],{"class":182},[165,1273,401],{"class":182},[165,1275,755],{"class":171},[165,1277,998],{"class":222},[165,1279,226],{"class":235},[165,1281,1186],{"class":175},[165,1283,362],{"class":235},[165,1285,1287,1289,1292,1294,1296,1298,1300,1302,1304,1306,1308,1310,1313,1315,1318,1320,1323,1325,1328,1330,1332,1334,1336],{"class":167,"line":1286},27,[165,1288,1265],{"class":206},[165,1290,1291],{"class":175}," found",[165,1293,401],{"class":182},[165,1295,989],{"class":175},[165,1297,219],{"class":182},[165,1299,1033],{"class":222},[165,1301,226],{"class":235},[165,1303,1127],{"class":383},[165,1305,878],{"class":206},[165,1307,1132],{"class":175},[165,1309,219],{"class":182},[165,1311,1312],{"class":175},"to",[165,1314,219],{"class":182},[165,1316,1317],{"class":222},"some",[165,1319,226],{"class":235},[165,1321,1322],{"class":383},"t",[165,1324,878],{"class":206},[165,1326,1327],{"class":175}," t",[165,1329,219],{"class":182},[165,1331,976],{"class":175},[165,1333,1049],{"class":182},[165,1335,1191],{"class":175},[165,1337,1338],{"class":235},"))\n",[165,1340,1342,1345,1347,1350],{"class":167,"line":1341},28,[165,1343,1344],{"class":171},"    if",[165,1346,797],{"class":235},[165,1348,1349],{"class":175},"found",[165,1351,362],{"class":235},[165,1353,1355,1358,1360,1362,1364,1366,1368,1371,1373,1375,1377,1379,1381],{"class":167,"line":1354},29,[165,1356,1357],{"class":171},"      return",[165,1359,998],{"class":222},[165,1361,226],{"class":235},[165,1363,826],{"class":182},[165,1365,1186],{"class":175},[165,1367,359],{"class":182},[165,1369,1370],{"class":186},"\u002F",[165,1372,410],{"class":182},[165,1374,1349],{"class":175},[165,1376,219],{"class":182},[165,1378,1088],{"class":175},[165,1380,511],{"class":182},[165,1382,362],{"class":235},[165,1384,1386,1389,1391,1395,1397,1400,1402,1405,1407,1409,1411,1414],{"class":167,"line":1385},30,[165,1387,1388],{"class":171},"    await",[165,1390,818],{"class":182},[165,1392,1394],{"class":1393},"sBMFI"," Promise",[165,1396,226],{"class":235},[165,1398,1399],{"class":383},"resolve",[165,1401,878],{"class":206},[165,1403,1404],{"class":222}," setTimeout",[165,1406,226],{"class":235},[165,1408,1399],{"class":175},[165,1410,773],{"class":182},[165,1412,1413],{"class":580}," 1000",[165,1415,1338],{"class":235},[165,1417,1419],{"class":167,"line":1418},31,[165,1420,1421],{"class":182},"  }\n",[165,1423,1425,1428,1430,1432,1434,1436,1439,1441,1443,1445,1448,1450,1453,1455,1458,1460],{"class":167,"line":1424},32,[165,1426,1427],{"class":171},"  throw",[165,1429,818],{"class":182},[165,1431,821],{"class":222},[165,1433,226],{"class":235},[165,1435,538],{"class":182},[165,1437,1438],{"class":186},"no message to ",[165,1440,410],{"class":182},[165,1442,1312],{"class":175},[165,1444,359],{"class":182},[165,1446,1447],{"class":186}," after ",[165,1449,410],{"class":182},[165,1451,1452],{"class":175},"timeout",[165,1454,359],{"class":182},[165,1456,1457],{"class":186},"ms",[165,1459,538],{"class":182},[165,1461,362],{"class":235},[165,1463,1465],{"class":167,"line":1464},33,[165,1466,554],{"class":182},[128,1468,1469],{},"Then the test, with Node's built-in runner:",[156,1471,1473],{"className":158,"code":1472,"language":160,"meta":161,"style":161},"import assert from 'node:assert'\nimport { before, test } from 'node:test'\nimport * as cheerio from 'cheerio'\nimport { inboxOf, waitForMessage } from '.\u002Fmail.js'\nimport { sendWelcome, transport } from '.\u002Fmailer.js'\n\nlet inbox\nbefore(async () => {\n  await transport.verify()\n  inbox = await inboxOf('user@test.example.com')\n})\n\ntest('welcome email', async () => {\n  const email = `user+${crypto.randomUUID()}@test.example.com`\n  const info = await sendWelcome({ email, token: 'abc123' })\n  assert.deepEqual(info.rejected, [])\n\n  const message = await waitForMessage(inbox, email)\n  assert.equal(message.subject, 'Welcome to Acme')\n  assert.equal(message.from.address, 'app@test.example.com')\n  assert.equal(message.from.name, 'Acme')\n\n  const link = message.text.match(\u002Fhttps:\\\u002F\\\u002F\\S+\u002F)[0]\n  assert.equal(link, 'https:\u002F\u002Fexample.com\u002Fconfirm\u002Fabc123')\n\n  const $ = cheerio.load(message.html.join(''))\n  assert.equal($('a:contains(\"Confirm your address\")').attr('href'), link)\n})\n",[132,1474,1475,1491,1516,1538,1561,1584,1588,1596,1612,1627,1649,1655,1659,1683,1717,1755,1780,1784,1807,1836,1867,1899,1903,1948,1971,1975,2013,2059],{"__ignoreMap":161},[165,1476,1477,1479,1482,1484,1486,1489],{"class":167,"line":168},[165,1478,172],{"class":171},[165,1480,1481],{"class":175}," assert ",[165,1483,179],{"class":171},[165,1485,183],{"class":182},[165,1487,1488],{"class":186},"node:assert",[165,1490,190],{"class":182},[165,1492,1493,1495,1497,1500,1502,1505,1507,1509,1511,1514],{"class":167,"line":193},[165,1494,172],{"class":171},[165,1496,650],{"class":182},[165,1498,1499],{"class":175}," before",[165,1501,773],{"class":182},[165,1503,1504],{"class":175}," test",[165,1506,656],{"class":182},[165,1508,659],{"class":171},[165,1510,183],{"class":182},[165,1512,1513],{"class":186},"node:test",[165,1515,190],{"class":182},[165,1517,1518,1520,1523,1526,1529,1531,1533,1536],{"class":167,"line":200},[165,1519,172],{"class":171},[165,1521,1522],{"class":182}," *",[165,1524,1525],{"class":171}," as",[165,1527,1528],{"class":175}," cheerio ",[165,1530,179],{"class":171},[165,1532,183],{"class":182},[165,1534,1535],{"class":186},"cheerio",[165,1537,190],{"class":182},[165,1539,1540,1542,1544,1546,1548,1550,1552,1554,1556,1559],{"class":167,"line":232},[165,1541,172],{"class":171},[165,1543,650],{"class":182},[165,1545,971],{"class":175},[165,1547,773],{"class":182},[165,1549,1181],{"class":175},[165,1551,656],{"class":182},[165,1553,659],{"class":171},[165,1555,183],{"class":182},[165,1557,1558],{"class":186},".\u002Fmail.js",[165,1560,190],{"class":182},[165,1562,1563,1565,1567,1569,1571,1573,1575,1577,1579,1582],{"class":167,"line":258},[165,1564,172],{"class":171},[165,1566,650],{"class":182},[165,1568,378],{"class":175},[165,1570,773],{"class":182},[165,1572,429],{"class":175},[165,1574,656],{"class":182},[165,1576,659],{"class":171},[165,1578,183],{"class":182},[165,1580,1581],{"class":186},".\u002Fmailer.js",[165,1583,190],{"class":182},[165,1585,1586],{"class":167,"line":283},[165,1587,197],{"emptyLinePlaceholder":196},[165,1589,1590,1593],{"class":167,"line":297},[165,1591,1592],{"class":206},"let",[165,1594,1595],{"class":175}," inbox\n",[165,1597,1598,1601,1603,1605,1608,1610],{"class":167,"line":308},[165,1599,1600],{"class":222},"before",[165,1602,226],{"class":175},[165,1604,729],{"class":206},[165,1606,1607],{"class":182}," ()",[165,1609,878],{"class":206},[165,1611,305],{"class":182},[165,1613,1614,1617,1619,1621,1624],{"class":167,"line":329},[165,1615,1616],{"class":171},"  await",[165,1618,429],{"class":175},[165,1620,219],{"class":182},[165,1622,1623],{"class":222},"verify",[165,1625,1626],{"class":235},"()\n",[165,1628,1629,1632,1634,1636,1638,1640,1642,1645,1647],{"class":167,"line":350},[165,1630,1631],{"class":175},"  inbox",[165,1633,401],{"class":182},[165,1635,755],{"class":171},[165,1637,971],{"class":222},[165,1639,226],{"class":235},[165,1641,453],{"class":182},[165,1643,1644],{"class":186},"user@test.example.com",[165,1646,453],{"class":182},[165,1648,362],{"class":235},[165,1650,1651,1653],{"class":167,"line":356},[165,1652,359],{"class":182},[165,1654,362],{"class":175},[165,1656,1657],{"class":167,"line":365},[165,1658,197],{"emptyLinePlaceholder":196},[165,1660,1661,1664,1666,1668,1671,1673,1675,1677,1679,1681],{"class":167,"line":370},[165,1662,1663],{"class":222},"test",[165,1665,226],{"class":175},[165,1667,453],{"class":182},[165,1669,1670],{"class":186},"welcome email",[165,1672,453],{"class":182},[165,1674,773],{"class":182},[165,1676,966],{"class":206},[165,1678,1607],{"class":182},[165,1680,878],{"class":206},[165,1682,305],{"class":182},[165,1684,1685,1687,1690,1692,1694,1697,1699,1702,1704,1707,1710,1712,1715],{"class":167,"line":392},[165,1686,395],{"class":206},[165,1688,1689],{"class":175}," email",[165,1691,401],{"class":182},[165,1693,404],{"class":182},[165,1695,1696],{"class":186},"user+",[165,1698,410],{"class":182},[165,1700,1701],{"class":175},"crypto",[165,1703,219],{"class":182},[165,1705,1706],{"class":222},"randomUUID",[165,1708,1709],{"class":175},"()",[165,1711,359],{"class":182},[165,1713,1714],{"class":186},"@test.example.com",[165,1716,1161],{"class":182},[165,1718,1719,1721,1724,1726,1728,1730,1732,1735,1737,1739,1742,1744,1746,1749,1751,1753],{"class":167,"line":423},[165,1720,395],{"class":206},[165,1722,1723],{"class":175}," info",[165,1725,401],{"class":182},[165,1727,755],{"class":171},[165,1729,378],{"class":222},[165,1731,226],{"class":235},[165,1733,1734],{"class":182},"{",[165,1736,1689],{"class":175},[165,1738,773],{"class":182},[165,1740,1741],{"class":235}," token",[165,1743,239],{"class":182},[165,1745,183],{"class":182},[165,1747,1748],{"class":186},"abc123",[165,1750,453],{"class":182},[165,1752,656],{"class":182},[165,1754,362],{"class":235},[165,1756,1757,1760,1762,1765,1767,1770,1772,1775,1777],{"class":167,"line":440},[165,1758,1759],{"class":175},"  assert",[165,1761,219],{"class":182},[165,1763,1764],{"class":222},"deepEqual",[165,1766,226],{"class":235},[165,1768,1769],{"class":175},"info",[165,1771,219],{"class":182},[165,1773,1774],{"class":175},"rejected",[165,1776,773],{"class":182},[165,1778,1779],{"class":235}," [])\n",[165,1781,1782],{"class":167,"line":458},[165,1783,197],{"emptyLinePlaceholder":196},[165,1785,1786,1788,1791,1793,1795,1797,1799,1801,1803,1805],{"class":167,"line":476},[165,1787,395],{"class":206},[165,1789,1790],{"class":175}," message",[165,1792,401],{"class":182},[165,1794,755],{"class":171},[165,1796,1181],{"class":222},[165,1798,226],{"class":235},[165,1800,1186],{"class":175},[165,1802,773],{"class":182},[165,1804,1689],{"class":175},[165,1806,362],{"class":235},[165,1808,1809,1811,1813,1816,1818,1821,1823,1826,1828,1830,1832,1834],{"class":167,"line":493},[165,1810,1759],{"class":175},[165,1812,219],{"class":182},[165,1814,1815],{"class":222},"equal",[165,1817,226],{"class":235},[165,1819,1820],{"class":175},"message",[165,1822,219],{"class":182},[165,1824,1825],{"class":175},"subject",[165,1827,773],{"class":182},[165,1829,183],{"class":182},[165,1831,486],{"class":186},[165,1833,453],{"class":182},[165,1835,362],{"class":235},[165,1837,1838,1840,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860,1863,1865],{"class":167,"line":516},[165,1839,1759],{"class":175},[165,1841,219],{"class":182},[165,1843,1815],{"class":222},[165,1845,226],{"class":235},[165,1847,1820],{"class":175},[165,1849,219],{"class":182},[165,1851,179],{"class":175},[165,1853,219],{"class":182},[165,1855,976],{"class":175},[165,1857,773],{"class":182},[165,1859,183],{"class":182},[165,1861,1862],{"class":186},"app@test.example.com",[165,1864,453],{"class":182},[165,1866,362],{"class":235},[165,1868,1869,1871,1873,1875,1877,1879,1881,1883,1885,1888,1890,1892,1895,1897],{"class":167,"line":543},[165,1870,1759],{"class":175},[165,1872,219],{"class":182},[165,1874,1815],{"class":222},[165,1876,226],{"class":235},[165,1878,1820],{"class":175},[165,1880,219],{"class":182},[165,1882,179],{"class":175},[165,1884,219],{"class":182},[165,1886,1887],{"class":175},"name",[165,1889,773],{"class":182},[165,1891,183],{"class":182},[165,1893,1894],{"class":186},"Acme",[165,1896,453],{"class":182},[165,1898,362],{"class":235},[165,1900,1901],{"class":167,"line":551},[165,1902,197],{"emptyLinePlaceholder":196},[165,1904,1905,1907,1909,1911,1913,1915,1918,1920,1923,1925,1927,1930,1933,1936,1939,1942,1945],{"class":167,"line":1172},[165,1906,395],{"class":206},[165,1908,398],{"class":175},[165,1910,401],{"class":182},[165,1912,1790],{"class":175},[165,1914,219],{"class":182},[165,1916,1917],{"class":175},"text",[165,1919,219],{"class":182},[165,1921,1922],{"class":222},"match",[165,1924,226],{"class":235},[165,1926,1370],{"class":182},[165,1928,1929],{"class":186},"https:",[165,1931,1932],{"class":175},"\\\u002F\\\u002F",[165,1934,1935],{"class":186},"\\S",[165,1937,1938],{"class":182},"+\u002F",[165,1940,1941],{"class":235},")[",[165,1943,1944],{"class":580},"0",[165,1946,1947],{"class":235},"]\n",[165,1949,1950,1952,1954,1956,1958,1960,1962,1964,1967,1969],{"class":167,"line":1208},[165,1951,1759],{"class":175},[165,1953,219],{"class":182},[165,1955,1815],{"class":222},[165,1957,226],{"class":235},[165,1959,508],{"class":175},[165,1961,773],{"class":182},[165,1963,183],{"class":182},[165,1965,1966],{"class":186},"https:\u002F\u002Fexample.com\u002Fconfirm\u002Fabc123",[165,1968,453],{"class":182},[165,1970,362],{"class":235},[165,1972,1973],{"class":167,"line":1235},[165,1974,197],{"emptyLinePlaceholder":196},[165,1976,1977,1979,1982,1984,1987,1989,1992,1994,1996,1998,2001,2003,2006,2008,2011],{"class":167,"line":1262},[165,1978,395],{"class":206},[165,1980,1981],{"class":175}," $",[165,1983,401],{"class":182},[165,1985,1986],{"class":175}," cheerio",[165,1988,219],{"class":182},[165,1990,1991],{"class":222},"load",[165,1993,226],{"class":235},[165,1995,1820],{"class":175},[165,1997,219],{"class":182},[165,1999,2000],{"class":175},"html",[165,2002,219],{"class":182},[165,2004,2005],{"class":222},"join",[165,2007,226],{"class":235},[165,2009,2010],{"class":182},"''",[165,2012,1338],{"class":235},[165,2014,2015,2017,2019,2021,2023,2026,2028,2030,2033,2035,2037,2039,2042,2044,2046,2049,2051,2053,2055,2057],{"class":167,"line":1286},[165,2016,1759],{"class":175},[165,2018,219],{"class":182},[165,2020,1815],{"class":222},[165,2022,226],{"class":235},[165,2024,2025],{"class":222},"$",[165,2027,226],{"class":235},[165,2029,453],{"class":182},[165,2031,2032],{"class":186},"a:contains(\"Confirm your address\")",[165,2034,453],{"class":182},[165,2036,387],{"class":235},[165,2038,219],{"class":182},[165,2040,2041],{"class":222},"attr",[165,2043,226],{"class":235},[165,2045,453],{"class":182},[165,2047,2048],{"class":186},"href",[165,2050,453],{"class":182},[165,2052,387],{"class":235},[165,2054,773],{"class":182},[165,2056,398],{"class":175},[165,2058,362],{"class":235},[165,2060,2061,2063],{"class":167,"line":1341},[165,2062,359],{"class":182},[165,2064,362],{"class":175},[128,2066,2067,2068,2071],{},"Run it with ",[132,2069,2070],{},"node --test",", on Node 20 or newer, which Nodemailer 10 requires. Three things in it are worth copying:",[2073,2074,2075,2089,2110],"ul",{},[2076,2077,2078,2084,2085,2088],"li",{},[2079,2080,2081],"strong",{},[132,2082,2083],{},"transport.verify()"," connects and logs in without sending. A wrong password fails there with ",[132,2086,2087],{},"EAUTH",", not thirty seconds later as a missing email.",[2076,2090,2091,2094,2095,2098,2099,2103,2104,2106,2107,2109],{},[2079,2092,2093],{},"One recipient per test."," ",[132,2096,2097],{},"user+\u003Cuuid>@test.example.com"," is a ",[630,2100,2102],{"href":2101},"\u002Fdocs\u002Ffeatures\u002Faccount\u002F#plus-sign-aliasing","plus alias",": it delivers to ",[132,2105,1644],{}," and stays in the message's ",[132,2108,1312],{},". Tests running in parallel each find their own message. Plus aliases work on every plan.",[2076,2111,2112,2094,2115,2117,2118,2120,2121,2123,2124,2127,2128,2131],{},[2079,2113,2114],{},"Both parts.",[132,2116,1917],{}," is the plain text part, and ",[132,2119,2000],{}," is an array of HTML parts. cheerio parses the HTML and returns ",[132,2122,2048],{}," decoded, so ",[132,2125,2126],{},"&amp;"," in the markup compares equal to ",[132,2129,2130],{},"&"," in the text link.",[148,2133,2135],{"id":2134},"attachments","Attachments",[156,2137,2139],{"className":158,"code":2138,"language":160,"meta":161,"style":161},"test('invoice email carries the PDF', async () => {\n  const to = `user+${crypto.randomUUID()}@test.example.com`\n  const pdf = await readFile('fixtures\u002Finvoice.pdf')\n\n  await transport.sendMail({\n    from: 'app@test.example.com',\n    to,\n    subject: 'Your invoice',\n    text: 'Attached.',\n    attachments: [\n      { filename: 'invoice.pdf', content: pdf },\n    ],\n  })\n\n  const message = await waitForMessage(inbox, to)\n  const [file] = message.attachments\n  assert.equal(file.filename, 'invoice.pdf')\n  assert.equal(file.contentType, 'application\u002Fpdf')\n  assert.deepEqual(await download(file.downloadUrl), pdf)\n})\n",[132,2140,2141,2164,2192,2217,2221,2235,2249,2255,2270,2285,2295,2324,2331,2337,2341,2363,2385,2412,2440,2473],{"__ignoreMap":161},[165,2142,2143,2145,2147,2149,2152,2154,2156,2158,2160,2162],{"class":167,"line":168},[165,2144,1663],{"class":222},[165,2146,226],{"class":175},[165,2148,453],{"class":182},[165,2150,2151],{"class":186},"invoice email carries the PDF",[165,2153,453],{"class":182},[165,2155,773],{"class":182},[165,2157,966],{"class":206},[165,2159,1607],{"class":182},[165,2161,878],{"class":206},[165,2163,305],{"class":182},[165,2165,2166,2168,2170,2172,2174,2176,2178,2180,2182,2184,2186,2188,2190],{"class":167,"line":193},[165,2167,395],{"class":206},[165,2169,1191],{"class":175},[165,2171,401],{"class":182},[165,2173,404],{"class":182},[165,2175,1696],{"class":186},[165,2177,410],{"class":182},[165,2179,1701],{"class":175},[165,2181,219],{"class":182},[165,2183,1706],{"class":222},[165,2185,1709],{"class":175},[165,2187,359],{"class":182},[165,2189,1714],{"class":186},[165,2191,1161],{"class":182},[165,2193,2194,2196,2199,2201,2203,2206,2208,2210,2213,2215],{"class":167,"line":200},[165,2195,395],{"class":206},[165,2197,2198],{"class":175}," pdf",[165,2200,401],{"class":182},[165,2202,755],{"class":171},[165,2204,2205],{"class":222}," readFile",[165,2207,226],{"class":235},[165,2209,453],{"class":182},[165,2211,2212],{"class":186},"fixtures\u002Finvoice.pdf",[165,2214,453],{"class":182},[165,2216,362],{"class":235},[165,2218,2219],{"class":167,"line":232},[165,2220,197],{"emptyLinePlaceholder":196},[165,2222,2223,2225,2227,2229,2231,2233],{"class":167,"line":258},[165,2224,1616],{"class":171},[165,2226,429],{"class":175},[165,2228,219],{"class":182},[165,2230,134],{"class":222},[165,2232,226],{"class":235},[165,2234,229],{"class":182},[165,2236,2237,2239,2241,2243,2245,2247],{"class":167,"line":283},[165,2238,443],{"class":235},[165,2240,239],{"class":182},[165,2242,183],{"class":182},[165,2244,1862],{"class":186},[165,2246,453],{"class":182},[165,2248,255],{"class":182},[165,2250,2251,2253],{"class":167,"line":297},[165,2252,461],{"class":175},[165,2254,255],{"class":182},[165,2256,2257,2259,2261,2263,2266,2268],{"class":167,"line":308},[165,2258,479],{"class":235},[165,2260,239],{"class":182},[165,2262,183],{"class":182},[165,2264,2265],{"class":186},"Your invoice",[165,2267,453],{"class":182},[165,2269,255],{"class":182},[165,2271,2272,2274,2276,2278,2281,2283],{"class":167,"line":329},[165,2273,496],{"class":235},[165,2275,239],{"class":182},[165,2277,183],{"class":182},[165,2279,2280],{"class":186},"Attached.",[165,2282,453],{"class":182},[165,2284,255],{"class":182},[165,2286,2287,2290,2292],{"class":167,"line":350},[165,2288,2289],{"class":235},"    attachments",[165,2291,239],{"class":182},[165,2293,2294],{"class":235}," [\n",[165,2296,2297,2300,2303,2305,2307,2310,2312,2314,2317,2319,2321],{"class":167,"line":356},[165,2298,2299],{"class":182},"      {",[165,2301,2302],{"class":235}," filename",[165,2304,239],{"class":182},[165,2306,183],{"class":182},[165,2308,2309],{"class":186},"invoice.pdf",[165,2311,453],{"class":182},[165,2313,773],{"class":182},[165,2315,2316],{"class":235}," content",[165,2318,239],{"class":182},[165,2320,2198],{"class":175},[165,2322,2323],{"class":182}," },\n",[165,2325,2326,2329],{"class":167,"line":365},[165,2327,2328],{"class":235},"    ]",[165,2330,255],{"class":182},[165,2332,2333,2335],{"class":167,"line":370},[165,2334,546],{"class":182},[165,2336,362],{"class":235},[165,2338,2339],{"class":167,"line":392},[165,2340,197],{"emptyLinePlaceholder":196},[165,2342,2343,2345,2347,2349,2351,2353,2355,2357,2359,2361],{"class":167,"line":423},[165,2344,395],{"class":206},[165,2346,1790],{"class":175},[165,2348,401],{"class":182},[165,2350,755],{"class":171},[165,2352,1181],{"class":222},[165,2354,226],{"class":235},[165,2356,1186],{"class":175},[165,2358,773],{"class":182},[165,2360,1191],{"class":175},[165,2362,362],{"class":235},[165,2364,2365,2367,2370,2373,2376,2378,2380,2382],{"class":167,"line":440},[165,2366,395],{"class":206},[165,2368,2369],{"class":182}," [",[165,2371,2372],{"class":175},"file",[165,2374,2375],{"class":182},"]",[165,2377,401],{"class":182},[165,2379,1790],{"class":175},[165,2381,219],{"class":182},[165,2383,2384],{"class":175},"attachments\n",[165,2386,2387,2389,2391,2393,2395,2397,2399,2402,2404,2406,2408,2410],{"class":167,"line":458},[165,2388,1759],{"class":175},[165,2390,219],{"class":182},[165,2392,1815],{"class":222},[165,2394,226],{"class":235},[165,2396,2372],{"class":175},[165,2398,219],{"class":182},[165,2400,2401],{"class":175},"filename",[165,2403,773],{"class":182},[165,2405,183],{"class":182},[165,2407,2309],{"class":186},[165,2409,453],{"class":182},[165,2411,362],{"class":235},[165,2413,2414,2416,2418,2420,2422,2424,2426,2429,2431,2433,2436,2438],{"class":167,"line":476},[165,2415,1759],{"class":175},[165,2417,219],{"class":182},[165,2419,1815],{"class":222},[165,2421,226],{"class":235},[165,2423,2372],{"class":175},[165,2425,219],{"class":182},[165,2427,2428],{"class":175},"contentType",[165,2430,773],{"class":182},[165,2432,183],{"class":182},[165,2434,2435],{"class":186},"application\u002Fpdf",[165,2437,453],{"class":182},[165,2439,362],{"class":235},[165,2441,2442,2444,2446,2448,2450,2453,2456,2458,2460,2462,2465,2467,2469,2471],{"class":167,"line":493},[165,2443,1759],{"class":175},[165,2445,219],{"class":182},[165,2447,1764],{"class":222},[165,2449,226],{"class":235},[165,2451,2452],{"class":171},"await",[165,2454,2455],{"class":222}," download",[165,2457,226],{"class":235},[165,2459,2372],{"class":175},[165,2461,219],{"class":182},[165,2463,2464],{"class":175},"downloadUrl",[165,2466,387],{"class":235},[165,2468,773],{"class":182},[165,2470,2198],{"class":175},[165,2472,362],{"class":235},[165,2474,2475,2477],{"class":167,"line":516},[165,2476,359],{"class":182},[165,2478,362],{"class":175},[128,2480,2481,2484,2485,2488,2489,2492,2493,2497],{},[132,2482,2483],{},"readFile"," comes from ",[132,2486,2487],{},"node:fs\u002Fpromises",", and ",[132,2490,2491],{},"download"," from the helper. Comparing the downloaded bytes with the fixture proves the file survived base64 encoding, which a check on the filename alone doesn't. Attachments count toward the ",[630,2494,2496],{"href":2495},"\u002Fdocs\u002Fguides\u002Femail-size-limits\u002F","20 MB message limit"," after encoding.",[148,2499,2501],{"id":2500},"headers","Headers",[128,2503,2504,2505,2507,2508,2507,2510,2507,2513,2507,2516,2488,2518,2521,2522,2526],{},"The message JSON has ",[132,2506,179],{},", ",[132,2509,1312],{},[132,2511,2512],{},"cc",[132,2514,2515],{},"replyTo",[132,2517,1825],{},[132,2519,2520],{},"date"," as fields. For any other header, fetch the ",[630,2523,2525],{"href":2524},"\u002Fdocs\u002Fapi\u002F#get-a-message-source","raw source"," and parse it:",[156,2528,2530],{"className":158,"code":2529,"language":160,"meta":161,"style":161},"import { simpleParser } from 'mailparser'\n\nconst { raw } = await api(`${inbox}\u002F${message.id}\u002Fsource`)\nconst { headers } = await simpleParser(raw)\n\nassert.equal(headers.get('x-order-id'), 'order-42')\nassert.equal(headers.get('list').unsubscribe.url, 'https:\u002F\u002Fexample.com\u002Funsubscribe?u=1')\n",[132,2531,2532,2552,2556,2600,2619,2623,2662],{"__ignoreMap":161},[165,2533,2534,2536,2538,2541,2543,2545,2547,2550],{"class":167,"line":168},[165,2535,172],{"class":171},[165,2537,650],{"class":182},[165,2539,2540],{"class":175}," simpleParser",[165,2542,656],{"class":182},[165,2544,659],{"class":171},[165,2546,183],{"class":182},[165,2548,2549],{"class":186},"mailparser",[165,2551,190],{"class":182},[165,2553,2554],{"class":167,"line":193},[165,2555,197],{"emptyLinePlaceholder":196},[165,2557,2558,2560,2562,2565,2567,2569,2571,2573,2575,2577,2579,2581,2583,2585,2587,2589,2591,2593,2596,2598],{"class":167,"line":200},[165,2559,675],{"class":206},[165,2561,650],{"class":182},[165,2563,2564],{"class":175}," raw ",[165,2566,359],{"class":182},[165,2568,401],{"class":182},[165,2570,755],{"class":171},[165,2572,998],{"class":222},[165,2574,226],{"class":175},[165,2576,826],{"class":182},[165,2578,1186],{"class":175},[165,2580,359],{"class":182},[165,2582,1370],{"class":186},[165,2584,410],{"class":182},[165,2586,1820],{"class":175},[165,2588,219],{"class":182},[165,2590,1088],{"class":175},[165,2592,359],{"class":182},[165,2594,2595],{"class":186},"\u002Fsource",[165,2597,538],{"class":182},[165,2599,362],{"class":175},[165,2601,2602,2604,2606,2608,2610,2612,2614,2616],{"class":167,"line":232},[165,2603,675],{"class":206},[165,2605,650],{"class":182},[165,2607,694],{"class":175},[165,2609,359],{"class":182},[165,2611,401],{"class":182},[165,2613,755],{"class":171},[165,2615,2540],{"class":222},[165,2617,2618],{"class":175},"(raw)\n",[165,2620,2621],{"class":167,"line":258},[165,2622,197],{"emptyLinePlaceholder":196},[165,2624,2625,2628,2630,2632,2635,2637,2640,2642,2644,2647,2649,2651,2653,2655,2658,2660],{"class":167,"line":283},[165,2626,2627],{"class":175},"assert",[165,2629,219],{"class":182},[165,2631,1815],{"class":222},[165,2633,2634],{"class":175},"(headers",[165,2636,219],{"class":182},[165,2638,2639],{"class":222},"get",[165,2641,226],{"class":175},[165,2643,453],{"class":182},[165,2645,2646],{"class":186},"x-order-id",[165,2648,453],{"class":182},[165,2650,387],{"class":175},[165,2652,773],{"class":182},[165,2654,183],{"class":182},[165,2656,2657],{"class":186},"order-42",[165,2659,453],{"class":182},[165,2661,362],{"class":175},[165,2663,2664,2666,2668,2670,2672,2674,2676,2678,2680,2683,2685,2687,2689,2692,2694,2697,2699,2701,2704,2706],{"class":167,"line":297},[165,2665,2627],{"class":175},[165,2667,219],{"class":182},[165,2669,1815],{"class":222},[165,2671,2634],{"class":175},[165,2673,219],{"class":182},[165,2675,2639],{"class":222},[165,2677,226],{"class":175},[165,2679,453],{"class":182},[165,2681,2682],{"class":186},"list",[165,2684,453],{"class":182},[165,2686,387],{"class":175},[165,2688,219],{"class":182},[165,2690,2691],{"class":175},"unsubscribe",[165,2693,219],{"class":182},[165,2695,2696],{"class":175},"url",[165,2698,773],{"class":182},[165,2700,183],{"class":182},[165,2702,2703],{"class":186},"https:\u002F\u002Fexample.com\u002Funsubscribe?u=1",[165,2705,453],{"class":182},[165,2707,362],{"class":175},[128,2709,2710,2711,2714,2715,2718,2719,2722,2723,2726],{},"That reads a message sent with ",[132,2712,2713],{},"headers: { 'X-Order-Id': 'order-42' }"," and ",[132,2716,2717],{},"list: { unsubscribe: 'https:\u002F\u002Fexample.com\u002Funsubscribe?u=1' }",". Use a parser instead of a regex over ",[132,2720,2721],{},"raw",": Nodemailer writes that header as ",[132,2724,2725],{},"X-Order-ID",", and long headers are folded across lines.",[148,2728,2730],{"id":2729},"refused-recipients","Refused Recipients",[128,2732,2733,2735],{},[132,2734,145],{}," refuses any recipient that isn't an account in your sandbox. What Nodemailer does next depends on the message:",[2073,2737,2738,2748],{},[2076,2739,2740,2094,2743,2745,2746,219],{},[2079,2741,2742],{},"Every recipient refused:",[132,2744,134],{}," rejects, and the error lists them in ",[132,2747,1774],{},[2076,2749,2750,2094,2753,2755,2756,219],{},[2079,2751,2752],{},"Some refused:",[132,2754,134],{}," resolves. The message goes to the rest, and the refused addresses are in ",[132,2757,2758],{},"info.rejected",[128,2760,2761,2762,2764],{},"The second case is silent unless you look, which is why the test above asserts that ",[132,2763,2758],{}," is empty. Seed test users with addresses on your sandbox domain, or plus aliases of one account.",[148,2766,2768],{"id":2767},"error-codes","Error Codes",[128,2770,2771,2772,2774,2775,2778],{},"Nodemailer sets ",[132,2773,132],{}," on the errors it throws, and ",[132,2776,2777],{},"responseCode"," when the server replied:",[2780,2781,2782,2796],"table",{},[2783,2784,2785],"thead",{},[2786,2787,2788,2793],"tr",{},[2789,2790,2791],"th",{},[132,2792,132],{},[2789,2794,2795],{},"Means",[2797,2798,2799,2809,2825,2835,2845,2855],"tbody",{},[2786,2800,2801,2806],{},[2802,2803,2804],"td",{},[132,2805,2087],{},[2802,2807,2808],{},"The server refused the login. A wrong password, or a username that isn't the full address",[2786,2810,2811,2816],{},[2802,2812,2813],{},[132,2814,2815],{},"ESOCKET",[2802,2817,2818,2819,2821,2822,2824],{},"The connection failed. With ",[132,2820,621],{},", it's ",[132,2823,617],{}," on a STARTTLS port",[2786,2826,2827,2832],{},[2802,2828,2829],{},[132,2830,2831],{},"ETLS",[2802,2833,2834],{},"STARTTLS was required and didn't complete",[2786,2836,2837,2842],{},[2802,2838,2839],{},[132,2840,2841],{},"ETIMEDOUT",[2802,2843,2844],{},"Nothing answered. The network probably blocks the port",[2786,2846,2847,2852],{},[2802,2848,2849],{},[132,2850,2851],{},"EENVELOPE",[2802,2853,2854],{},"The server refused the sender or every recipient",[2786,2856,2857,2862],{},[2802,2858,2859],{},[132,2860,2861],{},"EMESSAGE",[2802,2863,2864,2865,2867],{},"The server refused the message itself. ",[132,2866,2777],{}," 552 means it's over the size limit",[148,2869,2871],{"id":2870},"mocks-jsontransport-and-ethereal","Mocks, jsonTransport, and Ethereal",[2780,2873,2874,2895],{},[2783,2875,2876],{},[2786,2877,2878,2880,2885,2889,2892],{},[2789,2879],{},[2789,2881,2882,2883],{},"Mock ",[132,2884,134],{},[2789,2886,2887],{},[132,2888,138],{},[2789,2890,2891],{},"Ethereal",[2789,2893,2894],{},"Sandbox",[2797,2896,2897,2913,2930,2943,2958],{},[2786,2898,2899,2902,2905,2907,2910],{},[2802,2900,2901],{},"Runs your SMTP settings",[2802,2903,2904],{},"No",[2802,2906,2904],{},[2802,2908,2909],{},"With Ethereal's host",[2802,2911,2912],{},"Yes",[2786,2914,2915,2918,2921,2924,2927],{},[2802,2916,2917],{},"You assert on",[2802,2919,2920],{},"The arguments your code sent",[2802,2922,2923],{},"The message as JSON",[2802,2925,2926],{},"A preview page, or IMAP",[2802,2928,2929],{},"JSON over REST, IMAP, POP3",[2786,2931,2932,2935,2937,2939,2941],{},[2802,2933,2934],{},"Needs a network",[2802,2936,2904],{},[2802,2938,2904],{},[2802,2940,2912],{},[2802,2942,2912],{},[2786,2944,2945,2948,2950,2952,2955],{},[2802,2946,2947],{},"Mail from other senders",[2802,2949,2904],{},[2802,2951,2904],{},[2802,2953,2954],{},"Not on public accounts",[2802,2956,2957],{},"Yes, over MX",[2786,2959,2960,2963,2966,2968,2971],{},[2802,2961,2962],{},"Messages kept",[2802,2964,2965],{},"Not stored",[2802,2967,2965],{},[2802,2969,2970],{},"A few hours",[2802,2972,2973],{},"7, 30, or 90 days by plan",[128,2975,2976],{},"Each has a job:",[2073,2978,2979,2985,2998,3011],{},[2076,2980,2981,2984],{},[2079,2982,2983],{},"A mock"," fits unit tests of the code around the mailer: was it called, and for which user.",[2076,2986,2987,2991,2992,2994,2995,219],{},[2079,2988,2989],{},[132,2990,138],{}," fits template snapshots with no network. ",[132,2993,134],{}," resolves with the message as a JSON string in ",[132,2996,2997],{},"info.message",[2076,2999,3000,3002,3003,3006,3007,3010],{},[2079,3001,2891],{}," fits a quick look at one message. ",[132,3004,3005],{},"nodemailer.createTestAccount()"," returns SMTP credentials with no signup, and ",[132,3008,3009],{},"nodemailer.getTestMessageUrl(info)"," returns a preview link. Messages are deleted after a few hours, and the preview link opens without a login.",[2076,3012,3013,3016],{},[2079,3014,3015],{},"The sandbox"," fits when a test has to read the message back, when CI and the team need the same inbox, or when the email comes from somewhere other than your code.",[3018,3019,3020],"note",{},[128,3021,3022],{},"Ethereal's retention, preview links, and inbound rules are from its own FAQ, checked November 11, 2026.",[148,3024,3026],{"id":3025},"when-production-doesnt-use-smtp","When Production Doesn't Use SMTP",[128,3028,3029,3030,3034,3035,219],{},"If production sends through a provider's HTTP API or Nodemailer's SES transport, a second transport for tests would cover code that never runs in production. Keep the production transport and send to an address on a ",[630,3031,3033],{"href":3032},"\u002Fdocs\u002Fsetup\u002Fdomain\u002F","domain whose MX points at the sandbox",". The provider delivers it like any other email, and the same assertions apply. The message also arrives with ",[630,3036,3038],{"href":3037},"\u002Fdocs\u002Fguides\u002Fspf-dkim-dmarc\u002F","SPF, DKIM, and DMARC results",[148,3040,3042],{"id":3041},"next","Next",[2073,3044,3045,3053,3062],{},[2076,3046,3047,3048,3052],{},"The ",[630,3049,3051],{"href":3050},"\u002Fdocs\u002Fguides\u002Fci\u002F","CI guide"," runs these tests on every push, with the two secrets they need.",[2076,3054,3055,2714,3058,3061],{},[630,3056,69],{"href":3057},"\u002Fdocs\u002Fguides\u002Fe2e\u002F",[630,3059,73],{"href":3060},"\u002Fdocs\u002Fguides\u002Fcypress\u002F"," follow the link from the email in a browser test.",[2076,3063,3064,3068],{},[630,3065,3067],{"href":3066},"\u002Fdocs\u002Fguides\u002Fotp-magic-links\u002F","OTP codes and magic links"," covers resend, reuse, and expiry.",[3070,3071,3072],"style",{},"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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}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 .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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":161,"searchDepth":193,"depth":193,"links":3074},[3075,3076,3077,3078,3079,3080,3081,3082,3083],{"id":150,"depth":193,"text":151},{"id":624,"depth":193,"text":625},{"id":2134,"depth":193,"text":2135},{"id":2500,"depth":193,"text":2501},{"id":2729,"depth":193,"text":2730},{"id":2767,"depth":193,"text":2768},{"id":2870,"depth":193,"text":2871},{"id":3025,"depth":193,"text":3026},{"id":3041,"depth":193,"text":3042},"Point Nodemailer at a sandbox SMTP server, send from a test, and assert on links, headers, and attachments over REST. Compared with mocks and Ethereal.","md",{},{"title":101},{"title":123,"description":3084},"4glab8hD93Zy3VWpd5O7YohleJ3s1Cec8e3F2G2unS0",[3091,3093],{"title":97,"path":98,"stem":99,"description":3092,"children":-1},"IMAP works on the server's copy of a mailbox, POP3 downloads it. Ports, the commands to type into openssl, and curl, Python, and Node scripts for both.",{"title":110,"path":111,"stem":112,"description":3094,"children":-1},"Mailtrap's Email Sandbox and smtp.dev side by side on quotas, retention, and inbound addresses. When to stay on Mailtrap, and how to move a test suite.",1794400213481]