[{"data":1,"prerenderedAt":1765},["ShallowReactive",2],{"navigation_docs":3,"-sending-router-and-streams":191,"-sending-router-and-streams-surround":1760},[4,31,67,103,129,155,175],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":30},"Getting started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,20,25],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Quick start","\u002Fgetting-started\u002Fquick-start","1.getting-started\u002F3.quick-start","i-lucide-play",{"title":26,"path":27,"stem":28,"icon":29},"Agent Skill","\u002Fgetting-started\u002Fagent-skill","1.getting-started\u002F4.agent-skill","i-lucide-sparkles",false,{"title":32,"icon":33,"path":34,"stem":35,"children":36,"page":30},"Authoring","i-lucide-pen-line","\u002Fauthoring","2.authoring",[37,42,47,52,57,62],{"title":38,"path":39,"stem":40,"icon":41},"Comark syntax","\u002Fauthoring\u002Fcomark-syntax","2.authoring\u002F1.comark-syntax","i-lucide-file-code",{"title":43,"path":44,"stem":45,"icon":46},"Frontmatter","\u002Fauthoring\u002Ffrontmatter","2.authoring\u002F2.frontmatter","i-lucide-heading",{"title":48,"path":49,"stem":50,"icon":51},"Modules","\u002Fauthoring\u002Fmodules","2.authoring\u002F3.modules","i-lucide-blocks",{"title":53,"path":54,"stem":55,"icon":56},"Merge tags and bindings","\u002Fauthoring\u002Fmerge-tags-and-bindings","2.authoring\u002F4.merge-tags-and-bindings","i-lucide-braces",{"title":58,"path":59,"stem":60,"icon":61},"Actions and spans","\u002Fauthoring\u002Factions-and-spans","2.authoring\u002F5.actions-and-spans","i-lucide-mouse-pointer-click",{"title":63,"path":64,"stem":65,"icon":66},"Compile errors","\u002Fauthoring\u002Fcompile-errors","2.authoring\u002F6.compile-errors","i-lucide-circle-alert",{"title":68,"icon":69,"path":70,"stem":71,"children":72,"page":30},"Rendering","i-lucide-layout-template","\u002Frendering","3.rendering",[73,78,83,88,93,98],{"title":74,"path":75,"stem":76,"icon":77},"Families and streams","\u002Frendering\u002Ffamilies-and-streams","3.rendering\u002F1.families-and-streams","i-lucide-users",{"title":79,"path":80,"stem":81,"icon":82},"Themes","\u002Frendering\u002Fthemes","3.rendering\u002F2.themes","i-lucide-palette",{"title":84,"path":85,"stem":86,"icon":87},"Config and the renderer","\u002Frendering\u002Fconfig-and-renderer","3.rendering\u002F3.config-and-renderer","i-lucide-settings",{"title":89,"path":90,"stem":91,"icon":92},"Layout and spacing","\u002Frendering\u002Flayout-and-spacing","3.rendering\u002F4.layout-and-spacing","i-lucide-ruler",{"title":94,"path":95,"stem":96,"icon":97},"HTML output","\u002Frendering\u002Fhtml-output","3.rendering\u002F5.html-output","i-lucide-code-xml",{"title":99,"path":100,"stem":101,"icon":102},"Plain text","\u002Frendering\u002Fplain-text","3.rendering\u002F6.plain-text","i-lucide-file-text",{"title":104,"icon":105,"path":106,"stem":107,"children":108,"page":30},"Sending","i-lucide-send","\u002Fsending","4.sending",[109,114,119,124],{"title":110,"path":111,"stem":112,"icon":113},"Message and MIME","\u002Fsending\u002Fmessage-and-mime","4.sending\u002F1.message-and-mime","i-lucide-mail",{"title":115,"path":116,"stem":117,"icon":118},"Router and streams","\u002Fsending\u002Frouter-and-streams","4.sending\u002F2.router-and-streams","i-lucide-route",{"title":120,"path":121,"stem":122,"icon":123},"SMTP","\u002Fsending\u002Fsmtp","4.sending\u002F3.smtp","i-lucide-server",{"title":125,"path":126,"stem":127,"icon":128},"Maps","\u002Fsending\u002Fmaps","4.sending\u002F4.maps","i-lucide-map",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":30},"Tooling","i-lucide-wrench","\u002Ftooling","5.tooling",[135,140,145,150],{"title":136,"path":137,"stem":138,"icon":139},"The lttr CLI","\u002Ftooling\u002Fcli","5.tooling\u002F1.cli","i-lucide-terminal",{"title":141,"path":142,"stem":143,"icon":144},"Live preview","\u002Ftooling\u002Flive-preview","5.tooling\u002F2.live-preview","i-lucide-eye",{"title":146,"path":147,"stem":148,"icon":149},"Screenshots","\u002Ftooling\u002Fscreenshots","5.tooling\u002F3.screenshots","i-lucide-camera",{"title":151,"path":152,"stem":153,"icon":154},"Goldens and testing","\u002Ftooling\u002Fgoldens-and-testing","5.tooling\u002F4.goldens-and-testing","i-lucide-flask-conical",{"title":156,"icon":157,"path":158,"stem":159,"children":160,"page":30},"Guides","i-lucide-book-open","\u002Fguides","6.guides",[161,166,170],{"title":162,"path":163,"stem":164,"icon":165},"The Quiet example","\u002Fguides\u002Fthe-quiet-example","6.guides\u002F1.the-quiet-example","i-lucide-mail-open",{"title":167,"path":168,"stem":169,"icon":82},"Your own family and theme","\u002Fguides\u002Fyour-own-family-and-theme","6.guides\u002F2.your-own-family-and-theme",{"title":171,"path":172,"stem":173,"icon":174},"Releasing","\u002Fguides\u002Freleasing","6.guides\u002F3.releasing","i-lucide-tag",{"title":176,"icon":177,"path":178,"stem":179,"children":180,"page":30},"Reference","i-lucide-library","\u002Freference","7.reference",[181,186],{"title":182,"path":183,"stem":184,"icon":185},"API reference","\u002Freference\u002Fapi-reference","7.reference\u002F1.api-reference","i-lucide-list-tree",{"title":187,"path":188,"stem":189,"icon":190},"Gallery","\u002Freference\u002Fgallery","7.reference\u002F2.gallery","i-lucide-images",{"id":192,"title":115,"body":193,"description":1753,"extension":1754,"links":1755,"meta":1756,"navigation":1757,"path":116,"seo":1758,"stem":117,"__hash__":1759},"docs\u002F4.sending\u002F2.router-and-streams.md",{"type":194,"value":195,"toc":1747},"minimark",[196,216,291,308,313,419,496,500,547,554,644,648,1650,1653,1683,1721,1725,1743],[197,198,199,200,204,205,208,209,212,213,215],"p",{},"A ",[201,202,203],"code",{},"Sender"," delivers a ",[201,206,207],{},"Message","; a ",[201,210,211],{},"Router"," picks which ",[201,214,203],{}," to use and runs the checks that depend on where the message is going, not on its content.",[217,218,223],"pre",{"className":219,"code":220,"language":221,"meta":222,"style":222},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","type Sender interface {\n    Send(ctx context.Context, m *Message) error\n}\n","go","",[201,224,225,244,285],{"__ignoreMap":222},[226,227,230,234,238,241],"span",{"class":228,"line":229},"line",1,[226,231,233],{"class":232},"sMK4o","type",[226,235,237],{"class":236},"sBMFI"," Sender",[226,239,240],{"class":232}," interface",[226,242,243],{"class":232}," {\n",[226,245,247,251,254,258,261,264,267,270,273,276,278,281],{"class":228,"line":246},2,[226,248,250],{"class":249},"s2Zo4","    Send",[226,252,253],{"class":232},"(",[226,255,257],{"class":256},"sHdIc","ctx",[226,259,260],{"class":236}," context",[226,262,263],{"class":232},".",[226,265,266],{"class":236},"Context",[226,268,269],{"class":232},",",[226,271,272],{"class":256}," m",[226,274,275],{"class":232}," *",[226,277,207],{"class":236},[226,279,280],{"class":232},")",[226,282,284],{"class":283},"spNyl"," error\n",[226,286,288],{"class":228,"line":287},3,[226,289,290],{"class":232},"}\n",[197,292,293,296,297,300,301,303,304,307],{},[201,294,295],{},"lttr.SMTP"," (see ",[298,299,120],"a",{"href":121},") is the one ",[201,302,203],{}," the library ships. A provider client (SES, Postmark, Mailgun, …) is just another type with a ",[201,305,306],{},"Send"," method.",[309,310,312],"h2",{"id":311},"understand-the-routers-fields","Understand the Router's fields",[217,314,316],{"className":219,"code":315,"language":221,"meta":222,"style":222},"type Router struct {\n    Streams         map[Stream]Sender\n    InternalAllowed func(addr string) bool\n    Suppressed      func(ctx context.Context, s Stream, addr string) (bool, error)\n}\n",[201,317,318,330,348,367,414],{"__ignoreMap":222},[226,319,320,322,325,328],{"class":228,"line":229},[226,321,233],{"class":232},[226,323,324],{"class":236}," Router",[226,326,327],{"class":232}," struct",[226,329,243],{"class":232},[226,331,332,336,339,342,345],{"class":228,"line":246},[226,333,335],{"class":334},"sTEyZ","    Streams         ",[226,337,338],{"class":232},"map[",[226,340,341],{"class":236},"Stream",[226,343,344],{"class":232},"]",[226,346,347],{"class":236},"Sender\n",[226,349,350,353,356,359,362,364],{"class":228,"line":287},[226,351,352],{"class":334},"    InternalAllowed ",[226,354,355],{"class":232},"func(",[226,357,358],{"class":256},"addr",[226,360,361],{"class":283}," string",[226,363,280],{"class":232},[226,365,366],{"class":283}," bool\n",[226,368,370,373,375,377,379,381,383,385,388,391,393,396,398,400,403,406,408,411],{"class":228,"line":369},4,[226,371,372],{"class":334},"    Suppressed      ",[226,374,355],{"class":232},[226,376,257],{"class":256},[226,378,260],{"class":236},[226,380,263],{"class":232},[226,382,266],{"class":236},[226,384,269],{"class":232},[226,386,387],{"class":256}," s",[226,389,390],{"class":236}," Stream",[226,392,269],{"class":232},[226,394,395],{"class":256}," addr",[226,397,361],{"class":283},[226,399,280],{"class":232},[226,401,402],{"class":232}," (",[226,404,405],{"class":283},"bool",[226,407,269],{"class":232},[226,409,410],{"class":283}," error",[226,412,413],{"class":232},")\n",[226,415,417],{"class":228,"line":416},5,[226,418,290],{"class":232},[420,421,422,468,482],"ul",{},[423,424,425,431,432,402,436,439,440,439,443,439,446,439,449,452,453,455,456,459,460,463,464,467],"li",{},[426,427,428],"strong",{},[201,429,430],{},"Streams"," maps each ",[298,433,434],{"href":75},[201,435,341],{},[201,437,438],{},"Transactional",", ",[201,441,442],{},"Security",[201,444,445],{},"Marketing",[201,447,448],{},"Outreach",[201,450,451],{},"Internal",") to the ",[201,454,203],{}," that carries it. A stream with no entry, or an entry set to ",[201,457,458],{},"nil",", cannot be sent through — ",[201,461,462],{},"Router.Send"," fails with ",[201,465,466],{},"no sender for stream \"…\"",". Streams keep reputations apart, so it is normal to point transactional and marketing mail at different providers, or the same provider under different configuration sets.",[423,469,470,475,476,478,479,481],{},[426,471,472],{},[201,473,474],{},"InternalAllowed"," reports whether an address may receive ",[201,477,451],{}," mail — staff-only mail such as ops digests. A ",[201,480,458],{}," function denies all internal mail, which is the safe default: without an allow-list, nothing meant for your own team can leak to an outside address.",[423,483,484,489,490,492,493,495],{},[426,485,486],{},[201,487,488],{},"Suppressed"," reports whether an address is on the suppression list for a stream: bounces, complaints, unsubscribes. A ",[201,491,458],{}," function suppresses nothing. An error from ",[201,494,488],{}," is returned wrapped, and the router sends nothing — a suppression check you cannot answer is not a \"send anyway.\"",[309,497,499],{"id":498},"send-through-the-router","Send through the Router",[217,501,503],{"className":219,"code":502,"language":221,"meta":222,"style":222},"func (r *Router) Send(ctx context.Context, m *Message) error\n",[201,504,505],{"__ignoreMap":222},[226,506,507,510,512,515,518,520,522,525,527,529,531,533,535,537,539,541,543,545],{"class":228,"line":229},[226,508,509],{"class":232},"func",[226,511,402],{"class":232},[226,513,514],{"class":256},"r ",[226,516,517],{"class":232},"*",[226,519,211],{"class":236},[226,521,280],{"class":232},[226,523,524],{"class":249}," Send",[226,526,253],{"class":232},[226,528,257],{"class":256},[226,530,260],{"class":236},[226,532,263],{"class":232},[226,534,266],{"class":236},[226,536,269],{"class":232},[226,538,272],{"class":256},[226,540,275],{"class":232},[226,542,207],{"class":236},[226,544,280],{"class":232},[226,546,284],{"class":283},[197,548,549,550,553],{},"The checks run in this order, and ",[426,551,552],{},"the Router never modifies the message",":",[555,556,557,565,573,601,617,637],"ol",{},[423,558,559,562,563,263],{},[201,560,561],{},"m"," must not be ",[201,564,458],{},[423,566,567,568,570,571,263],{},"The stream must have a non-nil ",[201,569,203],{}," in ",[201,572,430],{},[423,574,575,578,579,582,583,586,587,590,591,593,594,596,597,600],{},[426,576,577],{},"The recipient address is validated first"," — the same ",[201,580,581],{},"local@domain"," check ",[201,584,585],{},"MIME"," and ",[201,588,589],{},"Render"," use — before ",[201,592,474],{}," or ",[201,595,488],{}," ever see it. This matters: ",[201,598,599],{},"net\u002Fsmtp"," writes the envelope from the address as given, so if the allow-list or suppression check ran on an unvalidated address, a crafted address could pass those checks while SMTP delivered somewhere else entirely.",[423,602,603,604,439,606,609,610,613,614,263],{},"If the stream is ",[201,605,451],{},[201,607,608],{},"InternalAllowed(addr)"," must return ",[201,611,612],{},"true",", or the send fails with ",[201,615,616],{},"internal mail to {addr} is not allowed",[423,618,619,620,622,623,625,626,629,630,633,634,263],{},"If ",[201,621,488],{}," is set, it runs; a ",[201,624,612],{}," result fails with ",[201,627,628],{},"{addr}: recipient suppressed",", wrapping the exported ",[201,631,632],{},"lttr.ErrSuppressed"," — test with ",[201,635,636],{},"errors.Is(err, lttr.ErrSuppressed)",[423,638,639,640,643],{},"Only then does ",[201,641,642],{},"Streams[m.Stream].Send(ctx, m)"," run.",[309,645,647],{"id":646},"wire-it-up","Wire it up",[217,649,652],{"className":219,"code":650,"filename":651,"language":221,"meta":222,"style":222},"package main\n\nimport (\n    \"context\"\n    \"errors\"\n    \"fmt\"\n    \"net\u002Fmail\"\n\n    \"github.com\u002Fsulv-io\u002Flttr\"\n)\n\n\u002F\u002F logSender is a Sender that just prints what it would send. Swap it for\n\u002F\u002F lttr.SMTP or your provider's Sender in production.\ntype logSender struct{ label string }\n\nfunc (s logSender) Send(_ context.Context, m *lttr.Message) error {\n    fmt.Printf(\"[%s] %s -> %s: %s\\n\", s.label, m.From.Address, m.To.Address, m.Subject)\n    return nil\n}\n\nfunc main() {\n    router := &lttr.Router{\n        Streams: map[lttr.Stream]lttr.Sender{\n            lttr.Transactional: logSender{\"transactional\"},\n            lttr.Security:      logSender{\"security\"},\n            lttr.Marketing:     logSender{\"marketing\"},\n            lttr.Internal:      logSender{\"internal\"},\n            \u002F\u002F Outreach has no sender here, so Router.Send fails for it with\n            \u002F\u002F \"no sender for stream ...\".\n        },\n        InternalAllowed: func(addr string) bool {\n            return false \u002F\u002F no staff allow-list configured in this example\n        },\n        Suppressed: func(_ context.Context, s lttr.Stream, addr string) (bool, error) {\n            return isSuppressed(s, addr), nil\n        },\n    }\n\n    m := &lttr.Message{\n        Stream:  lttr.Transactional,\n        From:    mail.Address{Name: \"CaribHubs\", Address: \"orders@caribhubs.com\"},\n        To:      mail.Address{Name: \"Keisha Browne\", Address: \"keisha@example.com\"},\n        Subject: \"Your order is on its way\",\n        HTML:    \"\u003Cp>On its way.\u003C\u002Fp>\",\n        Text:    \"On its way.\",\n    }\n\n    err := router.Send(context.Background(), m)\n    switch {\n    case errors.Is(err, lttr.ErrSuppressed):\n        fmt.Println(\"recipient is suppressed for this stream; not sent\")\n    case err != nil:\n        fmt.Println(\"send failed:\", err)\n    default:\n        fmt.Println(\"sent\")\n    }\n}\n\n\u002F\u002F isSuppressed would check a real suppression list (bounces, complaints,\n\u002F\u002F unsubscribes) keyed by stream and address.\nfunc isSuppressed(lttr.Stream, string) bool { return false }\n","main.go",[201,653,654,662,668,677,688,697,707,717,722,732,737,742,749,755,775,780,827,918,927,932,937,950,971,998,1025,1050,1075,1099,1105,1111,1117,1139,1152,1157,1206,1228,1233,1239,1244,1262,1280,1326,1369,1386,1403,1420,1425,1430,1461,1469,1500,1522,1536,1561,1570,1590,1595,1600,1605,1611,1617],{"__ignoreMap":222},[226,655,656,659],{"class":228,"line":229},[226,657,658],{"class":232},"package",[226,660,661],{"class":236}," main\n",[226,663,664],{"class":228,"line":246},[226,665,667],{"emptyLinePlaceholder":666},true,"\n",[226,669,670,674],{"class":228,"line":287},[226,671,673],{"class":672},"s7zQu","import",[226,675,676],{"class":232}," (\n",[226,678,679,682,685],{"class":228,"line":369},[226,680,681],{"class":232},"    \"",[226,683,684],{"class":236},"context",[226,686,687],{"class":232},"\"\n",[226,689,690,692,695],{"class":228,"line":416},[226,691,681],{"class":232},[226,693,694],{"class":236},"errors",[226,696,687],{"class":232},[226,698,700,702,705],{"class":228,"line":699},6,[226,701,681],{"class":232},[226,703,704],{"class":236},"fmt",[226,706,687],{"class":232},[226,708,710,712,715],{"class":228,"line":709},7,[226,711,681],{"class":232},[226,713,714],{"class":236},"net\u002Fmail",[226,716,687],{"class":232},[226,718,720],{"class":228,"line":719},8,[226,721,667],{"emptyLinePlaceholder":666},[226,723,725,727,730],{"class":228,"line":724},9,[226,726,681],{"class":232},[226,728,729],{"class":236},"github.com\u002Fsulv-io\u002Flttr",[226,731,687],{"class":232},[226,733,735],{"class":228,"line":734},10,[226,736,413],{"class":232},[226,738,740],{"class":228,"line":739},11,[226,741,667],{"emptyLinePlaceholder":666},[226,743,745],{"class":228,"line":744},12,[226,746,748],{"class":747},"sHwdD","\u002F\u002F logSender is a Sender that just prints what it would send. Swap it for\n",[226,750,752],{"class":228,"line":751},13,[226,753,754],{"class":747},"\u002F\u002F lttr.SMTP or your provider's Sender in production.\n",[226,756,758,760,763,766,769,772],{"class":228,"line":757},14,[226,759,233],{"class":232},[226,761,762],{"class":236}," logSender",[226,764,765],{"class":232}," struct{",[226,767,768],{"class":334}," label ",[226,770,771],{"class":283},"string",[226,773,774],{"class":232}," }\n",[226,776,778],{"class":228,"line":777},15,[226,779,667],{"emptyLinePlaceholder":666},[226,781,783,785,787,790,793,795,797,799,802,804,806,808,810,812,814,817,819,821,823,825],{"class":228,"line":782},16,[226,784,509],{"class":232},[226,786,402],{"class":232},[226,788,789],{"class":256},"s ",[226,791,792],{"class":236},"logSender",[226,794,280],{"class":232},[226,796,524],{"class":249},[226,798,253],{"class":232},[226,800,801],{"class":256},"_",[226,803,260],{"class":236},[226,805,263],{"class":232},[226,807,266],{"class":236},[226,809,269],{"class":232},[226,811,272],{"class":256},[226,813,275],{"class":232},[226,815,816],{"class":236},"lttr",[226,818,263],{"class":232},[226,820,207],{"class":236},[226,822,280],{"class":232},[226,824,410],{"class":283},[226,826,243],{"class":232},[226,828,830,833,835,838,840,843,847,851,854,856,859,861,864,866,869,871,873,875,877,880,882,884,886,889,891,894,896,898,900,903,905,907,909,911,913,916],{"class":228,"line":829},17,[226,831,832],{"class":334},"    fmt",[226,834,263],{"class":232},[226,836,837],{"class":249},"Printf",[226,839,253],{"class":232},[226,841,842],{"class":232},"\"",[226,844,846],{"class":845},"sfazB","[",[226,848,850],{"class":849},"swJcz","%s",[226,852,853],{"class":845},"] ",[226,855,850],{"class":849},[226,857,858],{"class":845}," -> ",[226,860,850],{"class":849},[226,862,863],{"class":845},": ",[226,865,850],{"class":849},[226,867,868],{"class":334},"\\n",[226,870,842],{"class":232},[226,872,269],{"class":232},[226,874,387],{"class":334},[226,876,263],{"class":232},[226,878,879],{"class":334},"label",[226,881,269],{"class":232},[226,883,272],{"class":334},[226,885,263],{"class":232},[226,887,888],{"class":334},"From",[226,890,263],{"class":232},[226,892,893],{"class":334},"Address",[226,895,269],{"class":232},[226,897,272],{"class":334},[226,899,263],{"class":232},[226,901,902],{"class":334},"To",[226,904,263],{"class":232},[226,906,893],{"class":334},[226,908,269],{"class":232},[226,910,272],{"class":334},[226,912,263],{"class":232},[226,914,915],{"class":334},"Subject",[226,917,413],{"class":232},[226,919,921,924],{"class":228,"line":920},18,[226,922,923],{"class":672},"    return",[226,925,926],{"class":232}," nil\n",[226,928,930],{"class":228,"line":929},19,[226,931,290],{"class":232},[226,933,935],{"class":228,"line":934},20,[226,936,667],{"emptyLinePlaceholder":666},[226,938,940,942,945,948],{"class":228,"line":939},21,[226,941,509],{"class":232},[226,943,944],{"class":249}," main",[226,946,947],{"class":232},"()",[226,949,243],{"class":232},[226,951,953,956,959,962,964,966,968],{"class":228,"line":952},22,[226,954,955],{"class":334},"    router ",[226,957,958],{"class":232},":=",[226,960,961],{"class":232}," &",[226,963,816],{"class":236},[226,965,263],{"class":232},[226,967,211],{"class":236},[226,969,970],{"class":232},"{\n",[226,972,974,977,979,982,984,986,988,990,992,994,996],{"class":228,"line":973},23,[226,975,976],{"class":334},"        Streams",[226,978,553],{"class":232},[226,980,981],{"class":232}," map[",[226,983,816],{"class":236},[226,985,263],{"class":232},[226,987,341],{"class":236},[226,989,344],{"class":232},[226,991,816],{"class":236},[226,993,263],{"class":232},[226,995,203],{"class":236},[226,997,970],{"class":232},[226,999,1001,1004,1006,1008,1010,1012,1015,1017,1020,1022],{"class":228,"line":1000},24,[226,1002,1003],{"class":334},"            lttr",[226,1005,263],{"class":232},[226,1007,438],{"class":334},[226,1009,553],{"class":232},[226,1011,762],{"class":236},[226,1013,1014],{"class":232},"{",[226,1016,842],{"class":232},[226,1018,1019],{"class":845},"transactional",[226,1021,842],{"class":232},[226,1023,1024],{"class":232},"},\n",[226,1026,1028,1030,1032,1034,1036,1039,1041,1043,1046,1048],{"class":228,"line":1027},25,[226,1029,1003],{"class":334},[226,1031,263],{"class":232},[226,1033,442],{"class":334},[226,1035,553],{"class":232},[226,1037,1038],{"class":236},"      logSender",[226,1040,1014],{"class":232},[226,1042,842],{"class":232},[226,1044,1045],{"class":845},"security",[226,1047,842],{"class":232},[226,1049,1024],{"class":232},[226,1051,1053,1055,1057,1059,1061,1064,1066,1068,1071,1073],{"class":228,"line":1052},26,[226,1054,1003],{"class":334},[226,1056,263],{"class":232},[226,1058,445],{"class":334},[226,1060,553],{"class":232},[226,1062,1063],{"class":236},"     logSender",[226,1065,1014],{"class":232},[226,1067,842],{"class":232},[226,1069,1070],{"class":845},"marketing",[226,1072,842],{"class":232},[226,1074,1024],{"class":232},[226,1076,1078,1080,1082,1084,1086,1088,1090,1092,1095,1097],{"class":228,"line":1077},27,[226,1079,1003],{"class":334},[226,1081,263],{"class":232},[226,1083,451],{"class":334},[226,1085,553],{"class":232},[226,1087,1038],{"class":236},[226,1089,1014],{"class":232},[226,1091,842],{"class":232},[226,1093,1094],{"class":845},"internal",[226,1096,842],{"class":232},[226,1098,1024],{"class":232},[226,1100,1102],{"class":228,"line":1101},28,[226,1103,1104],{"class":747},"            \u002F\u002F Outreach has no sender here, so Router.Send fails for it with\n",[226,1106,1108],{"class":228,"line":1107},29,[226,1109,1110],{"class":747},"            \u002F\u002F \"no sender for stream ...\".\n",[226,1112,1114],{"class":228,"line":1113},30,[226,1115,1116],{"class":232},"        },\n",[226,1118,1120,1123,1125,1128,1130,1132,1134,1137],{"class":228,"line":1119},31,[226,1121,1122],{"class":334},"        InternalAllowed",[226,1124,553],{"class":232},[226,1126,1127],{"class":232}," func(",[226,1129,358],{"class":256},[226,1131,361],{"class":283},[226,1133,280],{"class":232},[226,1135,1136],{"class":283}," bool",[226,1138,243],{"class":232},[226,1140,1142,1145,1149],{"class":228,"line":1141},32,[226,1143,1144],{"class":672},"            return",[226,1146,1148],{"class":1147},"sfNiH"," false",[226,1150,1151],{"class":747}," \u002F\u002F no staff allow-list configured in this example\n",[226,1153,1155],{"class":228,"line":1154},33,[226,1156,1116],{"class":232},[226,1158,1160,1163,1165,1167,1169,1171,1173,1175,1177,1179,1182,1184,1186,1188,1190,1192,1194,1196,1198,1200,1202,1204],{"class":228,"line":1159},34,[226,1161,1162],{"class":334},"        Suppressed",[226,1164,553],{"class":232},[226,1166,1127],{"class":232},[226,1168,801],{"class":256},[226,1170,260],{"class":236},[226,1172,263],{"class":232},[226,1174,266],{"class":236},[226,1176,269],{"class":232},[226,1178,387],{"class":256},[226,1180,1181],{"class":236}," lttr",[226,1183,263],{"class":232},[226,1185,341],{"class":236},[226,1187,269],{"class":232},[226,1189,395],{"class":256},[226,1191,361],{"class":283},[226,1193,280],{"class":232},[226,1195,402],{"class":232},[226,1197,405],{"class":283},[226,1199,269],{"class":232},[226,1201,410],{"class":283},[226,1203,280],{"class":232},[226,1205,243],{"class":232},[226,1207,1209,1211,1214,1216,1219,1221,1223,1226],{"class":228,"line":1208},35,[226,1210,1144],{"class":672},[226,1212,1213],{"class":249}," isSuppressed",[226,1215,253],{"class":232},[226,1217,1218],{"class":334},"s",[226,1220,269],{"class":232},[226,1222,395],{"class":334},[226,1224,1225],{"class":232},"),",[226,1227,926],{"class":232},[226,1229,1231],{"class":228,"line":1230},36,[226,1232,1116],{"class":232},[226,1234,1236],{"class":228,"line":1235},37,[226,1237,1238],{"class":232},"    }\n",[226,1240,1242],{"class":228,"line":1241},38,[226,1243,667],{"emptyLinePlaceholder":666},[226,1245,1247,1250,1252,1254,1256,1258,1260],{"class":228,"line":1246},39,[226,1248,1249],{"class":334},"    m ",[226,1251,958],{"class":232},[226,1253,961],{"class":232},[226,1255,816],{"class":236},[226,1257,263],{"class":232},[226,1259,207],{"class":236},[226,1261,970],{"class":232},[226,1263,1265,1268,1270,1273,1275,1277],{"class":228,"line":1264},40,[226,1266,1267],{"class":334},"        Stream",[226,1269,553],{"class":232},[226,1271,1272],{"class":334},"  lttr",[226,1274,263],{"class":232},[226,1276,438],{"class":334},[226,1278,1279],{"class":232},",\n",[226,1281,1283,1286,1288,1291,1293,1295,1297,1300,1302,1305,1308,1310,1312,1315,1317,1319,1322,1324],{"class":228,"line":1282},41,[226,1284,1285],{"class":334},"        From",[226,1287,553],{"class":232},[226,1289,1290],{"class":236},"    mail",[226,1292,263],{"class":232},[226,1294,893],{"class":236},[226,1296,1014],{"class":232},[226,1298,1299],{"class":334},"Name",[226,1301,553],{"class":232},[226,1303,1304],{"class":232}," \"",[226,1306,1307],{"class":845},"CaribHubs",[226,1309,842],{"class":232},[226,1311,269],{"class":232},[226,1313,1314],{"class":334}," Address",[226,1316,553],{"class":232},[226,1318,1304],{"class":232},[226,1320,1321],{"class":845},"orders@caribhubs.com",[226,1323,842],{"class":232},[226,1325,1024],{"class":232},[226,1327,1329,1332,1334,1337,1339,1341,1343,1345,1347,1349,1352,1354,1356,1358,1360,1362,1365,1367],{"class":228,"line":1328},42,[226,1330,1331],{"class":334},"        To",[226,1333,553],{"class":232},[226,1335,1336],{"class":236},"      mail",[226,1338,263],{"class":232},[226,1340,893],{"class":236},[226,1342,1014],{"class":232},[226,1344,1299],{"class":334},[226,1346,553],{"class":232},[226,1348,1304],{"class":232},[226,1350,1351],{"class":845},"Keisha Browne",[226,1353,842],{"class":232},[226,1355,269],{"class":232},[226,1357,1314],{"class":334},[226,1359,553],{"class":232},[226,1361,1304],{"class":232},[226,1363,1364],{"class":845},"keisha@example.com",[226,1366,842],{"class":232},[226,1368,1024],{"class":232},[226,1370,1372,1375,1377,1379,1382,1384],{"class":228,"line":1371},43,[226,1373,1374],{"class":334},"        Subject",[226,1376,553],{"class":232},[226,1378,1304],{"class":232},[226,1380,1381],{"class":845},"Your order is on its way",[226,1383,842],{"class":232},[226,1385,1279],{"class":232},[226,1387,1389,1392,1394,1396,1399,1401],{"class":228,"line":1388},44,[226,1390,1391],{"class":334},"        HTML",[226,1393,553],{"class":232},[226,1395,681],{"class":232},[226,1397,1398],{"class":845},"\u003Cp>On its way.\u003C\u002Fp>",[226,1400,842],{"class":232},[226,1402,1279],{"class":232},[226,1404,1406,1409,1411,1413,1416,1418],{"class":228,"line":1405},45,[226,1407,1408],{"class":334},"        Text",[226,1410,553],{"class":232},[226,1412,681],{"class":232},[226,1414,1415],{"class":845},"On its way.",[226,1417,842],{"class":232},[226,1419,1279],{"class":232},[226,1421,1423],{"class":228,"line":1422},46,[226,1424,1238],{"class":232},[226,1426,1428],{"class":228,"line":1427},47,[226,1429,667],{"emptyLinePlaceholder":666},[226,1431,1433,1436,1438,1441,1443,1445,1447,1449,1451,1454,1457,1459],{"class":228,"line":1432},48,[226,1434,1435],{"class":334},"    err ",[226,1437,958],{"class":232},[226,1439,1440],{"class":334}," router",[226,1442,263],{"class":232},[226,1444,306],{"class":249},[226,1446,253],{"class":232},[226,1448,684],{"class":334},[226,1450,263],{"class":232},[226,1452,1453],{"class":249},"Background",[226,1455,1456],{"class":232},"(),",[226,1458,272],{"class":334},[226,1460,413],{"class":232},[226,1462,1464,1467],{"class":228,"line":1463},49,[226,1465,1466],{"class":672},"    switch",[226,1468,243],{"class":232},[226,1470,1472,1475,1478,1480,1483,1485,1488,1490,1492,1494,1497],{"class":228,"line":1471},50,[226,1473,1474],{"class":672},"    case",[226,1476,1477],{"class":334}," errors",[226,1479,263],{"class":232},[226,1481,1482],{"class":249},"Is",[226,1484,253],{"class":232},[226,1486,1487],{"class":334},"err",[226,1489,269],{"class":232},[226,1491,1181],{"class":334},[226,1493,263],{"class":232},[226,1495,1496],{"class":334},"ErrSuppressed",[226,1498,1499],{"class":232},"):\n",[226,1501,1503,1506,1508,1511,1513,1515,1518,1520],{"class":228,"line":1502},51,[226,1504,1505],{"class":334},"        fmt",[226,1507,263],{"class":232},[226,1509,1510],{"class":249},"Println",[226,1512,253],{"class":232},[226,1514,842],{"class":232},[226,1516,1517],{"class":845},"recipient is suppressed for this stream; not sent",[226,1519,842],{"class":232},[226,1521,413],{"class":232},[226,1523,1525,1527,1530,1533],{"class":228,"line":1524},52,[226,1526,1474],{"class":672},[226,1528,1529],{"class":334}," err ",[226,1531,1532],{"class":232},"!=",[226,1534,1535],{"class":232}," nil:\n",[226,1537,1539,1541,1543,1545,1547,1549,1552,1554,1556,1559],{"class":228,"line":1538},53,[226,1540,1505],{"class":334},[226,1542,263],{"class":232},[226,1544,1510],{"class":249},[226,1546,253],{"class":232},[226,1548,842],{"class":232},[226,1550,1551],{"class":845},"send failed:",[226,1553,842],{"class":232},[226,1555,269],{"class":232},[226,1557,1558],{"class":334}," err",[226,1560,413],{"class":232},[226,1562,1564,1567],{"class":228,"line":1563},54,[226,1565,1566],{"class":672},"    default",[226,1568,1569],{"class":232},":\n",[226,1571,1573,1575,1577,1579,1581,1583,1586,1588],{"class":228,"line":1572},55,[226,1574,1505],{"class":334},[226,1576,263],{"class":232},[226,1578,1510],{"class":249},[226,1580,253],{"class":232},[226,1582,842],{"class":232},[226,1584,1585],{"class":845},"sent",[226,1587,842],{"class":232},[226,1589,413],{"class":232},[226,1591,1593],{"class":228,"line":1592},56,[226,1594,1238],{"class":232},[226,1596,1598],{"class":228,"line":1597},57,[226,1599,290],{"class":232},[226,1601,1603],{"class":228,"line":1602},58,[226,1604,667],{"emptyLinePlaceholder":666},[226,1606,1608],{"class":228,"line":1607},59,[226,1609,1610],{"class":747},"\u002F\u002F isSuppressed would check a real suppression list (bounces, complaints,\n",[226,1612,1614],{"class":228,"line":1613},60,[226,1615,1616],{"class":747},"\u002F\u002F unsubscribes) keyed by stream and address.\n",[226,1618,1620,1622,1624,1626,1628,1630,1632,1634,1636,1638,1640,1643,1646,1648],{"class":228,"line":1619},61,[226,1621,509],{"class":232},[226,1623,1213],{"class":249},[226,1625,253],{"class":232},[226,1627,816],{"class":236},[226,1629,263],{"class":232},[226,1631,341],{"class":236},[226,1633,269],{"class":232},[226,1635,361],{"class":283},[226,1637,280],{"class":232},[226,1639,1136],{"class":283},[226,1641,1642],{"class":232}," {",[226,1644,1645],{"class":672}," return",[226,1647,1148],{"class":1147},[226,1649,774],{"class":232},[197,1651,1652],{},"Running this prints:",[217,1654,1659],{"className":1655,"code":1656,"filename":1657,"language":1658,"meta":222,"style":222},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[transactional] orders@caribhubs.com -> keisha@example.com: Your order is on its way\nsent\n","Terminal","bash",[201,1660,1661,1678],{"__ignoreMap":222},[226,1662,1663,1665,1667,1669,1672,1675],{"class":228,"line":229},[226,1664,846],{"class":232},[226,1666,1019],{"class":334},[226,1668,344],{"class":232},[226,1670,1671],{"class":334}," orders@caribhubs.com -",[226,1673,1674],{"class":232},">",[226,1676,1677],{"class":334}," keisha@example.com: Your order is on its way\n",[226,1679,1680],{"class":228,"line":246},[226,1681,1682],{"class":236},"sent\n",[197,1684,1685,1686,1689,1690,1693,1694,1697,1698,1700,1701,1704,1705,1707,1708,1710,1711,1697,1714,1716,1717,1720],{},"Change ",[201,1687,1688],{},"m.Stream"," to ",[201,1691,1692],{},"lttr.Outreach"," and the same call fails with ",[201,1695,1696],{},"no sender for stream \"outreach\"",", since ",[201,1699,430],{}," has no entry for it. Change it to ",[201,1702,1703],{},"lttr.Internal"," instead — ",[201,1706,430],{}," does have a sender for it above, so this time the sender check passes and ",[201,1709,474],{}," actually runs — and it fails with ",[201,1712,1713],{},"internal mail to keisha@example.com is not allowed",[201,1715,474],{}," always returns ",[201,1718,1719],{},"false"," above.",[309,1722,1724],{"id":1723},"next-steps","Next steps",[420,1726,1727,1735],{},[423,1728,1729,1731,1732,1734],{},[298,1730,120],{"href":121}," for the ",[201,1733,203],{}," that actually delivers mail.",[423,1736,1737,1739,1740,1742],{},[298,1738,110],{"href":111}," for what a ",[201,1741,211],{}," is moving.",[1744,1745,1746],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":222,"searchDepth":246,"depth":246,"links":1748},[1749,1750,1751,1752],{"id":311,"depth":246,"text":312},{"id":498,"depth":246,"text":499},{"id":646,"depth":246,"text":647},{"id":1723,"depth":246,"text":1724},"Route a rendered Message to the right Sender, with the internal-mail and suppression checks Render cannot make for you.","md",null,{},{"icon":118},{"title":115,"description":1753},"V5oopUYSviaPWIw1j1yp7rRboF2I01VFcd8XY9aMdUA",[1761,1763],{"title":110,"path":111,"stem":112,"description":1762,"icon":113,"children":-1},"The rendered email as a Go value, the rules for adding your own headers, and the exact bytes MIME writes.",{"title":120,"path":121,"stem":122,"description":1764,"icon":123,"children":-1},"The one Sender the library ships, one session per message, and how to test it without a real mail server.",1790309825446]