[{"data":1,"prerenderedAt":2642},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-a-weekly-digest-of-several-reports-in-one-email":3,"surround:\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-a-weekly-digest-of-several-reports-in-one-email":2611,"ancestors:\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-a-weekly-digest-of-several-reports-in-one-email":2620,"children:\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-a-weekly-digest-of-several-reports-in-one-email":2625,"related:\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-a-weekly-digest-of-several-reports-in-one-email":2626},{"id":4,"title":5,"body":6,"dateModified":2578,"datePublished":2578,"description":2579,"extension":2580,"faq":2581,"keyword":2590,"kind":2591,"meta":2592,"navigation":111,"path":2600,"seo":2601,"slug":2604,"stem":2605,"topics":2606,"type":2609,"__hash__":2610},"docs\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-a-weekly-digest-of-several-reports-in-one-email\u002Findex.md","Send a Weekly Digest of Several Reports in One Email",{"type":7,"value":8,"toc":2567},"minimark",[9,19,24,53,65,69,885,896,1040,1044,1301,1308,1380,1384,2200,2219,2433,2437,2448,2452,2489,2493,2499,2503,2512,2518,2524,2530,2534,2563],[10,11,12,13,18],"p",{},"A team that automates its reporting soon has a problem of its own making: every Monday morning brings five or six separate emails — sales, stock, debtors, open orders, web traffic — each with an attachment, each sent by a different job at a slightly different time. People stop opening them. A weekly digest fixes that by sending one email that summarises every report in a few lines, with the headline figure from each, and attaches or links the full workbooks for anyone who wants the detail. It also makes failures visible: when one of the reports was not produced, the digest says so, rather than relying on someone noticing an email that never arrived. This page builds a digest from a small manifest of the week's reports: it reads a headline figure from each workbook, assembles one HTML email with a plain-text version, keeps attachments under a size limit, flags missing reports and writes a preview before anything is sent. It belongs to ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002F","Emailing Excel Reports with smtplib",".",[20,21,23],"h2",{"id":22},"prerequisites","Prerequisites",[25,26,31],"pre",{"className":27,"code":28,"language":29,"meta":30,"style":30},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[32,33,34],"code",{"__ignoreMap":30},[35,36,39,43,47,50],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"sMTad","pip",[35,44,46],{"class":45},"srMev"," install",[35,48,49],{"class":45}," pandas",[35,51,52],{"class":45}," openpyxl\n",[10,54,55,56,60,61,64],{},"Four report jobs have written their workbooks for week 41 of 2026 into one folder; a fifth, open orders, failed and wrote nothing. Each workbook has a ",[57,58,59],"em",{},"Summary"," sheet from which the digest takes its headline. The message is built with the standard library's ",[32,62,63],{},"email"," package; sending it is the same as for any report email.",[20,66,68],{"id":67},"step-1-a-manifest-that-says-what-to-expect","Step 1 — A manifest that says what to expect",[25,70,74],{"className":71,"code":72,"language":73,"meta":30,"style":30},"language-python shiki shiki-themes github-light github-dark-high-contrast","from pathlib import Path\nimport pandas as pd\n\nWEEK = \"2026-W41\"\nfolder = Path(\"reports\") \u002F WEEK\nMANIFEST = [\n    {\"title\": \"Weekly sales\", \"file\": \"weekly_sales.xlsx\", \"owner\": \"Sales ops\",\n     \"headline\": lambda df: f\"Revenue £{df['Revenue'].sum():,.0f} across {len(df)} regions\"},\n    {\"title\": \"Stock below reorder level\", \"file\": \"stock_levels.xlsx\", \"owner\": \"Warehouse\",\n     \"headline\": lambda df: f\"{int(df['Below reorder'].sum())} SKUs below reorder level\"},\n    {\"title\": \"Aged debtors\", \"file\": \"aged_debtors.xlsx\", \"owner\": \"Credit control\",\n     \"headline\": lambda df: f\"£{df['Amount'].sum():,.0f} owed, £{df.loc[df['Bucket'] == '90+', 'Amount'].sum():,.0f} over 90 days\"},\n    {\"title\": \"Open orders\", \"file\": \"open_orders.xlsx\", \"owner\": \"Customer service\",\n     \"headline\": lambda df: f\"{len(df)} open orders\"},\n    {\"title\": \"Web traffic\", \"file\": \"web_traffic.xlsx\", \"owner\": \"Marketing\",\n     \"headline\": lambda df: f\"{int(df.loc[0, 'Value']):,} sessions\"},\n]\n\nitems = []\nfor m in MANIFEST:\n    path = folder \u002F m[\"file\"]\n    item = {**m, \"path\": path, \"exists\": path.exists()}\n    if item[\"exists\"]:\n        try:\n            item[\"text\"] = m[\"headline\"](pd.read_excel(path, sheet_name=\"Summary\"))\n            item[\"size\"] = path.stat().st_size\n        except Exception as e:\n            item[\"exists\"], item[\"text\"] = False, f\"could not be read ({type(e).__name__})\"\n    else:\n        item[\"text\"] = \"not produced this week\"\n    items.append(item)\nfor i in items:\n    print(f\"{i['title']:28} {'OK ' if i['exists'] else 'MISSING'} {i['text']}\")\n","python",[32,75,76,92,106,113,126,150,161,200,258,290,327,359,423,455,483,515,558,564,569,580,598,618,647,661,669,702,717,732,774,782,797,803,816],{"__ignoreMap":30},[35,77,78,82,86,89],{"class":37,"line":38},[35,79,81],{"class":80},"s-kum","from",[35,83,85],{"class":84},"skGVy"," pathlib ",[35,87,88],{"class":80},"import",[35,90,91],{"class":84}," Path\n",[35,93,95,97,100,103],{"class":37,"line":94},2,[35,96,88],{"class":80},[35,98,99],{"class":84}," pandas ",[35,101,102],{"class":80},"as",[35,104,105],{"class":84}," pd\n",[35,107,109],{"class":37,"line":108},3,[35,110,112],{"emptyLinePlaceholder":111},true,"\n",[35,114,116,120,123],{"class":37,"line":115},4,[35,117,119],{"class":118},"sP0c6","WEEK",[35,121,122],{"class":80}," =",[35,124,125],{"class":45}," \"2026-W41\"\n",[35,127,129,132,135,138,141,144,147],{"class":37,"line":128},5,[35,130,131],{"class":84},"folder ",[35,133,134],{"class":80},"=",[35,136,137],{"class":84}," Path(",[35,139,140],{"class":45},"\"reports\"",[35,142,143],{"class":84},") ",[35,145,146],{"class":80},"\u002F",[35,148,149],{"class":118}," WEEK\n",[35,151,153,156,158],{"class":37,"line":152},6,[35,154,155],{"class":118},"MANIFEST",[35,157,122],{"class":80},[35,159,160],{"class":84}," [\n",[35,162,164,167,170,173,176,179,182,184,187,189,192,194,197],{"class":37,"line":163},7,[35,165,166],{"class":84},"    {",[35,168,169],{"class":45},"\"title\"",[35,171,172],{"class":84},": ",[35,174,175],{"class":45},"\"Weekly sales\"",[35,177,178],{"class":84},", ",[35,180,181],{"class":45},"\"file\"",[35,183,172],{"class":84},[35,185,186],{"class":45},"\"weekly_sales.xlsx\"",[35,188,178],{"class":84},[35,190,191],{"class":45},"\"owner\"",[35,193,172],{"class":84},[35,195,196],{"class":45},"\"Sales ops\"",[35,198,199],{"class":84},",\n",[35,201,203,206,208,211,214,217,220,224,227,230,233,236,239,242,244,247,250,252,255],{"class":37,"line":202},8,[35,204,205],{"class":45},"     \"headline\"",[35,207,172],{"class":84},[35,209,210],{"class":80},"lambda",[35,212,213],{"class":84}," df: ",[35,215,216],{"class":80},"f",[35,218,219],{"class":45},"\"Revenue £",[35,221,223],{"class":222},"sSjpA","{",[35,225,226],{"class":84},"df[",[35,228,229],{"class":45},"'Revenue'",[35,231,232],{"class":84},"].sum()",[35,234,235],{"class":80},":,.0f",[35,237,238],{"class":222},"}",[35,240,241],{"class":45}," across ",[35,243,223],{"class":222},[35,245,246],{"class":118},"len",[35,248,249],{"class":84},"(df)",[35,251,238],{"class":222},[35,253,254],{"class":45}," regions\"",[35,256,257],{"class":84},"},\n",[35,259,261,263,265,267,270,272,274,276,279,281,283,285,288],{"class":37,"line":260},9,[35,262,166],{"class":84},[35,264,169],{"class":45},[35,266,172],{"class":84},[35,268,269],{"class":45},"\"Stock below reorder level\"",[35,271,178],{"class":84},[35,273,181],{"class":45},[35,275,172],{"class":84},[35,277,278],{"class":45},"\"stock_levels.xlsx\"",[35,280,178],{"class":84},[35,282,191],{"class":45},[35,284,172],{"class":84},[35,286,287],{"class":45},"\"Warehouse\"",[35,289,199],{"class":84},[35,291,293,295,297,299,301,303,306,308,311,314,317,320,322,325],{"class":37,"line":292},10,[35,294,205],{"class":45},[35,296,172],{"class":84},[35,298,210],{"class":80},[35,300,213],{"class":84},[35,302,216],{"class":80},[35,304,305],{"class":45},"\"",[35,307,223],{"class":222},[35,309,310],{"class":118},"int",[35,312,313],{"class":84},"(df[",[35,315,316],{"class":45},"'Below reorder'",[35,318,319],{"class":84},"].sum())",[35,321,238],{"class":222},[35,323,324],{"class":45}," SKUs below reorder level\"",[35,326,257],{"class":84},[35,328,330,332,334,336,339,341,343,345,348,350,352,354,357],{"class":37,"line":329},11,[35,331,166],{"class":84},[35,333,169],{"class":45},[35,335,172],{"class":84},[35,337,338],{"class":45},"\"Aged debtors\"",[35,340,178],{"class":84},[35,342,181],{"class":45},[35,344,172],{"class":84},[35,346,347],{"class":45},"\"aged_debtors.xlsx\"",[35,349,178],{"class":84},[35,351,191],{"class":45},[35,353,172],{"class":84},[35,355,356],{"class":45},"\"Credit control\"",[35,358,199],{"class":84},[35,360,362,364,366,368,370,372,375,377,379,382,384,386,388,391,393,396,399,402,405,408,410,412,414,416,418,421],{"class":37,"line":361},12,[35,363,205],{"class":45},[35,365,172],{"class":84},[35,367,210],{"class":80},[35,369,213],{"class":84},[35,371,216],{"class":80},[35,373,374],{"class":45},"\"£",[35,376,223],{"class":222},[35,378,226],{"class":84},[35,380,381],{"class":45},"'Amount'",[35,383,232],{"class":84},[35,385,235],{"class":80},[35,387,238],{"class":222},[35,389,390],{"class":45}," owed, £",[35,392,223],{"class":222},[35,394,395],{"class":84},"df.loc[df[",[35,397,398],{"class":45},"'Bucket'",[35,400,401],{"class":84},"] ",[35,403,404],{"class":80},"==",[35,406,407],{"class":45}," '90+'",[35,409,178],{"class":84},[35,411,381],{"class":45},[35,413,232],{"class":84},[35,415,235],{"class":80},[35,417,238],{"class":222},[35,419,420],{"class":45}," over 90 days\"",[35,422,257],{"class":84},[35,424,426,428,430,432,435,437,439,441,444,446,448,450,453],{"class":37,"line":425},13,[35,427,166],{"class":84},[35,429,169],{"class":45},[35,431,172],{"class":84},[35,433,434],{"class":45},"\"Open orders\"",[35,436,178],{"class":84},[35,438,181],{"class":45},[35,440,172],{"class":84},[35,442,443],{"class":45},"\"open_orders.xlsx\"",[35,445,178],{"class":84},[35,447,191],{"class":45},[35,449,172],{"class":84},[35,451,452],{"class":45},"\"Customer service\"",[35,454,199],{"class":84},[35,456,458,460,462,464,466,468,470,472,474,476,478,481],{"class":37,"line":457},14,[35,459,205],{"class":45},[35,461,172],{"class":84},[35,463,210],{"class":80},[35,465,213],{"class":84},[35,467,216],{"class":80},[35,469,305],{"class":45},[35,471,223],{"class":222},[35,473,246],{"class":118},[35,475,249],{"class":84},[35,477,238],{"class":222},[35,479,480],{"class":45}," open orders\"",[35,482,257],{"class":84},[35,484,486,488,490,492,495,497,499,501,504,506,508,510,513],{"class":37,"line":485},15,[35,487,166],{"class":84},[35,489,169],{"class":45},[35,491,172],{"class":84},[35,493,494],{"class":45},"\"Web traffic\"",[35,496,178],{"class":84},[35,498,181],{"class":45},[35,500,172],{"class":84},[35,502,503],{"class":45},"\"web_traffic.xlsx\"",[35,505,178],{"class":84},[35,507,191],{"class":45},[35,509,172],{"class":84},[35,511,512],{"class":45},"\"Marketing\"",[35,514,199],{"class":84},[35,516,518,520,522,524,526,528,530,532,534,537,540,542,545,548,551,553,556],{"class":37,"line":517},16,[35,519,205],{"class":45},[35,521,172],{"class":84},[35,523,210],{"class":80},[35,525,213],{"class":84},[35,527,216],{"class":80},[35,529,305],{"class":45},[35,531,223],{"class":222},[35,533,310],{"class":118},[35,535,536],{"class":84},"(df.loc[",[35,538,539],{"class":118},"0",[35,541,178],{"class":84},[35,543,544],{"class":45},"'Value'",[35,546,547],{"class":84},"])",[35,549,550],{"class":80},":,",[35,552,238],{"class":222},[35,554,555],{"class":45}," sessions\"",[35,557,257],{"class":84},[35,559,561],{"class":37,"line":560},17,[35,562,563],{"class":84},"]\n",[35,565,567],{"class":37,"line":566},18,[35,568,112],{"emptyLinePlaceholder":111},[35,570,572,575,577],{"class":37,"line":571},19,[35,573,574],{"class":84},"items ",[35,576,134],{"class":80},[35,578,579],{"class":84}," []\n",[35,581,583,586,589,592,595],{"class":37,"line":582},20,[35,584,585],{"class":80},"for",[35,587,588],{"class":84}," m ",[35,590,591],{"class":80},"in",[35,593,594],{"class":118}," MANIFEST",[35,596,597],{"class":84},":\n",[35,599,601,604,606,609,611,614,616],{"class":37,"line":600},21,[35,602,603],{"class":84},"    path ",[35,605,134],{"class":80},[35,607,608],{"class":84}," folder ",[35,610,146],{"class":80},[35,612,613],{"class":84}," m[",[35,615,181],{"class":45},[35,617,563],{"class":84},[35,619,621,624,626,629,632,635,638,641,644],{"class":37,"line":620},22,[35,622,623],{"class":84},"    item ",[35,625,134],{"class":80},[35,627,628],{"class":84}," {",[35,630,631],{"class":80},"**",[35,633,634],{"class":84},"m, ",[35,636,637],{"class":45},"\"path\"",[35,639,640],{"class":84},": path, ",[35,642,643],{"class":45},"\"exists\"",[35,645,646],{"class":84},": path.exists()}\n",[35,648,650,653,656,658],{"class":37,"line":649},23,[35,651,652],{"class":80},"    if",[35,654,655],{"class":84}," item[",[35,657,643],{"class":45},[35,659,660],{"class":84},"]:\n",[35,662,664,667],{"class":37,"line":663},24,[35,665,666],{"class":80},"        try",[35,668,597],{"class":84},[35,670,672,675,678,680,682,684,687,690,694,696,699],{"class":37,"line":671},25,[35,673,674],{"class":84},"            item[",[35,676,677],{"class":45},"\"text\"",[35,679,401],{"class":84},[35,681,134],{"class":80},[35,683,613],{"class":84},[35,685,686],{"class":45},"\"headline\"",[35,688,689],{"class":84},"](pd.read_excel(path, ",[35,691,693],{"class":692},"sa561","sheet_name",[35,695,134],{"class":80},[35,697,698],{"class":45},"\"Summary\"",[35,700,701],{"class":84},"))\n",[35,703,705,707,710,712,714],{"class":37,"line":704},26,[35,706,674],{"class":84},[35,708,709],{"class":45},"\"size\"",[35,711,401],{"class":84},[35,713,134],{"class":80},[35,715,716],{"class":84}," path.stat().st_size\n",[35,718,720,723,726,729],{"class":37,"line":719},27,[35,721,722],{"class":80},"        except",[35,724,725],{"class":118}," Exception",[35,727,728],{"class":80}," as",[35,730,731],{"class":84}," e:\n",[35,733,735,737,739,742,744,746,748,751,753,755,758,760,763,766,769,771],{"class":37,"line":734},28,[35,736,674],{"class":84},[35,738,643],{"class":45},[35,740,741],{"class":84},"], item[",[35,743,677],{"class":45},[35,745,401],{"class":84},[35,747,134],{"class":80},[35,749,750],{"class":118}," False",[35,752,178],{"class":84},[35,754,216],{"class":80},[35,756,757],{"class":45},"\"could not be read (",[35,759,223],{"class":222},[35,761,762],{"class":118},"type",[35,764,765],{"class":84},"(e).",[35,767,768],{"class":118},"__name__",[35,770,238],{"class":222},[35,772,773],{"class":45},")\"\n",[35,775,777,780],{"class":37,"line":776},29,[35,778,779],{"class":80},"    else",[35,781,597],{"class":84},[35,783,785,788,790,792,794],{"class":37,"line":784},30,[35,786,787],{"class":84},"        item[",[35,789,677],{"class":45},[35,791,401],{"class":84},[35,793,134],{"class":80},[35,795,796],{"class":45}," \"not produced this week\"\n",[35,798,800],{"class":37,"line":799},31,[35,801,802],{"class":84},"    items.append(item)\n",[35,804,806,808,811,813],{"class":37,"line":805},32,[35,807,585],{"class":80},[35,809,810],{"class":84}," i ",[35,812,591],{"class":80},[35,814,815],{"class":84}," items:\n",[35,817,819,822,825,827,829,831,834,837,840,843,845,847,850,853,856,859,861,864,867,869,871,873,876,878,880,882],{"class":37,"line":818},33,[35,820,821],{"class":118},"    print",[35,823,824],{"class":84},"(",[35,826,216],{"class":80},[35,828,305],{"class":45},[35,830,223],{"class":222},[35,832,833],{"class":84},"i[",[35,835,836],{"class":45},"'title'",[35,838,839],{"class":84},"]",[35,841,842],{"class":80},":28",[35,844,238],{"class":222},[35,846,628],{"class":222},[35,848,849],{"class":45},"'OK '",[35,851,852],{"class":80}," if",[35,854,855],{"class":84}," i[",[35,857,858],{"class":45},"'exists'",[35,860,401],{"class":84},[35,862,863],{"class":80},"else",[35,865,866],{"class":45}," 'MISSING'",[35,868,238],{"class":222},[35,870,628],{"class":222},[35,872,833],{"class":84},[35,874,875],{"class":45},"'text'",[35,877,839],{"class":84},[35,879,238],{"class":222},[35,881,305],{"class":45},[35,883,884],{"class":84},")\n",[10,886,887,888,891,892,895],{},"The manifest is the list of reports the digest ",[57,889,890],{},"expects",", not whatever happens to be in the folder. Each entry names the report, its file, who owns it and how to turn its summary sheet into one headline sentence. Driving the digest from the manifest is what lets it notice absences: the open-orders report was not written this week, and the digest says ",[57,893,894],{},"not produced this week"," instead of silently leaving it out. A report that exists but cannot be read — a corrupt file, a renamed sheet — is treated the same way, with the reason. The headline functions are deliberately small; they read the figure each report's owner considers most important, and anything more belongs in the report itself.",[897,898,906,907,906,911,906,915,906,922,906,932,906,939,906,944,906,948,906,954,906,959,906,963,906,968,906,971,906,974,906,978,906,981,906,986,906,991,906,994,906,997,906,1001,906,1004,906,1009,906,1014,906,1017,906,1020,906,1024,906,1027,906,1030,906,1034,906,1037],"svg",{"viewBox":899,"role":900,"ariaLabelledBy":901,"xmlns":904,"style":905},"4 0 752 108","img",[902,903],"digest-flow-t","digest-flow-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[908,909,910],"title",{"id":902},"Building the weekly digest",[912,913,914],"desc",{"id":903},"The manifest lists the expected reports. Each is looked for in the week's folder; found reports have a headline read from their summary sheet, missing or unreadable ones are marked. Small files are attached, large ones linked, and one HTML email with a text alternative is written for preview.",[916,917],"rect",{"x":918,"y":539,"width":919,"height":920,"fill":921},"4","752","108","#ffffff",[916,923],{"x":924,"y":925,"width":926,"height":927,"rx":928,"fill":929,"stroke":930,"style":931},"20.0","16.0","115.2","76.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[933,934,938],"text",{"x":935,"y":936,"style":937},"77.6","41.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Manifest",[933,940,943],{"x":935,"y":941,"style":942},"61.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","expected",[933,945,947],{"x":935,"y":946,"style":942},"78.5","reports",[37,949],{"x1":950,"y1":951,"x2":952,"y2":951,"stroke":953,"style":931},"140.2","54.2","162.2","var(--brand,#5b5cf0)",[955,956],"polygon",{"points":957,"fill":958},"166.2,54.2 157.2,59.2 157.2,49.2","#5b5cf0",[916,960],{"x":961,"y":925,"width":926,"height":927,"rx":928,"fill":962,"stroke":953,"style":931},"171.2","#f0f4ff",[933,964,967],{"x":965,"y":936,"style":966},"228.8","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Find each file",[933,969,970],{"x":965,"y":941,"style":942},"found or",[933,972,973],{"x":965,"y":946,"style":942},"missing",[37,975],{"x1":976,"y1":951,"x2":977,"y2":951,"stroke":953,"style":931},"291.4","313.4",[955,979],{"points":980,"fill":958},"317.4,54.2 308.4,59.2 308.4,49.2",[916,982],{"x":983,"y":925,"width":926,"height":927,"rx":928,"fill":984,"stroke":985,"style":931},"322.4","#fdefd8","var(--gold,#b4740a)",[933,987,990],{"x":988,"y":936,"style":989},"380.0","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Headline",[933,992,993],{"x":988,"y":941,"style":942},"from the",[933,995,996],{"x":988,"y":946,"style":942},"Summary sheet",[37,998],{"x1":999,"y1":951,"x2":1000,"y2":951,"stroke":953,"style":931},"442.6","464.6",[955,1002],{"points":1003,"fill":958},"468.6,54.2 459.6,59.2 459.6,49.2",[916,1005],{"x":1006,"y":925,"width":926,"height":927,"rx":928,"fill":1007,"stroke":1008,"style":931},"473.6","#d9f4f1","var(--teal,#0f9488)",[933,1010,1013],{"x":1011,"y":936,"style":1012},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Attach or link",[933,1015,1016],{"x":1011,"y":941,"style":942},"under the size",[933,1018,1019],{"x":1011,"y":946,"style":942},"limit",[37,1021],{"x1":1022,"y1":951,"x2":1023,"y2":951,"stroke":953,"style":931},"593.8","615.8",[955,1025],{"points":1026,"fill":958},"619.8,54.2 610.8,59.2 610.8,49.2",[916,1028],{"x":1029,"y":925,"width":926,"height":927,"rx":928,"fill":962,"stroke":953,"style":931},"624.8",[933,1031,1033],{"x":1032,"y":936,"style":966},"682.4","One email",[933,1035,1036],{"x":1032,"y":941,"style":942},"HTML + text,",[933,1038,1039],{"x":1032,"y":946,"style":942},"preview",[20,1041,1043],{"id":1042},"step-2-attach-what-fits-link-the-rest","Step 2 — Attach what fits, link the rest",[25,1045,1047],{"className":71,"code":1046,"language":73,"meta":30,"style":30},"LIMIT = 3 * 1024 * 1024                              # total attachment budget for the email\nSHARE = \"https:\u002F\u002Fintranet.example.com\u002Freports\"       # where every report is also published\nbudget = LIMIT\nfor i in sorted((i for i in items if i[\"exists\"]), key=lambda i: i[\"size\"]):\n    if i[\"size\"] \u003C= budget:\n        i[\"delivery\"], budget = \"attached\", budget - i[\"size\"]\n    else:\n        i[\"delivery\"] = \"link\"\n    i[\"url\"] = f\"{SHARE}\u002F{WEEK}\u002F{i['file']}\"\nprint({i[\"title\"]: (i.get(\"delivery\"), round(i.get(\"size\", 0) \u002F 1024)) for i in items})\n",[32,1048,1049,1073,1086,1096,1143,1159,1187,1193,1206,1253],{"__ignoreMap":30},[35,1050,1051,1054,1056,1059,1062,1065,1067,1069],{"class":37,"line":38},[35,1052,1053],{"class":118},"LIMIT",[35,1055,122],{"class":80},[35,1057,1058],{"class":118}," 3",[35,1060,1061],{"class":80}," *",[35,1063,1064],{"class":118}," 1024",[35,1066,1061],{"class":80},[35,1068,1064],{"class":118},[35,1070,1072],{"class":1071},"s-wDw","                              # total attachment budget for the email\n",[35,1074,1075,1078,1080,1083],{"class":37,"line":94},[35,1076,1077],{"class":118},"SHARE",[35,1079,122],{"class":80},[35,1081,1082],{"class":45}," \"https:\u002F\u002Fintranet.example.com\u002Freports\"",[35,1084,1085],{"class":1071},"       # where every report is also published\n",[35,1087,1088,1091,1093],{"class":37,"line":108},[35,1089,1090],{"class":84},"budget ",[35,1092,134],{"class":80},[35,1094,1095],{"class":118}," LIMIT\n",[35,1097,1098,1100,1102,1104,1107,1110,1112,1114,1116,1119,1122,1124,1126,1129,1132,1135,1138,1140],{"class":37,"line":115},[35,1099,585],{"class":80},[35,1101,810],{"class":84},[35,1103,591],{"class":80},[35,1105,1106],{"class":118}," sorted",[35,1108,1109],{"class":84},"((i ",[35,1111,585],{"class":80},[35,1113,810],{"class":84},[35,1115,591],{"class":80},[35,1117,1118],{"class":84}," items ",[35,1120,1121],{"class":80},"if",[35,1123,855],{"class":84},[35,1125,643],{"class":45},[35,1127,1128],{"class":84},"]), ",[35,1130,1131],{"class":692},"key",[35,1133,1134],{"class":80},"=lambda",[35,1136,1137],{"class":84}," i: i[",[35,1139,709],{"class":45},[35,1141,1142],{"class":84},"]):\n",[35,1144,1145,1147,1149,1151,1153,1156],{"class":37,"line":128},[35,1146,652],{"class":80},[35,1148,855],{"class":84},[35,1150,709],{"class":45},[35,1152,401],{"class":84},[35,1154,1155],{"class":80},"\u003C=",[35,1157,1158],{"class":84}," budget:\n",[35,1160,1161,1164,1167,1170,1172,1175,1178,1181,1183,1185],{"class":37,"line":152},[35,1162,1163],{"class":84},"        i[",[35,1165,1166],{"class":45},"\"delivery\"",[35,1168,1169],{"class":84},"], budget ",[35,1171,134],{"class":80},[35,1173,1174],{"class":45}," \"attached\"",[35,1176,1177],{"class":84},", budget ",[35,1179,1180],{"class":80},"-",[35,1182,855],{"class":84},[35,1184,709],{"class":45},[35,1186,563],{"class":84},[35,1188,1189,1191],{"class":37,"line":163},[35,1190,779],{"class":80},[35,1192,597],{"class":84},[35,1194,1195,1197,1199,1201,1203],{"class":37,"line":202},[35,1196,1163],{"class":84},[35,1198,1166],{"class":45},[35,1200,401],{"class":84},[35,1202,134],{"class":80},[35,1204,1205],{"class":45}," \"link\"\n",[35,1207,1208,1211,1214,1216,1218,1221,1223,1225,1227,1229,1231,1233,1235,1237,1239,1241,1243,1246,1248,1250],{"class":37,"line":260},[35,1209,1210],{"class":84},"    i[",[35,1212,1213],{"class":45},"\"url\"",[35,1215,401],{"class":84},[35,1217,134],{"class":80},[35,1219,1220],{"class":80}," f",[35,1222,305],{"class":45},[35,1224,223],{"class":222},[35,1226,1077],{"class":118},[35,1228,238],{"class":222},[35,1230,146],{"class":45},[35,1232,223],{"class":222},[35,1234,119],{"class":118},[35,1236,238],{"class":222},[35,1238,146],{"class":45},[35,1240,223],{"class":222},[35,1242,833],{"class":84},[35,1244,1245],{"class":45},"'file'",[35,1247,839],{"class":84},[35,1249,238],{"class":222},[35,1251,1252],{"class":45},"\"\n",[35,1254,1255,1258,1261,1263,1266,1268,1271,1274,1277,1279,1281,1283,1285,1287,1289,1292,1294,1296,1298],{"class":37,"line":292},[35,1256,1257],{"class":118},"print",[35,1259,1260],{"class":84},"({i[",[35,1262,169],{"class":45},[35,1264,1265],{"class":84},"]: (i.get(",[35,1267,1166],{"class":45},[35,1269,1270],{"class":84},"), ",[35,1272,1273],{"class":118},"round",[35,1275,1276],{"class":84},"(i.get(",[35,1278,709],{"class":45},[35,1280,178],{"class":84},[35,1282,539],{"class":118},[35,1284,143],{"class":84},[35,1286,146],{"class":80},[35,1288,1064],{"class":118},[35,1290,1291],{"class":84},")) ",[35,1293,585],{"class":80},[35,1295,810],{"class":84},[35,1297,591],{"class":80},[35,1299,1300],{"class":84}," items})\n",[10,1302,1303,1304,18],{},"Mail servers reject or quietly drop messages above a size limit, often between 10 and 25 MB, and a digest that bundles every workbook can exceed it easily. The attachments are therefore allocated from a fixed budget, smallest first, so the most reports fit; anything that does not fit is linked instead, from the location where every report is published anyway. Here the three small workbooks, about 5 KB each, are attached and the web traffic workbook, 4.7 MB because of its raw data sheet, gets a link. Attachments are encoded in base64, which makes them about a third larger in the message than on disk, so the budget is set well below the real limit: in a test with the web traffic workbook attached, the 4.7 MB file produced a 6.4 MB email. For the general approach, see ",[14,1305,1307],{"href":1306},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Femail-a-download-link-instead-of-an-excel-attachment\u002F","Email a Download Link Instead of an Excel Attachment",[897,1309,906,1314,906,1316,906,1319,906,1322,906,1327,906,1333,906,1339,906,1344,906,1348,906,1352,906,1356,906,1359,906,1364,906,1368,906,1371,906,1374,906,1377],{"viewBox":1310,"role":900,"ariaLabelledBy":1311,"xmlns":904,"style":905},"4 0 752 199",[1312,1313],"digest-attach-t","digest-attach-d",[908,1315,1013],{"id":1312},[912,1317,1318],{"id":1313},"Small workbooks are attached so they can be opened from the email directly, offline or on a phone. Large workbooks are linked to their published location, which keeps the email small and avoids the server's size limit, at the cost of needing network access.",[916,1320],{"x":918,"y":539,"width":919,"height":1321,"fill":921},"199",[916,1323],{"x":924,"y":925,"width":1324,"height":1325,"rx":1326,"fill":1007,"stroke":1008,"style":931},"351.0","167.0","14",[933,1328,1332],{"x":1329,"y":1330,"style":1331},"195.5","44.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Attach",[37,1334],{"x1":1335,"y1":1336,"x2":1337,"y2":1336,"stroke":1008,"style":1338},"36.0","57.0","355.0","stroke-width:1px",[933,1340,1343],{"x":1329,"y":1341,"style":1342},"77.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","small workbooks",[933,1345,1347],{"x":1329,"y":1346,"style":1342},"103.0","opens straight from the email",[933,1349,1351],{"x":1329,"y":1350,"style":1342},"129.0","works offline",[933,1353,1355],{"x":1329,"y":1354,"style":1342},"155.0","counts against the limit",[916,1357],{"x":1358,"y":925,"width":1324,"height":1325,"rx":1326,"fill":962,"stroke":953,"style":931},"389.0",[933,1360,1363],{"x":1361,"y":1330,"style":1362},"564.5","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Link",[37,1365],{"x1":1366,"y1":1336,"x2":1367,"y2":1336,"stroke":953,"style":1338},"405.0","724.0",[933,1369,1370],{"x":1361,"y":1341,"style":1342},"large workbooks",[933,1372,1373],{"x":1361,"y":1346,"style":1342},"keeps the email small",[933,1375,1376],{"x":1361,"y":1350,"style":1342},"one copy, always current",[933,1378,1379],{"x":1361,"y":1354,"style":1342},"needs access to the share",[20,1381,1383],{"id":1382},"step-3-one-email-html-and-plain-text","Step 3 — One email, HTML and plain text",[25,1385,1387],{"className":71,"code":1386,"language":73,"meta":30,"style":30},"from email.message import EmailMessage\nfrom html import escape\n\nrows_html = []\nfor i in items:\n    status = (\"&#10003;\" if i[\"exists\"] else \"&#9888;\")\n    detail = (f'attached' if i.get(\"delivery\") == \"attached\" else\n              f'\u003Ca href=\"{escape(i[\"url\"])}\">open\u003C\u002Fa>' if i[\"exists\"] else f'ask {escape(i[\"owner\"])}')\n    colour = \"#222\" if i[\"exists\"] else \"#C62828\"\n    rows_html.append(f'\u003Ctr>\u003Ctd style=\"padding:6px 10px\">{status}\u003C\u002Ftd>\u003Ctd style=\"padding:6px 10px\">\u003Cb>{escape(i[\"title\"])}\u003C\u002Fb>\u003C\u002Ftd>'\n                     f'\u003Ctd style=\"padding:6px 10px;color:{colour}\">{escape(i[\"text\"])}\u003C\u002Ftd>\u003Ctd style=\"padding:6px 10px\">{detail}\u003C\u002Ftd>\u003C\u002Ftr>')\nmissing = [i[\"title\"] for i in items if not i[\"exists\"]]\nhtml = (f'\u003Cdiv style=\"font-family:Arial,sans-serif;font-size:14px\">'\n        f'\u003Cp>Weekly reports for \u003Cb>{WEEK}\u003C\u002Fb>: {len(items) - len(missing)} of {len(items)} produced.\u003C\u002Fp>'\n        f'\u003Ctable style=\"border-collapse:collapse\">{\"\".join(rows_html)}\u003C\u002Ftable>'\n        f'\u003Cp style=\"color:#888;font-size:12px\">All reports are also at {escape(SHARE)}\u002F{WEEK}\u002F\u003C\u002Fp>\u003C\u002Fdiv>')\ntext = \"\\n\".join([f\"Weekly reports for {WEEK}: {len(items) - len(missing)} of {len(items)} produced.\", \"\"] +\n                 [f\"- {i['title']}: {i['text']}\" + (f\" ({i['url']})\" if i.get('delivery') == 'link' else \"\") for i in items])\n\nmsg = EmailMessage()\nmsg[\"Subject\"] = f\"Weekly reports {WEEK}\" + (f\" — {len(missing)} missing\" if missing else \"\")\nmsg[\"From\"], msg[\"To\"] = \"reports@example.com\", \"management@example.com\"\nmsg.set_content(text)\nmsg.add_alternative(html, subtype=\"html\")\nfor i in items:\n    if i.get(\"delivery\") == \"attached\":\n        msg.add_attachment(i[\"path\"].read_bytes(), maintype=\"application\",\n                           subtype=\"vnd.openxmlformats-officedocument.spreadsheetml.sheet\", filename=i[\"file\"])\nPath(\"digest_preview.eml\").write_bytes(bytes(msg))\nprint(msg[\"Subject\"], \"|\", round(len(bytes(msg)) \u002F 1024), \"KB\")\nprint(text)\n",[32,1388,1389,1401,1413,1417,1426,1436,1464,1494,1546,1569,1602,1645,1679,1693,1742,1762,1794,1861,1950,1954,1964,2020,2045,2050,2065,2075,2091,2111,2135,2152,2193],{"__ignoreMap":30},[35,1390,1391,1393,1396,1398],{"class":37,"line":38},[35,1392,81],{"class":80},[35,1394,1395],{"class":84}," email.message ",[35,1397,88],{"class":80},[35,1399,1400],{"class":84}," EmailMessage\n",[35,1402,1403,1405,1408,1410],{"class":37,"line":94},[35,1404,81],{"class":80},[35,1406,1407],{"class":84}," html ",[35,1409,88],{"class":80},[35,1411,1412],{"class":84}," escape\n",[35,1414,1415],{"class":37,"line":108},[35,1416,112],{"emptyLinePlaceholder":111},[35,1418,1419,1422,1424],{"class":37,"line":115},[35,1420,1421],{"class":84},"rows_html ",[35,1423,134],{"class":80},[35,1425,579],{"class":84},[35,1427,1428,1430,1432,1434],{"class":37,"line":128},[35,1429,585],{"class":80},[35,1431,810],{"class":84},[35,1433,591],{"class":80},[35,1435,815],{"class":84},[35,1437,1438,1441,1443,1446,1449,1451,1453,1455,1457,1459,1462],{"class":37,"line":152},[35,1439,1440],{"class":84},"    status ",[35,1442,134],{"class":80},[35,1444,1445],{"class":84}," (",[35,1447,1448],{"class":45},"\"&#10003;\"",[35,1450,852],{"class":80},[35,1452,855],{"class":84},[35,1454,643],{"class":45},[35,1456,401],{"class":84},[35,1458,863],{"class":80},[35,1460,1461],{"class":45}," \"&#9888;\"",[35,1463,884],{"class":84},[35,1465,1466,1469,1471,1473,1475,1478,1480,1483,1485,1487,1489,1491],{"class":37,"line":163},[35,1467,1468],{"class":84},"    detail ",[35,1470,134],{"class":80},[35,1472,1445],{"class":84},[35,1474,216],{"class":80},[35,1476,1477],{"class":45},"'attached'",[35,1479,852],{"class":80},[35,1481,1482],{"class":84}," i.get(",[35,1484,1166],{"class":45},[35,1486,143],{"class":84},[35,1488,404],{"class":80},[35,1490,1174],{"class":45},[35,1492,1493],{"class":80}," else\n",[35,1495,1496,1499,1502,1504,1507,1509,1511,1513,1516,1518,1520,1522,1524,1526,1528,1531,1533,1535,1537,1539,1541,1544],{"class":37,"line":202},[35,1497,1498],{"class":80},"              f",[35,1500,1501],{"class":45},"'\u003Ca href=\"",[35,1503,223],{"class":222},[35,1505,1506],{"class":84},"escape(i[",[35,1508,1213],{"class":45},[35,1510,547],{"class":84},[35,1512,238],{"class":222},[35,1514,1515],{"class":45},"\">open\u003C\u002Fa>'",[35,1517,852],{"class":80},[35,1519,855],{"class":84},[35,1521,643],{"class":45},[35,1523,401],{"class":84},[35,1525,863],{"class":80},[35,1527,1220],{"class":80},[35,1529,1530],{"class":45},"'ask ",[35,1532,223],{"class":222},[35,1534,1506],{"class":84},[35,1536,191],{"class":45},[35,1538,547],{"class":84},[35,1540,238],{"class":222},[35,1542,1543],{"class":45},"'",[35,1545,884],{"class":84},[35,1547,1548,1551,1553,1556,1558,1560,1562,1564,1566],{"class":37,"line":260},[35,1549,1550],{"class":84},"    colour ",[35,1552,134],{"class":80},[35,1554,1555],{"class":45}," \"#222\"",[35,1557,852],{"class":80},[35,1559,855],{"class":84},[35,1561,643],{"class":45},[35,1563,401],{"class":84},[35,1565,863],{"class":80},[35,1567,1568],{"class":45}," \"#C62828\"\n",[35,1570,1571,1574,1576,1579,1581,1584,1586,1589,1591,1593,1595,1597,1599],{"class":37,"line":292},[35,1572,1573],{"class":84},"    rows_html.append(",[35,1575,216],{"class":80},[35,1577,1578],{"class":45},"'\u003Ctr>\u003Ctd style=\"padding:6px 10px\">",[35,1580,223],{"class":222},[35,1582,1583],{"class":84},"status",[35,1585,238],{"class":222},[35,1587,1588],{"class":45},"\u003C\u002Ftd>\u003Ctd style=\"padding:6px 10px\">\u003Cb>",[35,1590,223],{"class":222},[35,1592,1506],{"class":84},[35,1594,169],{"class":45},[35,1596,547],{"class":84},[35,1598,238],{"class":222},[35,1600,1601],{"class":45},"\u003C\u002Fb>\u003C\u002Ftd>'\n",[35,1603,1604,1607,1610,1612,1615,1617,1620,1622,1624,1626,1628,1630,1633,1635,1638,1640,1643],{"class":37,"line":329},[35,1605,1606],{"class":80},"                     f",[35,1608,1609],{"class":45},"'\u003Ctd style=\"padding:6px 10px;color:",[35,1611,223],{"class":222},[35,1613,1614],{"class":84},"colour",[35,1616,238],{"class":222},[35,1618,1619],{"class":45},"\">",[35,1621,223],{"class":222},[35,1623,1506],{"class":84},[35,1625,677],{"class":45},[35,1627,547],{"class":84},[35,1629,238],{"class":222},[35,1631,1632],{"class":45},"\u003C\u002Ftd>\u003Ctd style=\"padding:6px 10px\">",[35,1634,223],{"class":222},[35,1636,1637],{"class":84},"detail",[35,1639,238],{"class":222},[35,1641,1642],{"class":45},"\u003C\u002Ftd>\u003C\u002Ftr>'",[35,1644,884],{"class":84},[35,1646,1647,1650,1652,1655,1657,1659,1661,1663,1665,1667,1669,1672,1674,1676],{"class":37,"line":361},[35,1648,1649],{"class":84},"missing ",[35,1651,134],{"class":80},[35,1653,1654],{"class":84}," [i[",[35,1656,169],{"class":45},[35,1658,401],{"class":84},[35,1660,585],{"class":80},[35,1662,810],{"class":84},[35,1664,591],{"class":80},[35,1666,1118],{"class":84},[35,1668,1121],{"class":80},[35,1670,1671],{"class":80}," not",[35,1673,855],{"class":84},[35,1675,643],{"class":45},[35,1677,1678],{"class":84},"]]\n",[35,1680,1681,1684,1686,1688,1690],{"class":37,"line":425},[35,1682,1683],{"class":84},"html ",[35,1685,134],{"class":80},[35,1687,1445],{"class":84},[35,1689,216],{"class":80},[35,1691,1692],{"class":45},"'\u003Cdiv style=\"font-family:Arial,sans-serif;font-size:14px\">'\n",[35,1694,1695,1698,1701,1703,1705,1707,1710,1712,1714,1717,1719,1722,1725,1727,1730,1732,1734,1737,1739],{"class":37,"line":457},[35,1696,1697],{"class":80},"        f",[35,1699,1700],{"class":45},"'\u003Cp>Weekly reports for \u003Cb>",[35,1702,223],{"class":222},[35,1704,119],{"class":118},[35,1706,238],{"class":222},[35,1708,1709],{"class":45},"\u003C\u002Fb>: ",[35,1711,223],{"class":222},[35,1713,246],{"class":118},[35,1715,1716],{"class":84},"(items) ",[35,1718,1180],{"class":80},[35,1720,1721],{"class":118}," len",[35,1723,1724],{"class":84},"(missing)",[35,1726,238],{"class":222},[35,1728,1729],{"class":45}," of ",[35,1731,223],{"class":222},[35,1733,246],{"class":118},[35,1735,1736],{"class":84},"(items)",[35,1738,238],{"class":222},[35,1740,1741],{"class":45}," produced.\u003C\u002Fp>'\n",[35,1743,1744,1746,1749,1751,1754,1757,1759],{"class":37,"line":485},[35,1745,1697],{"class":80},[35,1747,1748],{"class":45},"'\u003Ctable style=\"border-collapse:collapse\">",[35,1750,223],{"class":222},[35,1752,1753],{"class":45},"\"\"",[35,1755,1756],{"class":84},".join(rows_html)",[35,1758,238],{"class":222},[35,1760,1761],{"class":45},"\u003C\u002Ftable>'\n",[35,1763,1764,1766,1769,1771,1774,1776,1779,1781,1783,1785,1787,1789,1792],{"class":37,"line":517},[35,1765,1697],{"class":80},[35,1767,1768],{"class":45},"'\u003Cp style=\"color:#888;font-size:12px\">All reports are also at ",[35,1770,223],{"class":222},[35,1772,1773],{"class":84},"escape(",[35,1775,1077],{"class":118},[35,1777,1778],{"class":84},")",[35,1780,238],{"class":222},[35,1782,146],{"class":45},[35,1784,223],{"class":222},[35,1786,119],{"class":118},[35,1788,238],{"class":222},[35,1790,1791],{"class":45},"\u002F\u003C\u002Fp>\u003C\u002Fdiv>'",[35,1793,884],{"class":84},[35,1795,1796,1799,1801,1804,1807,1809,1812,1814,1817,1819,1821,1823,1825,1827,1829,1831,1833,1835,1837,1839,1841,1843,1845,1847,1849,1852,1854,1856,1858],{"class":37,"line":560},[35,1797,1798],{"class":84},"text ",[35,1800,134],{"class":80},[35,1802,1803],{"class":45}," \"",[35,1805,1806],{"class":222},"\\n",[35,1808,305],{"class":45},[35,1810,1811],{"class":84},".join([",[35,1813,216],{"class":80},[35,1815,1816],{"class":45},"\"Weekly reports for ",[35,1818,223],{"class":222},[35,1820,119],{"class":118},[35,1822,238],{"class":222},[35,1824,172],{"class":45},[35,1826,223],{"class":222},[35,1828,246],{"class":118},[35,1830,1716],{"class":84},[35,1832,1180],{"class":80},[35,1834,1721],{"class":118},[35,1836,1724],{"class":84},[35,1838,238],{"class":222},[35,1840,1729],{"class":45},[35,1842,223],{"class":222},[35,1844,246],{"class":118},[35,1846,1736],{"class":84},[35,1848,238],{"class":222},[35,1850,1851],{"class":45}," produced.\"",[35,1853,178],{"class":84},[35,1855,1753],{"class":45},[35,1857,401],{"class":84},[35,1859,1860],{"class":80},"+\n",[35,1862,1863,1866,1868,1871,1873,1875,1877,1879,1881,1883,1885,1887,1889,1891,1893,1895,1898,1900,1902,1905,1907,1909,1912,1914,1916,1919,1921,1923,1926,1928,1930,1933,1936,1939,1941,1943,1945,1947],{"class":37,"line":566},[35,1864,1865],{"class":84},"                 [",[35,1867,216],{"class":80},[35,1869,1870],{"class":45},"\"- ",[35,1872,223],{"class":222},[35,1874,833],{"class":84},[35,1876,836],{"class":45},[35,1878,839],{"class":84},[35,1880,238],{"class":222},[35,1882,172],{"class":45},[35,1884,223],{"class":222},[35,1886,833],{"class":84},[35,1888,875],{"class":45},[35,1890,839],{"class":84},[35,1892,238],{"class":222},[35,1894,305],{"class":45},[35,1896,1897],{"class":80}," +",[35,1899,1445],{"class":84},[35,1901,216],{"class":80},[35,1903,1904],{"class":45},"\" (",[35,1906,223],{"class":222},[35,1908,833],{"class":84},[35,1910,1911],{"class":45},"'url'",[35,1913,839],{"class":84},[35,1915,238],{"class":222},[35,1917,1918],{"class":45},")\"",[35,1920,852],{"class":80},[35,1922,1482],{"class":84},[35,1924,1925],{"class":45},"'delivery'",[35,1927,143],{"class":84},[35,1929,404],{"class":80},[35,1931,1932],{"class":45}," 'link'",[35,1934,1935],{"class":80}," else",[35,1937,1938],{"class":45}," \"\"",[35,1940,143],{"class":84},[35,1942,585],{"class":80},[35,1944,810],{"class":84},[35,1946,591],{"class":80},[35,1948,1949],{"class":84}," items])\n",[35,1951,1952],{"class":37,"line":571},[35,1953,112],{"emptyLinePlaceholder":111},[35,1955,1956,1959,1961],{"class":37,"line":582},[35,1957,1958],{"class":84},"msg ",[35,1960,134],{"class":80},[35,1962,1963],{"class":84}," EmailMessage()\n",[35,1965,1966,1969,1972,1974,1976,1978,1981,1983,1985,1987,1989,1991,1993,1995,1998,2000,2002,2004,2006,2009,2011,2014,2016,2018],{"class":37,"line":600},[35,1967,1968],{"class":84},"msg[",[35,1970,1971],{"class":45},"\"Subject\"",[35,1973,401],{"class":84},[35,1975,134],{"class":80},[35,1977,1220],{"class":80},[35,1979,1980],{"class":45},"\"Weekly reports ",[35,1982,223],{"class":222},[35,1984,119],{"class":118},[35,1986,238],{"class":222},[35,1988,305],{"class":45},[35,1990,1897],{"class":80},[35,1992,1445],{"class":84},[35,1994,216],{"class":80},[35,1996,1997],{"class":45},"\" — ",[35,1999,223],{"class":222},[35,2001,246],{"class":118},[35,2003,1724],{"class":84},[35,2005,238],{"class":222},[35,2007,2008],{"class":45}," missing\"",[35,2010,852],{"class":80},[35,2012,2013],{"class":84}," missing ",[35,2015,863],{"class":80},[35,2017,1938],{"class":45},[35,2019,884],{"class":84},[35,2021,2022,2024,2027,2030,2033,2035,2037,2040,2042],{"class":37,"line":620},[35,2023,1968],{"class":84},[35,2025,2026],{"class":45},"\"From\"",[35,2028,2029],{"class":84},"], msg[",[35,2031,2032],{"class":45},"\"To\"",[35,2034,401],{"class":84},[35,2036,134],{"class":80},[35,2038,2039],{"class":45}," \"reports@example.com\"",[35,2041,178],{"class":84},[35,2043,2044],{"class":45},"\"management@example.com\"\n",[35,2046,2047],{"class":37,"line":649},[35,2048,2049],{"class":84},"msg.set_content(text)\n",[35,2051,2052,2055,2058,2060,2063],{"class":37,"line":663},[35,2053,2054],{"class":84},"msg.add_alternative(html, ",[35,2056,2057],{"class":692},"subtype",[35,2059,134],{"class":80},[35,2061,2062],{"class":45},"\"html\"",[35,2064,884],{"class":84},[35,2066,2067,2069,2071,2073],{"class":37,"line":671},[35,2068,585],{"class":80},[35,2070,810],{"class":84},[35,2072,591],{"class":80},[35,2074,815],{"class":84},[35,2076,2077,2079,2081,2083,2085,2087,2089],{"class":37,"line":704},[35,2078,652],{"class":80},[35,2080,1482],{"class":84},[35,2082,1166],{"class":45},[35,2084,143],{"class":84},[35,2086,404],{"class":80},[35,2088,1174],{"class":45},[35,2090,597],{"class":84},[35,2092,2093,2096,2098,2101,2104,2106,2109],{"class":37,"line":719},[35,2094,2095],{"class":84},"        msg.add_attachment(i[",[35,2097,637],{"class":45},[35,2099,2100],{"class":84},"].read_bytes(), ",[35,2102,2103],{"class":692},"maintype",[35,2105,134],{"class":80},[35,2107,2108],{"class":45},"\"application\"",[35,2110,199],{"class":84},[35,2112,2113,2116,2118,2121,2123,2126,2128,2130,2132],{"class":37,"line":734},[35,2114,2115],{"class":692},"                           subtype",[35,2117,134],{"class":80},[35,2119,2120],{"class":45},"\"vnd.openxmlformats-officedocument.spreadsheetml.sheet\"",[35,2122,178],{"class":84},[35,2124,2125],{"class":692},"filename",[35,2127,134],{"class":80},[35,2129,833],{"class":84},[35,2131,181],{"class":45},[35,2133,2134],{"class":84},"])\n",[35,2136,2137,2140,2143,2146,2149],{"class":37,"line":776},[35,2138,2139],{"class":84},"Path(",[35,2141,2142],{"class":45},"\"digest_preview.eml\"",[35,2144,2145],{"class":84},").write_bytes(",[35,2147,2148],{"class":118},"bytes",[35,2150,2151],{"class":84},"(msg))\n",[35,2153,2154,2156,2159,2161,2164,2167,2169,2171,2173,2175,2177,2179,2182,2184,2186,2188,2191],{"class":37,"line":784},[35,2155,1257],{"class":118},[35,2157,2158],{"class":84},"(msg[",[35,2160,1971],{"class":45},[35,2162,2163],{"class":84},"], ",[35,2165,2166],{"class":45},"\"|\"",[35,2168,178],{"class":84},[35,2170,1273],{"class":118},[35,2172,824],{"class":84},[35,2174,246],{"class":118},[35,2176,824],{"class":84},[35,2178,2148],{"class":118},[35,2180,2181],{"class":84},"(msg)) ",[35,2183,146],{"class":80},[35,2185,1064],{"class":118},[35,2187,1270],{"class":84},[35,2189,2190],{"class":45},"\"KB\"",[35,2192,884],{"class":84},[35,2194,2195,2197],{"class":37,"line":799},[35,2196,1257],{"class":118},[35,2198,2199],{"class":84},"(text)\n",[10,2201,2202,2203,2206,2207,2210,2211,2214,2215,18],{},"The message body is a short table: a tick or a warning sign, the report's name, its headline, and whether the workbook is attached or linked — or, for a missing report, who to ask. The opening line counts how many of the expected reports were produced, and the subject line says when something is missing, so the gap is visible even in an inbox list. Every value inserted into the HTML goes through ",[32,2204,2205],{},"html.escape",", because report titles and figures can contain characters such as ",[32,2208,2209],{},"&"," that would otherwise break the markup. The plain-text alternative carries the same information for mail clients and notification previews that do not show HTML. The message is written to an ",[32,2212,2213],{},".eml"," file as a preview — 24 KB with the three small workbooks attached — sending it is one call to an SMTP connection, as in ",[14,2216,2218],{"href":2217},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-excel-report-to-multiple-recipients-python\u002F","Send Excel Report to Multiple Recipients in Python",[897,2220,906,2226,906,2229,906,2232,906,2237,906,2242,906,2245,906,2251,906,2255,906,2259,906,2263,906,2267,906,2271,906,2275,906,2279,906,2284,906,2287,906,2289,906,2293,906,2295,906,2297,906,2299,906,2302,906,2305,906,2309,906,2311,906,2314,906,2316,906,2319,906,2321,906,2324,906,2326,906,2329,906,2332,906,2336,906,2338,906,2340,906,2342,906,2345,906,2347,906,2350,906,2352,906,2354,906,2357,906,2360,906,2362,906,2364,906,2366,906,2369,906,2371,906,2374,906,2376,906,2378,906,2381,906,2385,906,2387,906,2390,906,2392,906,2395,906,2399,906,2402,906,2404,906,2407,906,2410,906,2414,906,2416,906,2418,906,2420,906,2423,906,2425,906,2428,906,2430],{"viewBox":2221,"role":900,"ariaLabelledBy":2222,"xmlns":904,"style":2225},"31 0 698 224",[2223,2224],"digest-body-t","digest-body-d","width:100%;max-width:698px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[908,2227,2228],{"id":2223},"The digest for week 41",[912,2230,2231],{"id":2224},"Weekly sales shows revenue of 422,191 pounds across three regions, attached. Three SKUs are below reorder level, attached. Aged debtors shows 121,200 pounds owed with 4,100 over 90 days, attached. Open orders was not produced. Web traffic shows 48,210 sessions with a link to the large workbook.",[916,2233],{"x":2234,"y":539,"width":2235,"height":2236,"fill":921},"31","698","224",[916,2238],{"x":2239,"y":925,"width":2240,"height":2241,"rx":539,"fill":929,"stroke":930,"style":1338},"47.2","32.0","24.0",[916,2243],{"x":2244,"y":925,"width":1330,"height":2241,"rx":539,"fill":929,"stroke":930,"style":1338},"79.2",[933,2246,2250],{"x":2247,"y":2248,"style":2249},"101.2","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[916,2252],{"x":2253,"y":925,"width":2254,"height":2241,"rx":539,"fill":929,"stroke":930,"style":1338},"123.2","181.0",[933,2256,2258],{"x":2257,"y":2248,"style":2249},"213.7","B",[916,2260],{"x":2261,"y":925,"width":2262,"height":2241,"rx":539,"fill":929,"stroke":930,"style":1338},"304.2","250.7",[933,2264,2266],{"x":2265,"y":2248,"style":2249},"429.5","C",[916,2268],{"x":2269,"y":925,"width":2270,"height":2241,"rx":539,"fill":929,"stroke":930,"style":1338},"554.9","158.0",[933,2272,2274],{"x":2273,"y":2248,"style":2249},"633.9","D",[916,2276],{"x":2239,"y":2277,"width":2240,"height":2278,"rx":539,"fill":929,"stroke":930,"style":1338},"40.0","28.0",[933,2280,2283],{"x":2281,"y":2282,"style":2249},"63.2","59.0","1",[916,2285],{"x":2244,"y":2277,"width":1330,"height":2278,"rx":539,"fill":962,"stroke":953,"style":2286},"stroke-width:1.5px",[916,2288],{"x":2253,"y":2277,"width":2254,"height":2278,"rx":539,"fill":962,"stroke":953,"style":2286},[933,2290,2292],{"x":2257,"y":2282,"style":2291},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Report",[916,2294],{"x":2261,"y":2277,"width":2262,"height":2278,"rx":539,"fill":962,"stroke":953,"style":2286},[933,2296,990],{"x":2265,"y":2282,"style":2291},[916,2298],{"x":2269,"y":2277,"width":2270,"height":2278,"rx":539,"fill":962,"stroke":953,"style":2286},[933,2300,2301],{"x":2273,"y":2282,"style":2291},"Delivery",[916,2303],{"x":2239,"y":2304,"width":2240,"height":2278,"rx":539,"fill":929,"stroke":930,"style":1338},"68.0",[933,2306,2308],{"x":2281,"y":2307,"style":2249},"87.0","2",[916,2310],{"x":2244,"y":2304,"width":1330,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2312,2313],{"x":2247,"y":2307,"style":1342},"✓",[916,2315],{"x":2253,"y":2304,"width":2254,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2317,2318],{"x":2257,"y":2307,"style":1342},"Weekly sales",[916,2320],{"x":2261,"y":2304,"width":2262,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2322,2323],{"x":2265,"y":2307,"style":1342},"Revenue £422,191 across 3 regions",[916,2325],{"x":2269,"y":2304,"width":2270,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2327,2328],{"x":2273,"y":2307,"style":1342},"attached",[916,2330],{"x":2239,"y":2331,"width":2240,"height":2278,"rx":539,"fill":929,"stroke":930,"style":1338},"96.0",[933,2333,2335],{"x":2281,"y":2334,"style":2249},"115.0","3",[916,2337],{"x":2244,"y":2331,"width":1330,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2339,2313],{"x":2247,"y":2334,"style":1342},[916,2341],{"x":2253,"y":2331,"width":2254,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2343,2344],{"x":2257,"y":2334,"style":1342},"Stock below reorder level",[916,2346],{"x":2261,"y":2331,"width":2262,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2348,2349],{"x":2265,"y":2334,"style":1342},"3 SKUs below reorder level",[916,2351],{"x":2269,"y":2331,"width":2270,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2353,2328],{"x":2273,"y":2334,"style":1342},[916,2355],{"x":2239,"y":2356,"width":2240,"height":2278,"rx":539,"fill":929,"stroke":930,"style":1338},"124.0",[933,2358,918],{"x":2281,"y":2359,"style":2249},"143.0",[916,2361],{"x":2244,"y":2356,"width":1330,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2363,2313],{"x":2247,"y":2359,"style":1342},[916,2365],{"x":2253,"y":2356,"width":2254,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2367,2368],{"x":2257,"y":2359,"style":1342},"Aged debtors",[916,2370],{"x":2261,"y":2356,"width":2262,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2372,2373],{"x":2265,"y":2359,"style":1342},"£121,200 owed, £4,100 over 90 days",[916,2375],{"x":2269,"y":2356,"width":2270,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2377,2328],{"x":2273,"y":2359,"style":1342},[916,2379],{"x":2239,"y":2380,"width":2240,"height":2278,"rx":539,"fill":929,"stroke":930,"style":1338},"152.0",[933,2382,2384],{"x":2281,"y":2383,"style":2249},"171.0","5",[916,2386],{"x":2244,"y":2380,"width":1330,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2388,2389],{"x":2247,"y":2383,"style":1342},"⚠",[916,2391],{"x":2253,"y":2380,"width":2254,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2393,2394],{"x":2257,"y":2383,"style":1342},"Open orders",[916,2396],{"x":2261,"y":2380,"width":2262,"height":2278,"rx":539,"fill":2397,"stroke":2398,"style":2286},"#fce9e9","var(--danger,#dc2626)",[933,2400,894],{"x":2265,"y":2383,"style":2401},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle",[916,2403],{"x":2269,"y":2380,"width":2270,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2405,2406],{"x":2273,"y":2383,"style":1342},"ask Customer service",[916,2408],{"x":2239,"y":2409,"width":2240,"height":2278,"rx":539,"fill":929,"stroke":930,"style":1338},"180.0",[933,2411,2413],{"x":2281,"y":2412,"style":2249},"199.0","6",[916,2415],{"x":2244,"y":2409,"width":1330,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2417,2313],{"x":2247,"y":2412,"style":1342},[916,2419],{"x":2253,"y":2409,"width":2254,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2421,2422],{"x":2257,"y":2412,"style":1342},"Web traffic",[916,2424],{"x":2261,"y":2409,"width":2262,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2426,2427],{"x":2265,"y":2412,"style":1342},"48,210 sessions",[916,2429],{"x":2269,"y":2409,"width":2270,"height":2278,"rx":539,"fill":921,"stroke":930,"style":1338},[933,2431,2432],{"x":2273,"y":2412,"style":1342},"link",[20,2434,2436],{"id":2435},"when-to-send-it-and-to-whom","When to send it, and to whom",[10,2438,2439,2440,2442,2443,2447],{},"A digest works best on a fixed schedule after the last report job is due to finish, with a short grace period: if the reports normally finish by 06:30, send the digest at 07:00, and anything not there by then is reported as missing. The recipients are people who want the overview — managers, the leadership team — while each report's owners keep receiving their own report in full. Owners also get the most value from the ",[57,2441,973],{}," line, so it is worth sending them a copy of the digest, or a separate alert, when their report is the one that failed; the pattern in ",[14,2444,2446],{"href":2445},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fdetect-a-scheduled-report-that-did-not-run\u002F","Detect a Scheduled Report That Did Not Run"," covers that.",[20,2449,2451],{"id":2450},"common-pitfalls","Common pitfalls",[2453,2454,2455,2463,2469,2477,2483],"ul",{},[2456,2457,2458,2462],"li",{},[2459,2460,2461],"strong",{},"Listing whatever is in the folder."," Drive the digest from a manifest so absences show.",[2456,2464,2465,2468],{},[2459,2466,2467],{},"Attaching everything."," Keep a size budget and link the large files.",[2456,2470,2471,2474,2475,18],{},[2459,2472,2473],{},"Unescaped values in HTML."," Pass every inserted value through ",[32,2476,2205],{},[2456,2478,2479,2482],{},[2459,2480,2481],{},"HTML only."," Add a plain-text alternative with the same content.",[2456,2484,2485,2488],{},[2459,2486,2487],{},"Missing reports buried."," Say so in the subject line and in the opening sentence.",[20,2490,2492],{"id":2491},"conclusion","Conclusion",[10,2494,2495,2496,2498],{},"A weekly digest replaces a stream of report emails with one message: a manifest of the reports expected, a headline read from each workbook's summary sheet, attachments allocated from a size budget with large files linked instead, and an HTML table with a plain-text alternative. Missing or unreadable reports are named in the subject and the body, so a failed job is noticed the same morning, and the message is previewed as an ",[32,2497,2213],{}," before it is sent.",[20,2500,2502],{"id":2501},"frequently-asked-questions","Frequently asked questions",[10,2504,2505,2508,2509,18],{},[2459,2506,2507],{},"How do I combine several reports into one email in Python?","\nCollect the reports from a manifest, build one HTML body with a section per report, and attach the files to a single ",[32,2510,2511],{},"EmailMessage",[10,2513,2514,2517],{},[2459,2515,2516],{},"How do I keep an email with attachments under the size limit?","\nAllocate attachments from a fixed budget, smallest first, allowing for base64 growth, and link any file that does not fit.",[10,2519,2520,2523],{},[2459,2521,2522],{},"How do I show missing reports in a digest?","\nDrive the digest from a list of expected reports and mark any whose file is absent or unreadable.",[10,2525,2526,2529],{},[2459,2527,2528],{},"Why add a plain-text version to an HTML email?","\nSome mail clients and previews show only text, and spam filters treat HTML-only messages with suspicion.",[20,2531,2533],{"id":2532},"related","Related",[2453,2535,2536,2542,2549,2556],{},[2456,2537,2538,2539,2541],{},"Up one level: ",[14,2540,17],{"href":16}," — sending reports by email.",[2456,2543,2544,2548],{},[14,2545,2547],{"href":2546},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Femail-an-excel-report-with-an-html-summary-body\u002F","Email an Excel Report with an HTML Summary Body"," — HTML email in depth.",[2456,2550,2551,2555],{},[14,2552,2554],{"href":2553},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fpersonalise-report-emails-with-jinja2-templates\u002F","Personalise Report Emails with Jinja2 Templates"," — templated bodies.",[2456,2557,2558,2562],{},[14,2559,2561],{"href":2560},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fattach-multiple-excel-files-to-one-email-in-python\u002F","Attach Multiple Excel Files to One Email in Python"," — attachments.",[2564,2565,2566],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}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 .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":30,"searchDepth":94,"depth":94,"links":2568},[2569,2570,2571,2572,2573,2574,2575,2576,2577],{"id":22,"depth":94,"text":23},{"id":67,"depth":94,"text":68},{"id":1042,"depth":94,"text":1043},{"id":1382,"depth":94,"text":1383},{"id":2435,"depth":94,"text":2436},{"id":2450,"depth":94,"text":2451},{"id":2491,"depth":94,"text":2492},{"id":2501,"depth":94,"text":2502},{"id":2532,"depth":94,"text":2533},"2026-10-02","Replace five separate report emails with one weekly digest: collect the week's report files from a manifest, pull a headline figure from each workbook, build one HTML email with a section per report and a plain-text alternative, attach the files while keeping under a size limit (linking the large ones instead), state clearly which reports were not produced, and preview the message before sending.","md",[2582,2584,2586,2588],{"q":2507,"a":2583},"Collect the reports from a manifest, build one HTML body with a section per report, and attach the files to a single EmailMessage.",{"q":2516,"a":2585},"Allocate attachments from a fixed budget, smallest first, allowing for base64 growth, and link any file that does not fit.",{"q":2522,"a":2587},"Drive the digest from a list of expected reports and mark any whose file is absent or unreadable.",{"q":2528,"a":2589},"Some mail clients and previews show only text, and spam filters treat HTML-only messages with suspicion.","weekly digest email excel reports python",null,{"breadcrumb":2593},[2594,2596,2599],{"name":2595,"item":146},"Home",{"name":2597,"item":2598},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":17,"item":16},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-a-weekly-digest-of-several-reports-in-one-email",{"title":2602,"description":2603},"Send a Weekly Digest of Several Excel Reports in One Email","One weekly email for many reports with Python: a manifest of reports, headline figures read from each workbook, an HTML section per report, attachments under a size limit, missing reports flagged and an .eml preview.","send-a-weekly-digest-of-several-reports-in-one-email","automating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-a-weekly-digest-of-several-reports-in-one-email\u002Findex",[2607,2608],"email-delivery","report-layouts","how-to","q5fNxHXSQV5cDZHequ9k7xibXNkew-BlSW3c4mJ0kZM",[2612,2616],{"title":2613,"path":2614,"stem":2615,"children":-1},"Queue and Retry Failed Report Emails","\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fqueue-and-retry-failed-report-emails","automating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fqueue-and-retry-failed-report-emails\u002Findex",{"title":2617,"path":2618,"stem":2619,"children":-1},"Send an Excel Report Through Outlook from Python","\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-an-excel-report-through-outlook-from-python","automating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-an-excel-report-through-outlook-from-python\u002Findex",[2621,2623],{"path":2622,"title":17},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib",{"path":2624,"title":2597},"\u002Fautomating-reporting-workflows",[],[2627,2630,2632,2635,2637,2639],{"path":2628,"title":2629,"kind":2609},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-credit-control-dunning-letter-run","Build a Credit Control Dunning Letter Run",{"path":2631,"title":2561,"kind":2609},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fattach-multiple-excel-files-to-one-email-in-python",{"path":2633,"title":2634,"kind":2609},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fdownload-excel-attachments-from-an-email-inbox","Download Excel Attachments from an Email Inbox with Python",{"path":2636,"title":1307,"kind":2609},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Femail-a-download-link-instead-of-an-excel-attachment",{"path":2638,"title":2547,"kind":2609},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Femail-an-excel-report-with-an-html-summary-body",{"path":2640,"title":2641,"kind":2609},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fembed-a-chart-image-in-the-body-of-a-report-email","Embed a Chart Image in the Body of a Report Email",1790972129746]