[{"data":1,"prerenderedAt":1952},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fzip-large-excel-attachments-before-emailing":3,"surround:\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fzip-large-excel-attachments-before-emailing":1920,"ancestors:\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fzip-large-excel-attachments-before-emailing":1929,"children:\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fzip-large-excel-attachments-before-emailing":1934,"related:\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fzip-large-excel-attachments-before-emailing":1935},{"id":4,"title":5,"body":6,"dateModified":1887,"datePublished":1887,"description":1888,"extension":1889,"faq":1890,"keyword":1899,"kind":1900,"meta":1901,"navigation":255,"path":1909,"seo":1910,"slug":1913,"stem":1914,"topics":1915,"type":1918,"__hash__":1919},"docs\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fzip-large-excel-attachments-before-emailing\u002Findex.md","Zip Large Excel Attachments Before Emailing Them",{"type":7,"value":8,"toc":1875},"minimark",[9,24,160,165,180,208,211,508,512,515,733,741,745,751,906,912,1017,1103,1107,1110,1196,1203,1207,1210,1578,1585,1589,1599,1737,1741,1800,1804,1810,1814,1820,1826,1832,1838,1842,1871],[10,11,12,13,17,18,23],"p",{},"A monthly export grows until one day the email bounces: \"message size exceeds fixed limit\". The instinctive fix is to zip the workbook — and it barely helps, because an ",[14,15,16],"code",{},".xlsx"," file is already a zip archive of compressed XML. This guide, part of ",[19,20,22],"a",{"href":21},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002F","Emailing Excel Reports with smtplib",", measures what each option really saves on a 200,000-row workbook, builds a size budget that accounts for email encoding, and shows the approaches that shrink a report enough to send — and when to stop attaching files at all.",[25,26,34,35,34,39,34,43,34,51,34,61,34,68,34,73,34,78,34,81,34,85,34,90,34,95,34,98,34,102,34,105,34,109,34,112,34,116,34,119,34,123,34,126,34,130,34,133,34,137,34,140,34,144,34,149,34,154],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"4 0 744 286","img",[30,31],"zp-sizes-t","zp-sizes-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:744px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"The same 200,000 rows in different packages",[40,41,42],"desc",{"id":31},"Measured sizes for one dataset of 200,000 rows and eight columns. The xlsx workbook is 8.59 MB. Zipping it gives 8.27 MB and LZMA compression 7.82 MB. Repacking the workbook's contents at maximum compression gives 8.16 MB. The raw CSV is 13.96 MB, but the CSV inside a zip is only 2.93 MB.",[44,45],"rect",{"x":46,"y":47,"width":48,"height":49,"fill":50},"4","0","744","286","#ffffff",[44,52],{"x":53,"y":54,"width":55,"height":56,"rx":57,"fill":58,"stroke":59,"style":60},"20.0","16.0","121.0","28.0","7","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[62,63,67],"text",{"x":64,"y":65,"style":66},"80.5","34.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","CSV, raw",[44,69],{"x":70,"y":54,"width":71,"height":56,"rx":57,"fill":58,"stroke":59,"style":72},"151.0","515.8","stroke-width:1.5px",[62,74,77],{"x":75,"y":65,"style":76},"674.8","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:start","13.96 MB",[44,79],{"x":53,"y":80,"width":55,"height":56,"rx":57,"fill":58,"stroke":59,"style":60},"56.0",[62,82,84],{"x":64,"y":83,"style":66},"74.5","xlsx as written",[44,86],{"x":70,"y":80,"width":87,"height":56,"rx":57,"fill":88,"stroke":89,"style":72},"317.4","#fdefd8","var(--gold,#b4740a)",[62,91,94],{"x":92,"y":83,"style":93},"476.4","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","8.59 MB",[44,96],{"x":53,"y":97,"width":55,"height":56,"rx":57,"fill":58,"stroke":59,"style":60},"96.0",[62,99,101],{"x":64,"y":100,"style":66},"114.5","xlsx zipped",[44,103],{"x":70,"y":97,"width":104,"height":56,"rx":57,"fill":88,"stroke":89,"style":72},"305.6",[62,106,108],{"x":107,"y":100,"style":93},"464.6","8.27 MB",[44,110],{"x":53,"y":111,"width":55,"height":56,"rx":57,"fill":58,"stroke":59,"style":60},"136.0",[62,113,115],{"x":64,"y":114,"style":66},"154.5","xlsx repacked",[44,117],{"x":70,"y":111,"width":118,"height":56,"rx":57,"fill":88,"stroke":89,"style":72},"301.5",[62,120,122],{"x":121,"y":114,"style":93},"460.5","8.16 MB",[44,124],{"x":53,"y":125,"width":55,"height":56,"rx":57,"fill":58,"stroke":59,"style":60},"176.0",[62,127,129],{"x":64,"y":128,"style":66},"194.5","xlsx LZMA zip",[44,131],{"x":70,"y":125,"width":132,"height":56,"rx":57,"fill":88,"stroke":89,"style":72},"288.9",[62,134,136],{"x":135,"y":128,"style":93},"447.9","7.82 MB",[44,138],{"x":53,"y":139,"width":55,"height":56,"rx":57,"fill":58,"stroke":59,"style":60},"216.0",[62,141,143],{"x":64,"y":142,"style":66},"234.5","CSV zipped",[44,145],{"x":70,"y":139,"width":146,"height":56,"rx":57,"fill":147,"stroke":148,"style":72},"108.3","#d9f4f1","var(--teal,#0f9488)",[62,150,153],{"x":151,"y":142,"style":152},"267.3","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","2.93 MB",[62,155,159],{"x":156,"y":157,"style":158},"380.0","266.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","zipping a workbook saves a few percent; zipping text saves most of it",[161,162,164],"h2",{"id":163},"prerequisites","Prerequisites",[10,166,167,168,171,172,175,176,179],{},"Python's standard library covers everything here: ",[14,169,170],{},"zipfile",", ",[14,173,174],{},"io"," and ",[14,177,178],{},"email",". pandas is used for the CSV example:",[181,182,187],"pre",{"className":183,"code":184,"language":185,"meta":186,"style":186},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,188,189],{"__ignoreMap":186},[190,191,194,198,202,205],"span",{"class":192,"line":193},"line",1,[190,195,197],{"class":196},"sMTad","pip",[190,199,201],{"class":200},"srMev"," install",[190,203,204],{"class":200}," pandas",[190,206,207],{"class":200}," openpyxl\n",[10,209,210],{},"A stand-in report, large enough to show the effect:",[181,212,216],{"className":213,"code":214,"language":215,"meta":186,"style":186},"language-python shiki shiki-themes github-light github-dark-high-contrast","import io\nimport zipfile\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\n\nrng = np.random.default_rng(0)\nn = 50_000\nframe = pd.DataFrame({\n    \"order_id\": np.arange(n),\n    \"region\": rng.choice([\"North\", \"South\", \"East\", \"West\"], n),\n    \"product\": rng.choice([f\"SKU-{i:04d}\" for i in range(500)], n),\n    \"amount\": (rng.random(n) * 1000).round(2),\n})\nframe.to_excel(\"export.xlsx\", index=False)\nprint(round(Path(\"export.xlsx\").stat().st_size \u002F 1e6, 2), \"MB\")\n","python",[14,217,218,228,236,250,257,271,284,289,307,318,329,338,368,419,443,449,471],{"__ignoreMap":186},[190,219,220,224],{"class":192,"line":193},[190,221,223],{"class":222},"s-kum","import",[190,225,227],{"class":226},"skGVy"," io\n",[190,229,231,233],{"class":192,"line":230},2,[190,232,223],{"class":222},[190,234,235],{"class":226}," zipfile\n",[190,237,239,242,245,247],{"class":192,"line":238},3,[190,240,241],{"class":222},"from",[190,243,244],{"class":226}," pathlib ",[190,246,223],{"class":222},[190,248,249],{"class":226}," Path\n",[190,251,253],{"class":192,"line":252},4,[190,254,256],{"emptyLinePlaceholder":255},true,"\n",[190,258,260,262,265,268],{"class":192,"line":259},5,[190,261,223],{"class":222},[190,263,264],{"class":226}," numpy ",[190,266,267],{"class":222},"as",[190,269,270],{"class":226}," np\n",[190,272,274,276,279,281],{"class":192,"line":273},6,[190,275,223],{"class":222},[190,277,278],{"class":226}," pandas ",[190,280,267],{"class":222},[190,282,283],{"class":226}," pd\n",[190,285,287],{"class":192,"line":286},7,[190,288,256],{"emptyLinePlaceholder":255},[190,290,292,295,298,301,304],{"class":192,"line":291},8,[190,293,294],{"class":226},"rng ",[190,296,297],{"class":222},"=",[190,299,300],{"class":226}," np.random.default_rng(",[190,302,47],{"class":303},"sP0c6",[190,305,306],{"class":226},")\n",[190,308,310,313,315],{"class":192,"line":309},9,[190,311,312],{"class":226},"n ",[190,314,297],{"class":222},[190,316,317],{"class":303}," 50_000\n",[190,319,321,324,326],{"class":192,"line":320},10,[190,322,323],{"class":226},"frame ",[190,325,297],{"class":222},[190,327,328],{"class":226}," pd.DataFrame({\n",[190,330,332,335],{"class":192,"line":331},11,[190,333,334],{"class":200},"    \"order_id\"",[190,336,337],{"class":226},": np.arange(n),\n",[190,339,341,344,347,350,352,355,357,360,362,365],{"class":192,"line":340},12,[190,342,343],{"class":200},"    \"region\"",[190,345,346],{"class":226},": rng.choice([",[190,348,349],{"class":200},"\"North\"",[190,351,171],{"class":226},[190,353,354],{"class":200},"\"South\"",[190,356,171],{"class":226},[190,358,359],{"class":200},"\"East\"",[190,361,171],{"class":226},[190,363,364],{"class":200},"\"West\"",[190,366,367],{"class":226},"], n),\n",[190,369,371,374,376,379,382,386,389,392,395,398,401,404,407,410,413,416],{"class":192,"line":370},13,[190,372,373],{"class":200},"    \"product\"",[190,375,346],{"class":226},[190,377,378],{"class":222},"f",[190,380,381],{"class":200},"\"SKU-",[190,383,385],{"class":384},"sSjpA","{",[190,387,388],{"class":226},"i",[190,390,391],{"class":222},":04d",[190,393,394],{"class":384},"}",[190,396,397],{"class":200},"\"",[190,399,400],{"class":222}," for",[190,402,403],{"class":226}," i ",[190,405,406],{"class":222},"in",[190,408,409],{"class":303}," range",[190,411,412],{"class":226},"(",[190,414,415],{"class":303},"500",[190,417,418],{"class":226},")], n),\n",[190,420,422,425,428,431,434,437,440],{"class":192,"line":421},14,[190,423,424],{"class":200},"    \"amount\"",[190,426,427],{"class":226},": (rng.random(n) ",[190,429,430],{"class":222},"*",[190,432,433],{"class":303}," 1000",[190,435,436],{"class":226},").round(",[190,438,439],{"class":303},"2",[190,441,442],{"class":226},"),\n",[190,444,446],{"class":192,"line":445},15,[190,447,448],{"class":226},"})\n",[190,450,452,455,458,460,464,466,469],{"class":192,"line":451},16,[190,453,454],{"class":226},"frame.to_excel(",[190,456,457],{"class":200},"\"export.xlsx\"",[190,459,171],{"class":226},[190,461,463],{"class":462},"sa561","index",[190,465,297],{"class":222},[190,467,468],{"class":303},"False",[190,470,306],{"class":226},[190,472,474,477,479,482,485,487,490,493,496,498,500,503,506],{"class":192,"line":473},17,[190,475,476],{"class":303},"print",[190,478,412],{"class":226},[190,480,481],{"class":303},"round",[190,483,484],{"class":226},"(Path(",[190,486,457],{"class":200},[190,488,489],{"class":226},").stat().st_size ",[190,491,492],{"class":222},"\u002F",[190,494,495],{"class":303}," 1e6",[190,497,171],{"class":226},[190,499,439],{"class":303},[190,501,502],{"class":226},"), ",[190,504,505],{"class":200},"\"MB\"",[190,507,306],{"class":226},[161,509,511],{"id":510},"step-1-budget-for-the-encoded-size","Step 1 — Budget for the encoded size",[10,513,514],{},"Mail servers limit the size of the whole message, and attachments travel base64-encoded, which turns every 3 bytes into 4. A 25 MB limit therefore allows only about 18 MB of raw attachments once headers and the body are counted. Check before sending, not after the bounce:",[181,516,518],{"className":213,"code":517,"language":215,"meta":186,"style":186},"import math\n\ndef encoded_size(raw_bytes):\n    return 4 * math.ceil(raw_bytes \u002F 3) + raw_bytes \u002F\u002F 57 * 2      # base64 plus line breaks\n\nLIMIT = 25 * 1024 * 1024\nHEADROOM = 256 * 1024                                              # headers, body, MIME parts\n\ndef fits(paths):\n    total = sum(encoded_size(Path(p).stat().st_size) for p in paths)\n    return total + HEADROOM \u003C= LIMIT, total\n\nok, total = fits([\"export.xlsx\"])\nprint(ok, round(total \u002F 1e6, 2), \"MB encoded\")\n",[14,519,520,527,531,543,586,590,611,628,632,642,666,687,691,706],{"__ignoreMap":186},[190,521,522,524],{"class":192,"line":193},[190,523,223],{"class":222},[190,525,526],{"class":226}," math\n",[190,528,529],{"class":192,"line":230},[190,530,256],{"emptyLinePlaceholder":255},[190,532,533,536,540],{"class":192,"line":238},[190,534,535],{"class":222},"def",[190,537,539],{"class":538},"s_Opv"," encoded_size",[190,541,542],{"class":226},"(raw_bytes):\n",[190,544,545,548,551,554,557,559,562,565,568,571,574,577,579,582],{"class":192,"line":252},[190,546,547],{"class":222},"    return",[190,549,550],{"class":303}," 4",[190,552,553],{"class":222}," *",[190,555,556],{"class":226}," math.ceil(raw_bytes ",[190,558,492],{"class":222},[190,560,561],{"class":303}," 3",[190,563,564],{"class":226},") ",[190,566,567],{"class":222},"+",[190,569,570],{"class":226}," raw_bytes ",[190,572,573],{"class":222},"\u002F\u002F",[190,575,576],{"class":303}," 57",[190,578,553],{"class":222},[190,580,581],{"class":303}," 2",[190,583,585],{"class":584},"s-wDw","      # base64 plus line breaks\n",[190,587,588],{"class":192,"line":259},[190,589,256],{"emptyLinePlaceholder":255},[190,591,592,595,598,601,603,606,608],{"class":192,"line":273},[190,593,594],{"class":303},"LIMIT",[190,596,597],{"class":222}," =",[190,599,600],{"class":303}," 25",[190,602,553],{"class":222},[190,604,605],{"class":303}," 1024",[190,607,553],{"class":222},[190,609,610],{"class":303}," 1024\n",[190,612,613,616,618,621,623,625],{"class":192,"line":286},[190,614,615],{"class":303},"HEADROOM",[190,617,597],{"class":222},[190,619,620],{"class":303}," 256",[190,622,553],{"class":222},[190,624,605],{"class":303},[190,626,627],{"class":584},"                                              # headers, body, MIME parts\n",[190,629,630],{"class":192,"line":291},[190,631,256],{"emptyLinePlaceholder":255},[190,633,634,636,639],{"class":192,"line":309},[190,635,535],{"class":222},[190,637,638],{"class":538}," fits",[190,640,641],{"class":226},"(paths):\n",[190,643,644,647,649,652,655,658,661,663],{"class":192,"line":320},[190,645,646],{"class":226},"    total ",[190,648,297],{"class":222},[190,650,651],{"class":303}," sum",[190,653,654],{"class":226},"(encoded_size(Path(p).stat().st_size) ",[190,656,657],{"class":222},"for",[190,659,660],{"class":226}," p ",[190,662,406],{"class":222},[190,664,665],{"class":226}," paths)\n",[190,667,668,670,673,675,678,681,684],{"class":192,"line":331},[190,669,547],{"class":222},[190,671,672],{"class":226}," total ",[190,674,567],{"class":222},[190,676,677],{"class":303}," HEADROOM",[190,679,680],{"class":222}," \u003C=",[190,682,683],{"class":303}," LIMIT",[190,685,686],{"class":226},", total\n",[190,688,689],{"class":192,"line":340},[190,690,256],{"emptyLinePlaceholder":255},[190,692,693,696,698,701,703],{"class":192,"line":370},[190,694,695],{"class":226},"ok, total ",[190,697,297],{"class":222},[190,699,700],{"class":226}," fits([",[190,702,457],{"class":200},[190,704,705],{"class":226},"])\n",[190,707,708,710,713,715,718,720,722,724,726,728,731],{"class":192,"line":421},[190,709,476],{"class":303},[190,711,712],{"class":226},"(ok, ",[190,714,481],{"class":303},[190,716,717],{"class":226},"(total ",[190,719,492],{"class":222},[190,721,495],{"class":303},[190,723,171],{"class":226},[190,725,439],{"class":303},[190,727,502],{"class":226},[190,729,730],{"class":200},"\"MB encoded\"",[190,732,306],{"class":226},[10,734,735,736,740],{},"Base64 lines are 76 characters with a line break, which is where the extra two bytes per 57 come from. Limits differ by provider and by the receiving side, which may be stricter than yours; if you do not know the recipients' limit, budget for 10 MB. Sending the report itself is covered in ",[19,737,739],{"href":738},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-an-excel-report-with-gmail\u002F","Send an Excel Report with Gmail from Python",".",[161,742,744],{"id":743},"step-2-why-zipping-a-workbook-saves-so-little","Step 2 — Why zipping a workbook saves so little",[10,746,747,748,750],{},"An ",[14,749,16],{}," is a zip container whose XML parts are already deflate-compressed. Compressing compressed data again gains almost nothing:",[181,752,754],{"className":213,"code":753,"language":215,"meta":186,"style":186},"def zip_bytes(name, data, method=zipfile.ZIP_DEFLATED, level=9):\n    buffer = io.BytesIO()\n    with zipfile.ZipFile(buffer, \"w\", method, compresslevel=level) as archive:\n        archive.writestr(name, data)\n    return buffer.getvalue()\n\nworkbook = Path(\"export.xlsx\").read_bytes()\nzipped = zip_bytes(\"export.xlsx\", workbook)\nprint(round(1 - len(zipped) \u002F len(workbook), 3))       # a few percent at most\n",[14,755,756,785,795,822,827,834,838,853,868],{"__ignoreMap":186},[190,757,758,760,763,766,768,771,774,777,779,782],{"class":192,"line":193},[190,759,535],{"class":222},[190,761,762],{"class":538}," zip_bytes",[190,764,765],{"class":226},"(name, data, method",[190,767,297],{"class":222},[190,769,770],{"class":226},"zipfile.",[190,772,773],{"class":303},"ZIP_DEFLATED",[190,775,776],{"class":226},", level",[190,778,297],{"class":222},[190,780,781],{"class":303},"9",[190,783,784],{"class":226},"):\n",[190,786,787,790,792],{"class":192,"line":230},[190,788,789],{"class":226},"    buffer ",[190,791,297],{"class":222},[190,793,794],{"class":226}," io.BytesIO()\n",[190,796,797,800,803,806,809,812,814,817,819],{"class":192,"line":238},[190,798,799],{"class":222},"    with",[190,801,802],{"class":226}," zipfile.ZipFile(buffer, ",[190,804,805],{"class":200},"\"w\"",[190,807,808],{"class":226},", method, ",[190,810,811],{"class":462},"compresslevel",[190,813,297],{"class":222},[190,815,816],{"class":226},"level) ",[190,818,267],{"class":222},[190,820,821],{"class":226}," archive:\n",[190,823,824],{"class":192,"line":252},[190,825,826],{"class":226},"        archive.writestr(name, data)\n",[190,828,829,831],{"class":192,"line":259},[190,830,547],{"class":222},[190,832,833],{"class":226}," buffer.getvalue()\n",[190,835,836],{"class":192,"line":273},[190,837,256],{"emptyLinePlaceholder":255},[190,839,840,843,845,848,850],{"class":192,"line":286},[190,841,842],{"class":226},"workbook ",[190,844,297],{"class":222},[190,846,847],{"class":226}," Path(",[190,849,457],{"class":200},[190,851,852],{"class":226},").read_bytes()\n",[190,854,855,858,860,863,865],{"class":192,"line":291},[190,856,857],{"class":226},"zipped ",[190,859,297],{"class":222},[190,861,862],{"class":226}," zip_bytes(",[190,864,457],{"class":200},[190,866,867],{"class":226},", workbook)\n",[190,869,870,872,874,876,878,881,884,887,890,892,894,897,900,903],{"class":192,"line":309},[190,871,476],{"class":303},[190,873,412],{"class":226},[190,875,481],{"class":303},[190,877,412],{"class":226},[190,879,880],{"class":303},"1",[190,882,883],{"class":222}," -",[190,885,886],{"class":303}," len",[190,888,889],{"class":226},"(zipped) ",[190,891,492],{"class":222},[190,893,886],{"class":303},[190,895,896],{"class":226},"(workbook), ",[190,898,899],{"class":303},"3",[190,901,902],{"class":226},"))       ",[190,904,905],{"class":584},"# a few percent at most\n",[10,907,908,909,911],{},"On the 200,000-row test workbook, plain zipping saved 3.7% and the slower LZMA method 9%. Neither turns a message that is too large into one that fits, unless it was only just over. The one workbook-level gain that is free is repacking: rewriting the workbook's own parts at the maximum compression level. It saved 5% on the test file, and the result is still a normal ",[14,910,16],{},":",[181,913,915],{"className":213,"code":914,"language":215,"meta":186,"style":186},"def repack_xlsx(src, dst):\n    with zipfile.ZipFile(src) as original, \\\n         zipfile.ZipFile(dst, \"w\", zipfile.ZIP_DEFLATED, compresslevel=9) as smaller:\n        for item in original.infolist():\n            smaller.writestr(item.filename, original.read(item.filename))\n\nrepack_xlsx(\"export.xlsx\", \"export_small.xlsx\")\nprint(pd.read_excel(\"export_small.xlsx\").shape)        # (50000, 4) — still readable\n",[14,916,917,927,939,966,979,984,988,1002],{"__ignoreMap":186},[190,918,919,921,924],{"class":192,"line":193},[190,920,535],{"class":222},[190,922,923],{"class":538}," repack_xlsx",[190,925,926],{"class":226},"(src, dst):\n",[190,928,929,931,934,936],{"class":192,"line":230},[190,930,799],{"class":222},[190,932,933],{"class":226}," zipfile.ZipFile(src) ",[190,935,267],{"class":222},[190,937,938],{"class":226}," original, \\\n",[190,940,941,944,946,949,951,953,955,957,959,961,963],{"class":192,"line":238},[190,942,943],{"class":226},"         zipfile.ZipFile(dst, ",[190,945,805],{"class":200},[190,947,948],{"class":226},", zipfile.",[190,950,773],{"class":303},[190,952,171],{"class":226},[190,954,811],{"class":462},[190,956,297],{"class":222},[190,958,781],{"class":303},[190,960,564],{"class":226},[190,962,267],{"class":222},[190,964,965],{"class":226}," smaller:\n",[190,967,968,971,974,976],{"class":192,"line":252},[190,969,970],{"class":222},"        for",[190,972,973],{"class":226}," item ",[190,975,406],{"class":222},[190,977,978],{"class":226}," original.infolist():\n",[190,980,981],{"class":192,"line":259},[190,982,983],{"class":226},"            smaller.writestr(item.filename, original.read(item.filename))\n",[190,985,986],{"class":192,"line":273},[190,987,256],{"emptyLinePlaceholder":255},[190,989,990,993,995,997,1000],{"class":192,"line":286},[190,991,992],{"class":226},"repack_xlsx(",[190,994,457],{"class":200},[190,996,171],{"class":226},[190,998,999],{"class":200},"\"export_small.xlsx\"",[190,1001,306],{"class":226},[190,1003,1004,1006,1009,1011,1014],{"class":192,"line":291},[190,1005,476],{"class":303},[190,1007,1008],{"class":226},"(pd.read_excel(",[190,1010,999],{"class":200},[190,1012,1013],{"class":226},").shape)        ",[190,1015,1016],{"class":584},"# (50000, 4) — still readable\n",[25,1018,34,1024,34,1027,34,1030,34,1035,34,1042,34,1053,34,1058,34,1063,34,1068,34,1075,34,1080,34,1084,34,1087,34,1094,34,1099],{"viewBox":1019,"role":28,"ariaLabelledBy":1020,"xmlns":32,"style":1023},"84 0 592 331",[1021,1022],"zp-layers-t","zp-layers-d","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[36,1025,1026],{"id":1021},"Why a zipped workbook stays the same size",[40,1028,1029],{"id":1022},"The xlsx file is already a zip archive containing deflate-compressed XML parts. Zipping it again wraps compressed data in another compression layer, which finds almost nothing left to remove. Text such as CSV has not been compressed yet, so zipping it removes most of its size.",[44,1031],{"x":1032,"y":47,"width":1033,"height":1034,"fill":50},"84","592","331",[44,1036],{"x":1037,"y":54,"width":1038,"height":1039,"rx":1040,"fill":88,"stroke":89,"style":1041},"100.0","560.0","93.0","12","stroke-width:2px",[1043,1044,1046],"g",{"transform":1045},"translate(368.0,29.0) scale(1.000)",[1047,1048],"path",{"d":1049,"fill":1050,"stroke":1051,"style":1052},"M12 2.5l8.5 4.7v9.6L12 21.5l-8.5-4.7V7.2z M3.5 7.2l8.5 4.7 8.5-4.7 M12 11.9v9.6","none","var(--gold-ink,#7a4e06)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[62,1054,1057],{"x":156,"y":1055,"style":1056},"75.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Outer zip (your attachment)",[62,1059,1062],{"x":156,"y":1060,"style":1061},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","finds little to compress",[44,1064],{"x":1037,"y":1065,"width":1038,"height":1039,"rx":1040,"fill":1066,"stroke":1067,"style":1041},"119.0","#f0f4ff","var(--brand,#5b5cf0)",[1043,1069,1071],{"transform":1070},"translate(368.0,132.0) scale(1.000)",[1047,1072],{"d":1073,"fill":1050,"stroke":1074,"style":1052},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","var(--brand-strong,#4338ca)",[62,1076,1079],{"x":156,"y":1077,"style":1078},"178.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","xlsx container (already a zip)",[62,1081,1083],{"x":156,"y":1082,"style":1061},"198.0","deflate-compressed parts",[44,1085],{"x":1037,"y":1086,"width":1038,"height":1039,"rx":1040,"fill":147,"stroke":148,"style":1041},"222.0",[1043,1088,1090],{"transform":1089},"translate(368.0,235.0) scale(1.000)",[1047,1091],{"d":1092,"fill":1050,"stroke":1093,"style":1052},"M9 8l-4 4 4 4 M15 8l4 4-4 4","var(--teal-ink,#0b6157)",[62,1095,1098],{"x":156,"y":1096,"style":1097},"281.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","sheet XML (60 MB uncompressed)",[62,1100,1102],{"x":156,"y":1101,"style":1061},"301.0","compressed inside the xlsx",[161,1104,1106],{"id":1105},"step-3-send-raw-data-as-a-zipped-csv","Step 3 — Send raw data as a zipped CSV",[10,1108,1109],{},"When the attachment is a data export rather than a formatted report — no charts, no styling, just rows — CSV in a zip is dramatically smaller. The same 200,000 rows were 13.96 MB as CSV and 2.93 MB zipped, a third of the workbook's size:",[181,1111,1113],{"className":213,"code":1112,"language":215,"meta":186,"style":186},"csv_bytes = frame.to_csv(index=False).encode(\"utf-8\")\nbundle = zip_bytes(\"export.csv\", csv_bytes)\nPath(\"export_csv.zip\").write_bytes(bundle)\nprint(round(len(bundle) \u002F len(workbook), 2), \"× the workbook size\")\n",[14,1114,1115,1139,1154,1165],{"__ignoreMap":186},[190,1116,1117,1120,1122,1125,1127,1129,1131,1134,1137],{"class":192,"line":193},[190,1118,1119],{"class":226},"csv_bytes ",[190,1121,297],{"class":222},[190,1123,1124],{"class":226}," frame.to_csv(",[190,1126,463],{"class":462},[190,1128,297],{"class":222},[190,1130,468],{"class":303},[190,1132,1133],{"class":226},").encode(",[190,1135,1136],{"class":200},"\"utf-8\"",[190,1138,306],{"class":226},[190,1140,1141,1144,1146,1148,1151],{"class":192,"line":230},[190,1142,1143],{"class":226},"bundle ",[190,1145,297],{"class":222},[190,1147,862],{"class":226},[190,1149,1150],{"class":200},"\"export.csv\"",[190,1152,1153],{"class":226},", csv_bytes)\n",[190,1155,1156,1159,1162],{"class":192,"line":238},[190,1157,1158],{"class":226},"Path(",[190,1160,1161],{"class":200},"\"export_csv.zip\"",[190,1163,1164],{"class":226},").write_bytes(bundle)\n",[190,1166,1167,1169,1171,1173,1175,1178,1181,1183,1185,1187,1189,1191,1194],{"class":192,"line":252},[190,1168,476],{"class":303},[190,1170,412],{"class":226},[190,1172,481],{"class":303},[190,1174,412],{"class":226},[190,1176,1177],{"class":303},"len",[190,1179,1180],{"class":226},"(bundle) ",[190,1182,492],{"class":222},[190,1184,886],{"class":303},[190,1186,896],{"class":226},[190,1188,439],{"class":303},[190,1190,502],{"class":226},[190,1192,1193],{"class":200},"\"× the workbook size\"",[190,1195,306],{"class":226},[10,1197,1198,1199,740],{},"The trade-off is what CSV loses: types, formatting, several sheets, formulas. Recipients opening it in Excel may see dates and leading zeros mangled by Excel's import. It suits data handed to another system or an analyst, not a finished report for managers. Converting in the other direction is covered in ",[19,1200,1202],{"href":1201},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fconvert-excel-to-csv-with-python\u002F","Convert Excel to CSV with Python",[161,1204,1206],{"id":1205},"step-4-several-files-pack-them-into-messages-under-the-budget","Step 4 — Several files: pack them into messages under the budget",[10,1208,1209],{},"With a batch of regional workbooks, zipping them together helps a little and bundling them into as few messages as fit helps more. A simple first-fit packing keeps every message under the budget:",[181,1211,1213],{"className":213,"code":1212,"language":215,"meta":186,"style":186},"def pack_into_messages(paths, budget=LIMIT - HEADROOM):\n    messages = []\n    for path in sorted(paths, key=lambda p: Path(p).stat().st_size, reverse=True):\n        size = encoded_size(Path(path).stat().st_size)\n        if size > budget:\n            raise ValueError(f\"{path} alone exceeds the budget; link to it instead\")\n        for message in messages:\n            if message[\"size\"] + size \u003C= budget:\n                message[\"files\"].append(path)\n                message[\"size\"] += size\n                break\n        else:\n            messages.append({\"files\": [path], \"size\": size})\n    return messages\n\nfor region in [\"North\", \"South\", \"East\"]:\n    frame[frame[\"region\"] == region].to_excel(f\"export_{region}.xlsx\", index=False)\nplan = pack_into_messages([f\"export_{r}.xlsx\" for r in [\"North\", \"South\", \"East\"]], budget=900_000)\nprint([m[\"files\"] for m in plan])\n# [['export_South.xlsx', 'export_East.xlsx'], ['export_North.xlsx']]\n",[14,1214,1215,1235,1245,1280,1290,1304,1329,1341,1364,1375,1389,1394,1402,1417,1424,1428,1453,1494,1550,1572],{"__ignoreMap":186},[190,1216,1217,1219,1222,1225,1227,1229,1231,1233],{"class":192,"line":193},[190,1218,535],{"class":222},[190,1220,1221],{"class":538}," pack_into_messages",[190,1223,1224],{"class":226},"(paths, budget",[190,1226,297],{"class":222},[190,1228,594],{"class":303},[190,1230,883],{"class":222},[190,1232,677],{"class":303},[190,1234,784],{"class":226},[190,1236,1237,1240,1242],{"class":192,"line":230},[190,1238,1239],{"class":226},"    messages ",[190,1241,297],{"class":222},[190,1243,1244],{"class":226}," []\n",[190,1246,1247,1250,1253,1255,1258,1261,1264,1267,1270,1273,1275,1278],{"class":192,"line":238},[190,1248,1249],{"class":222},"    for",[190,1251,1252],{"class":226}," path ",[190,1254,406],{"class":222},[190,1256,1257],{"class":303}," sorted",[190,1259,1260],{"class":226},"(paths, ",[190,1262,1263],{"class":462},"key",[190,1265,1266],{"class":222},"=lambda",[190,1268,1269],{"class":226}," p: Path(p).stat().st_size, ",[190,1271,1272],{"class":462},"reverse",[190,1274,297],{"class":222},[190,1276,1277],{"class":303},"True",[190,1279,784],{"class":226},[190,1281,1282,1285,1287],{"class":192,"line":252},[190,1283,1284],{"class":226},"        size ",[190,1286,297],{"class":222},[190,1288,1289],{"class":226}," encoded_size(Path(path).stat().st_size)\n",[190,1291,1292,1295,1298,1301],{"class":192,"line":259},[190,1293,1294],{"class":222},"        if",[190,1296,1297],{"class":226}," size ",[190,1299,1300],{"class":222},">",[190,1302,1303],{"class":226}," budget:\n",[190,1305,1306,1309,1312,1314,1316,1318,1320,1322,1324,1327],{"class":192,"line":273},[190,1307,1308],{"class":222},"            raise",[190,1310,1311],{"class":303}," ValueError",[190,1313,412],{"class":226},[190,1315,378],{"class":222},[190,1317,397],{"class":200},[190,1319,385],{"class":384},[190,1321,1047],{"class":226},[190,1323,394],{"class":384},[190,1325,1326],{"class":200}," alone exceeds the budget; link to it instead\"",[190,1328,306],{"class":226},[190,1330,1331,1333,1336,1338],{"class":192,"line":286},[190,1332,970],{"class":222},[190,1334,1335],{"class":226}," message ",[190,1337,406],{"class":222},[190,1339,1340],{"class":226}," messages:\n",[190,1342,1343,1346,1349,1352,1355,1357,1359,1362],{"class":192,"line":291},[190,1344,1345],{"class":222},"            if",[190,1347,1348],{"class":226}," message[",[190,1350,1351],{"class":200},"\"size\"",[190,1353,1354],{"class":226},"] ",[190,1356,567],{"class":222},[190,1358,1297],{"class":226},[190,1360,1361],{"class":222},"\u003C=",[190,1363,1303],{"class":226},[190,1365,1366,1369,1372],{"class":192,"line":309},[190,1367,1368],{"class":226},"                message[",[190,1370,1371],{"class":200},"\"files\"",[190,1373,1374],{"class":226},"].append(path)\n",[190,1376,1377,1379,1381,1383,1386],{"class":192,"line":320},[190,1378,1368],{"class":226},[190,1380,1351],{"class":200},[190,1382,1354],{"class":226},[190,1384,1385],{"class":222},"+=",[190,1387,1388],{"class":226}," size\n",[190,1390,1391],{"class":192,"line":331},[190,1392,1393],{"class":222},"                break\n",[190,1395,1396,1399],{"class":192,"line":340},[190,1397,1398],{"class":222},"        else",[190,1400,1401],{"class":226},":\n",[190,1403,1404,1407,1409,1412,1414],{"class":192,"line":370},[190,1405,1406],{"class":226},"            messages.append({",[190,1408,1371],{"class":200},[190,1410,1411],{"class":226},": [path], ",[190,1413,1351],{"class":200},[190,1415,1416],{"class":226},": size})\n",[190,1418,1419,1421],{"class":192,"line":421},[190,1420,547],{"class":222},[190,1422,1423],{"class":226}," messages\n",[190,1425,1426],{"class":192,"line":445},[190,1427,256],{"emptyLinePlaceholder":255},[190,1429,1430,1432,1435,1437,1440,1442,1444,1446,1448,1450],{"class":192,"line":451},[190,1431,657],{"class":222},[190,1433,1434],{"class":226}," region ",[190,1436,406],{"class":222},[190,1438,1439],{"class":226}," [",[190,1441,349],{"class":200},[190,1443,171],{"class":226},[190,1445,354],{"class":200},[190,1447,171],{"class":226},[190,1449,359],{"class":200},[190,1451,1452],{"class":226},"]:\n",[190,1454,1455,1458,1461,1463,1466,1469,1471,1474,1476,1479,1481,1484,1486,1488,1490,1492],{"class":192,"line":473},[190,1456,1457],{"class":226},"    frame[frame[",[190,1459,1460],{"class":200},"\"region\"",[190,1462,1354],{"class":226},[190,1464,1465],{"class":222},"==",[190,1467,1468],{"class":226}," region].to_excel(",[190,1470,378],{"class":222},[190,1472,1473],{"class":200},"\"export_",[190,1475,385],{"class":384},[190,1477,1478],{"class":226},"region",[190,1480,394],{"class":384},[190,1482,1483],{"class":200},".xlsx\"",[190,1485,171],{"class":226},[190,1487,463],{"class":462},[190,1489,297],{"class":222},[190,1491,468],{"class":303},[190,1493,306],{"class":226},[190,1495,1497,1500,1502,1505,1507,1509,1511,1514,1516,1518,1520,1523,1525,1527,1529,1531,1533,1535,1537,1540,1543,1545,1548],{"class":192,"line":1496},18,[190,1498,1499],{"class":226},"plan ",[190,1501,297],{"class":222},[190,1503,1504],{"class":226}," pack_into_messages([",[190,1506,378],{"class":222},[190,1508,1473],{"class":200},[190,1510,385],{"class":384},[190,1512,1513],{"class":226},"r",[190,1515,394],{"class":384},[190,1517,1483],{"class":200},[190,1519,400],{"class":222},[190,1521,1522],{"class":226}," r ",[190,1524,406],{"class":222},[190,1526,1439],{"class":226},[190,1528,349],{"class":200},[190,1530,171],{"class":226},[190,1532,354],{"class":200},[190,1534,171],{"class":226},[190,1536,359],{"class":200},[190,1538,1539],{"class":226},"]], ",[190,1541,1542],{"class":462},"budget",[190,1544,297],{"class":222},[190,1546,1547],{"class":303},"900_000",[190,1549,306],{"class":226},[190,1551,1553,1555,1558,1560,1562,1564,1567,1569],{"class":192,"line":1552},19,[190,1554,476],{"class":303},[190,1556,1557],{"class":226},"([m[",[190,1559,1371],{"class":200},[190,1561,1354],{"class":226},[190,1563,657],{"class":222},[190,1565,1566],{"class":226}," m ",[190,1568,406],{"class":222},[190,1570,1571],{"class":226}," plan])\n",[190,1573,1575],{"class":192,"line":1574},20,[190,1576,1577],{"class":584},"# [['export_South.xlsx', 'export_East.xlsx'], ['export_North.xlsx']]\n",[10,1579,1580,1581,740],{},"Each regional workbook is about 0.3 MB, 0.4 MB once encoded, so with a 0.9 MB budget two fit in the first message and the third goes in a second. Largest-first ordering keeps the number of messages low without any clever optimisation. Label split messages \"part 1 of 2\" in the subject so recipients know to expect the rest. Attaching several files to one message is covered in ",[19,1582,1584],{"href":1583},"\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",[161,1586,1588],{"id":1587},"step-5-know-when-to-stop-attaching","Step 5 — Know when to stop attaching",[10,1590,1591,1592,1594,1595,740],{},"Every option above has a ceiling. Beyond it — or when the data is confidential — upload the file to shared storage and email a link: recipients get the current version, access can be revoked, and nothing large travels through mail servers. Encrypted zips are a weak substitute: Python's ",[14,1593,170],{}," cannot write them, third-party AES zips need the recipient to have a tool that opens them, and the password still has to be sent somehow. Uploading reports is covered in ",[19,1596,1598],{"href":1597},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-azure-blob-storage\u002F","Upload an Excel Report to Azure Blob Storage with Python",[25,1600,34,1606,34,1609,34,1612,34,1616,34,1620,34,1625,34,1628,34,1632,34,1636,34,1639,34,1644,34,1652,34,1656,34,1661,34,1665,34,1669,34,1672,34,1675,34,1679,34,1682,34,1685,34,1688,34,1691,34,1694,34,1697,34,1701,34,1704,34,1707,34,1710,34,1713,34,1715,34,1718,34,1722,34,1725,34,1730,34,1734],{"viewBox":1601,"role":28,"ariaLabelledBy":1602,"xmlns":32,"style":1605},"4 0 752 238",[1603,1604],"zp-choose-t","zp-choose-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[36,1607,1608],{"id":1603},"Choosing how to send a large report",[40,1610,1611],{"id":1604},"If the report fits the encoded budget, attach it as it is, repacked for a few percent. If it is raw data, send CSV inside a zip. If there are several files, pack them into as few messages as fit. If a single file is still too large or the content is confidential, upload it and send a link.",[44,1613],{"x":46,"y":47,"width":1614,"height":1615,"fill":50},"752","238",[44,1617],{"x":1618,"y":54,"width":1619,"height":1055,"rx":1040,"fill":1066,"stroke":1067,"style":1041},"231.0","298.1",[1043,1621,1622],{"transform":1045},[1047,1623],{"d":1624,"fill":1050,"stroke":1074,"style":1052},"M3 6h18v12H3z M3 7l9 6 9-6",[62,1626,1627],{"x":156,"y":1055,"style":1078},"What is being sent, and does it fit?",[192,1629],{"x1":156,"y1":1630,"x2":156,"y2":1631,"stroke":1067,"style":1041},"91.5","111.5",[192,1633],{"x1":1634,"y1":1631,"x2":1635,"y2":1631,"stroke":1067,"style":1041},"104.0","656.0",[192,1637],{"x1":1634,"y1":1631,"x2":1634,"y2":1638,"stroke":1067,"style":1041},"157.5",[1640,1641],"polygon",{"points":1642,"fill":1643},"104.0,161.5 99.0,152.5 109.0,152.5","#5b5cf0",[44,1645],{"x":1646,"y":1647,"width":1648,"height":1649,"rx":1650,"fill":50,"stroke":59,"style":1651},"51.9","121.5","104.1","21.0","10","stroke-width:1.2px",[62,1653,1655],{"x":1634,"y":111,"style":1654},"font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","fits the budget",[44,1657],{"x":53,"y":1658,"width":1659,"height":1660,"rx":1040,"fill":1066,"stroke":1067,"style":1041},"163.5","168.0","59.0",[62,1662,1664],{"x":1634,"y":1663,"style":1078},"189.0","attach as is",[62,1666,1668],{"x":1634,"y":1667,"style":1061},"208.5","repack for a few %",[192,1670],{"x1":1671,"y1":1631,"x2":1671,"y2":1638,"stroke":1067,"style":1041},"288.0",[1640,1673],{"points":1674,"fill":1643},"288.0,161.5 283.0,152.5 293.0,152.5",[44,1676],{"x":1677,"y":1647,"width":1678,"height":1649,"rx":1650,"fill":50,"stroke":59,"style":1651},"232.7","110.5",[62,1680,1681],{"x":1671,"y":111,"style":1654},"raw data export",[44,1683],{"x":1684,"y":1658,"width":1659,"height":1660,"rx":1040,"fill":147,"stroke":148,"style":1041},"204.0",[62,1686,1687],{"x":1671,"y":1663,"style":1097},"CSV in a zip",[62,1689,1690],{"x":1671,"y":1667,"style":1061},"about a third the size",[192,1692],{"x1":1693,"y1":1631,"x2":1693,"y2":1638,"stroke":1067,"style":1041},"472.0",[1640,1695],{"points":1696,"fill":1643},"472.0,161.5 467.0,152.5 477.0,152.5",[44,1698],{"x":1699,"y":1647,"width":1700,"height":1649,"rx":1650,"fill":50,"stroke":59,"style":1651},"427.1","89.8",[62,1702,1703],{"x":1693,"y":111,"style":1654},"several files",[44,1705],{"x":1706,"y":1658,"width":1659,"height":1660,"rx":1040,"fill":88,"stroke":89,"style":1041},"388.0",[62,1708,1709],{"x":1693,"y":1663,"style":1056},"pack into messages",[62,1711,1712],{"x":1693,"y":1667,"style":1061},"part 1 of n",[192,1714],{"x1":1635,"y1":1631,"x2":1635,"y2":1638,"stroke":1067,"style":1041},[1640,1716],{"points":1717,"fill":1643},"656.0,161.5 651.0,152.5 661.0,152.5",[44,1719],{"x":1720,"y":1647,"width":1721,"height":1649,"rx":1650,"fill":50,"stroke":59,"style":1651},"582.5","147.0",[62,1723,1724],{"x":1635,"y":111,"style":1654},"too big or confidential",[44,1726],{"x":1727,"y":1658,"width":1659,"height":1660,"rx":1040,"fill":1728,"stroke":1729,"style":1041},"572.0","#fee8f2","var(--accent,#f43f8f)",[62,1731,1733],{"x":1635,"y":1663,"style":1732},"font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","upload and link",[62,1735,1736],{"x":1635,"y":1667,"style":1061},"access control",[161,1738,1740],{"id":1739},"common-pitfalls","Common pitfalls",[1742,1743,1744,1756,1762,1768,1774,1784,1794],"ul",{},[1745,1746,1747,1751,1752,1755],"li",{},[1748,1749,1750],"strong",{},"Zip attachments blocked."," Many corporate gateways strip or quarantine ",[14,1753,1754],{},".zip"," files. Confirm with the recipients before switching to zips.",[1745,1757,1758,1761],{},[1748,1759,1760],{},"Measuring the file, not the message."," A 20 MB file is a 27 MB message. Budget with the encoded size.",[1745,1763,1764,1767],{},[1748,1765,1766],{},"Shrinking by deleting."," Dropping columns or rows to fit a limit changes the report. Split or link instead, and say so in the email.",[1745,1769,1770,1773],{},[1748,1771,1772],{},"Forgetting the recipient's side."," Your server may accept 35 MB while the recipient's gateway rejects anything over 10 MB, and that bounce arrives hours later, after the job has reported success. When the recipients are outside your organisation, use the smallest limit you have seen from them.",[1745,1775,1776,1779,1780,1783],{},[1748,1777,1778],{},"Repacking with the wrong entry order."," Some strict readers expect ",[14,1781,1782],{},"[Content_Types].xml"," to be the first entry in the archive. The repacking loop above copies entries in their original order, which keeps it first; do not sort or filter the entries while repacking.",[1745,1785,1786,1789,1790,1793],{},[1748,1787,1788],{},"Zipping with the operating system's defaults."," A zip built by a shell tool may use a storing method that does not compress at all, or add hidden folders such as ",[14,1791,1792],{},"__MACOSX",". Building the archive in Python keeps the method and contents under your control.",[1745,1795,1796,1799],{},[1748,1797,1798],{},"Checking the size once."," Reports grow month by month. Run the budget check on every send and log the encoded size, so the log shows the trend before the first bounce does.",[161,1801,1803],{"id":1802},"conclusion","Conclusion",[10,1805,1806,1807,1809],{},"Zipping an ",[14,1808,16],{}," saves a few percent because the workbook is already compressed; repacking it at maximum compression saves a little more. Budget attachments by their base64-encoded size, send pure data exports as zipped CSV — a third of the workbook's size in our test — pack several files into as few messages as fit, and switch to a shared link once files outgrow email.",[161,1811,1813],{"id":1812},"frequently-asked-questions","Frequently asked questions",[10,1815,1816,1819],{},[1748,1817,1818],{},"Does zipping an Excel file make it smaller?","\nOnly slightly. An .xlsx is itself a zip archive of compressed XML, so zipping it again saved about 4% on an 8.6 MB workbook in our test. Zipping helps much more for several files together or for CSV exports.",[10,1821,1822,1825],{},[1748,1823,1824],{},"How large can an email attachment be?","\nCommon limits are around 20 to 25 MB for the whole message, and attachments are base64-encoded, which adds about a third to their size. In practice, keep the raw attachments under roughly 18 MB for a 25 MB limit.",[10,1827,1828,1831],{},[1748,1829,1830],{},"What is the most effective way to shrink a large data export?","\nSend the data as CSV inside a zip. The same 200,000 rows that made an 8.6 MB workbook compressed to 2.9 MB as a zipped CSV, because CSV text compresses far better than a workbook that is already compressed.",[10,1833,1834,1837],{},[1748,1835,1836],{},"Can Python create a password-protected zip?","\nThe standard zipfile module can read but not write encrypted archives. A third-party package such as pyzipper can write AES-encrypted zips; alternatively share the file through a service with access control instead of email.",[161,1839,1841],{"id":1840},"related","Related",[1742,1843,1844,1850,1857,1864],{},[1745,1845,1846,1847,1849],{},"Up one level: ",[19,1848,22],{"href":21}," — sending reports by email.",[1745,1851,1852,1856],{},[19,1853,1855],{"href":1854},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Freduce-memory-when-reading-excel-with-pandas\u002F","Reduce Memory When Reading Excel with pandas"," — the same 200,000-row test file, read efficiently.",[1745,1858,1859,1863],{},[19,1860,1862],{"href":1861},"\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"," — putting the key figures in the message itself.",[1745,1865,1866,1870],{},[19,1867,1869],{"href":1868},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-an-excel-report-with-microsoft-graph-api\u002F","Send an Excel Report with Microsoft Graph API"," — Graph's own limits for large attachments.",[1872,1873,1874],"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_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":186,"searchDepth":230,"depth":230,"links":1876},[1877,1878,1879,1880,1881,1882,1883,1884,1885,1886],{"id":163,"depth":230,"text":164},{"id":510,"depth":230,"text":511},{"id":743,"depth":230,"text":744},{"id":1105,"depth":230,"text":1106},{"id":1205,"depth":230,"text":1206},{"id":1587,"depth":230,"text":1588},{"id":1739,"depth":230,"text":1740},{"id":1802,"depth":230,"text":1803},{"id":1812,"depth":230,"text":1813},{"id":1840,"depth":230,"text":1841},"2026-09-23","An .xlsx is already a zip file, so zipping it saves only a few percent — measured here at 4% on an 8.6 MB workbook. Find out what actually shrinks a report enough to email: size budgets that include base64 overhead, repacking at maximum compression, CSV inside a zip for raw data, and splitting attachments across messages.","md",[1891,1893,1895,1897],{"q":1818,"a":1892},"Only slightly. An .xlsx is itself a zip archive of compressed XML, so zipping it again saved about 4% on an 8.6 MB workbook in our test. Zipping helps much more for several files together or for CSV exports.",{"q":1824,"a":1894},"Common limits are around 20 to 25 MB for the whole message, and attachments are base64-encoded, which adds about a third to their size. In practice, keep the raw attachments under roughly 18 MB for a 25 MB limit.",{"q":1830,"a":1896},"Send the data as CSV inside a zip. The same 200,000 rows that made an 8.6 MB workbook compressed to 2.9 MB as a zipped CSV, because CSV text compresses far better than a workbook that is already compressed.",{"q":1836,"a":1898},"The standard zipfile module can read but not write encrypted archives. A third-party package such as pyzipper can write AES-encrypted zips; alternatively share the file through a service with access control instead of email.","zip large excel attachments before emailing them",null,{"breadcrumb":1902},[1903,1905,1908],{"name":1904,"item":492},"Home",{"name":1906,"item":1907},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":22,"item":21},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fzip-large-excel-attachments-before-emailing",{"title":1911,"description":1912},"Zip Large Excel Attachments Before Emailing (What Actually Helps)","Make large Excel reports small enough to email with Python — why zipping an xlsx saves little, base64 size budgets for Gmail and Outlook limits, repacking at compresslevel 9, zipped CSV for data exports, packing files into several messages and when to send a link.","zip-large-excel-attachments-before-emailing","automating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fzip-large-excel-attachments-before-emailing\u002Findex",[1916,1917],"email-delivery","large-files","how-to","Vh_KK4JPT5cX6MWgkOlJchCBSKDn7dypCwo7jzaTRkY",[1921,1925],{"title":1922,"path":1923,"stem":1924,"children":-1},"Test Report Emails with a Local SMTP Server","\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Ftest-report-emails-with-a-local-smtp-server","automating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Ftest-report-emails-with-a-local-smtp-server\u002Findex",{"title":1926,"path":1927,"stem":1928,"children":-1},"Error Handling and Logging in Excel Automation","\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation","automating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Findex",[1930,1932],{"path":1931,"title":22},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib",{"path":1933,"title":1906},"\u002Fautomating-reporting-workflows",[],[1936,1939,1941,1944,1947,1949],{"path":1937,"title":1938,"kind":1918},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-credit-control-dunning-letter-run","Build a Credit Control Dunning Letter Run",{"path":1940,"title":1584,"kind":1918},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fattach-multiple-excel-files-to-one-email-in-python",{"path":1942,"title":1943,"kind":1918},"\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":1945,"title":1946,"kind":1918},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Femail-a-download-link-instead-of-an-excel-attachment","Email a Download Link Instead of an Excel Attachment",{"path":1948,"title":1862,"kind":1918},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Femail-an-excel-report-with-an-html-summary-body",{"path":1950,"title":1951,"kind":1918},"\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",1790972129999]