[{"data":1,"prerenderedAt":3077},["ShallowReactive",2],{"navigation_docs_en":3,"-en-mail-overview":441,"-en-mail-overview-surround":3072},[4,45,60,81,112,129,146,173,197,213,242,279,356,372,384,403,429],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Getting Started","i-lucide-rocket","\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Coming from Laravel","\u002Fen\u002Fgetting-started\u002Fcoming-from-laravel","en\u002F1.getting-started\u002F2.coming-from-laravel","i-lucide-arrow-right-left",{"title":21,"path":22,"stem":23,"icon":19},"Coming from Symfony","\u002Fen\u002Fgetting-started\u002Fcoming-from-symfony","en\u002F1.getting-started\u002F3.coming-from-symfony",{"title":25,"path":26,"stem":27,"icon":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F4.installation","i-lucide-download",{"title":30,"path":31,"stem":32,"icon":33},"Your first module","\u002Fen\u002Fgetting-started\u002Ffirst-module","en\u002F1.getting-started\u002F5.first-module","i-lucide-package-plus",{"title":35,"path":36,"stem":37,"icon":38},"Project structure","\u002Fen\u002Fgetting-started\u002Fproject-structure","en\u002F1.getting-started\u002F6.project-structure","i-lucide-folder-tree",{"title":40,"path":41,"stem":42,"icon":43},"The shop sample application","\u002Fen\u002Fgetting-started\u002Fsample-app","en\u002F1.getting-started\u002F7.sample-app","i-lucide-shopping-cart",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"queue","i-lucide-layers","\u002Fen\u002Fqueue","en\u002F10.queue",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Overview","\u002Fen\u002Fqueue\u002Foverview","en\u002F10.queue\u002F1.overview","i-lucide-list-ordered",{"title":57,"path":58,"stem":59,"icon":47},"Tasks","\u002Fen\u002Fqueue\u002Ftasks","en\u002F10.queue\u002F2.tasks",{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":44},"events","i-lucide-workflow","\u002Fen\u002Fevents","en\u002F11.events",[66,71,76],{"title":67,"path":68,"stem":69,"icon":70},"Events","\u002Fen\u002Fevents\u002Foverview","en\u002F11.events\u002F1.overview","i-lucide-radio",{"title":72,"path":73,"stem":74,"icon":75},"Outbox","\u002Fen\u002Fevents\u002Foutbox","en\u002F11.events\u002F2.outbox","i-lucide-inbox",{"title":77,"path":78,"stem":79,"icon":80},"Scheduling","\u002Fen\u002Fevents\u002Fscheduler","en\u002F11.events\u002F3.scheduler","i-lucide-calendar-clock",{"title":82,"icon":83,"path":84,"stem":85,"children":86,"page":44},"auth","i-lucide-shield-check","\u002Fen\u002Fauth","en\u002F12.auth",[87,92,97,102,107],{"title":88,"path":89,"stem":90,"icon":91},"Authentication","\u002Fen\u002Fauth\u002Foverview","en\u002F12.auth\u002F1.overview","i-lucide-key-round",{"title":93,"path":94,"stem":95,"icon":96},"RBAC","\u002Fen\u002Fauth\u002Frbac","en\u002F12.auth\u002F2.rbac","i-lucide-users",{"title":98,"path":99,"stem":100,"icon":101},"Policies","\u002Fen\u002Fauth\u002Fpolicy","en\u002F12.auth\u002F3.policy","i-lucide-gavel",{"title":103,"path":104,"stem":105,"icon":106},"Using auth standalone","\u002Fen\u002Fauth\u002Fstandalone","en\u002F12.auth\u002F4.standalone","i-lucide-plug",{"title":108,"path":109,"stem":110,"icon":111},"Social login with goth, standalone","\u002Fen\u002Fauth\u002Fsocial-standalone","en\u002F12.auth\u002F5.social-standalone","i-lucide-log-in",{"title":113,"icon":114,"path":115,"stem":116,"children":117,"page":44},"mail","i-lucide-mail","\u002Fen\u002Fmail","en\u002F13.mail",[118,121,125],{"title":52,"path":119,"stem":120,"icon":114},"\u002Fen\u002Fmail\u002Foverview","en\u002F13.mail\u002F1.overview",{"title":122,"path":123,"stem":124,"icon":114},"SMTP","\u002Fen\u002Fmail\u002Fsmtp","en\u002F13.mail\u002F2.smtp",{"title":126,"path":127,"stem":128,"icon":114},"MJML","\u002Fen\u002Fmail\u002Fmjml","en\u002F13.mail\u002F3.mjml",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":44},"notify","i-lucide-bell","\u002Fen\u002Fnotify","en\u002F14.notify",[135,138,142],{"title":52,"path":136,"stem":137,"icon":131},"\u002Fen\u002Fnotify\u002Foverview","en\u002F14.notify\u002F1.overview",{"title":139,"path":140,"stem":141,"icon":131},"Database channel","\u002Fen\u002Fnotify\u002Fdatabase","en\u002F14.notify\u002F2.database",{"title":143,"path":144,"stem":145,"icon":131},"Broadcast channel","\u002Fen\u002Fnotify\u002Fbroadcast","en\u002F14.notify\u002F3.broadcast",{"title":147,"icon":148,"path":149,"stem":150,"children":151,"page":44},"storage","i-lucide-hard-drive","\u002Fen\u002Fstorage","en\u002F15.storage",[152,155,159,163,168],{"title":52,"path":153,"stem":154,"icon":148},"\u002Fen\u002Fstorage\u002Foverview","en\u002F15.storage\u002F1.overview",{"title":156,"path":157,"stem":158},"Disk API","\u002Fen\u002Fstorage\u002Fdisk-api","en\u002F15.storage\u002F2.disk-api",{"title":160,"path":161,"stem":162,"icon":148},"Memory and local disk","\u002Fen\u002Fstorage\u002Flocal","en\u002F15.storage\u002F3.local",{"title":164,"path":165,"stem":166,"icon":167},"S3","\u002Fen\u002Fstorage\u002Fs3","en\u002F15.storage\u002F4.s3","i-lucide-cloud",{"title":169,"path":170,"stem":171,"icon":172},"Testing","\u002Fen\u002Fstorage\u002Ftesting","en\u002F15.storage\u002F5.testing","i-lucide-flask-conical",{"title":174,"icon":175,"path":176,"stem":177,"children":178,"page":44},"telemetry","i-lucide-activity","\u002Fen\u002Ftelemetry","en\u002F16.telemetry",[179,182,187,192],{"title":52,"path":180,"stem":181,"icon":175},"\u002Fen\u002Ftelemetry\u002Foverview","en\u002F16.telemetry\u002F1.overview",{"title":183,"path":184,"stem":185,"icon":186},"Logging","\u002Fen\u002Ftelemetry\u002Flogging","en\u002F16.telemetry\u002F2.logging","i-lucide-scroll-text",{"title":188,"path":189,"stem":190,"icon":191},"OpenTelemetry","\u002Fen\u002Ftelemetry\u002Fopentelemetry","en\u002F16.telemetry\u002F3.opentelemetry","i-lucide-radar",{"title":193,"path":194,"stem":195,"icon":196},"Sentry","\u002Fen\u002Ftelemetry\u002Fsentry","en\u002F16.telemetry\u002F4.sentry","i-lucide-bug",{"title":198,"icon":199,"path":200,"stem":201,"children":202,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F17.reference",[203,208],{"title":204,"path":205,"stem":206,"icon":207},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F17.reference\u002F1.configuration","i-lucide-settings",{"title":209,"path":210,"stem":211,"icon":212},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F17.reference\u002F2.dependencies","i-lucide-package",{"title":214,"icon":215,"path":216,"stem":217,"children":218,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[219,223,228,232,237],{"title":220,"path":221,"stem":222,"icon":47},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture",{"title":224,"path":225,"stem":226,"icon":227},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F2.error-model","i-lucide-shield-alert",{"title":204,"path":229,"stem":230,"icon":231},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F3.configuration","i-lucide-settings-2",{"title":233,"path":234,"stem":235,"icon":236},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F4.codegen-pipeline","i-lucide-file-json",{"title":238,"path":239,"stem":240,"icon":241},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F5.design-patterns","i-lucide-puzzle",{"title":243,"icon":244,"path":245,"stem":246,"children":247,"page":44},"Kit","i-lucide-box","\u002Fen\u002Fkit","en\u002F3.kit",[248,251,256,261,266,270,275],{"title":52,"path":249,"stem":250,"icon":244},"\u002Fen\u002Fkit\u002Foverview","en\u002F3.kit\u002F1.overview",{"title":252,"path":253,"stem":254,"icon":255},"Application lifecycle","\u002Fen\u002Fkit\u002Fapp","en\u002F3.kit\u002F2.app","i-lucide-power",{"title":257,"path":258,"stem":259,"icon":260},"Server","\u002Fen\u002Fkit\u002Fserver","en\u002F3.kit\u002F3.server","i-lucide-server",{"title":262,"path":263,"stem":264,"icon":265},"Worker","\u002Fen\u002Fkit\u002Fworker","en\u002F3.kit\u002F4.worker","i-lucide-cog",{"title":267,"path":268,"stem":269,"icon":70},"Realtime","\u002Fen\u002Fkit\u002Frealtime","en\u002F3.kit\u002F5.realtime",{"title":271,"path":272,"stem":273,"icon":274},"Migrations","\u002Fen\u002Fkit\u002Fmigrations","en\u002F3.kit\u002F6.migrations","i-lucide-file-stack",{"title":276,"path":277,"stem":278,"icon":111},"Social login","\u002Fen\u002Fkit\u002Fsocial-login","en\u002F3.kit\u002F7.social-login",{"title":280,"icon":281,"path":282,"stem":283,"children":284,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F4.cli",[285,288,293,298,303,308,313,318,323,328,333,337,342,347,351],{"title":52,"path":286,"stem":287,"icon":281},"\u002Fen\u002Fcli\u002Foverview","en\u002F4.cli\u002F1.overview",{"title":289,"path":290,"stem":291,"icon":292},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F4.cli\u002F10.add-db","i-lucide-database-zap",{"title":294,"path":295,"stem":296,"icon":297},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F4.cli\u002F11.add-compose","i-lucide-container",{"title":299,"path":300,"stem":301,"icon":302},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F4.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":304,"path":305,"stem":306,"icon":307},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F4.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":309,"path":310,"stem":311,"icon":312},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F4.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":314,"path":315,"stem":316,"icon":317},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F4.cli\u002F15.add-seeder","i-lucide-sprout",{"title":319,"path":320,"stem":321,"icon":322},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F4.cli\u002F2.new-project","i-lucide-folder-plus",{"title":324,"path":325,"stem":326,"icon":327},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F4.cli\u002F3.new-module","i-lucide-blocks",{"title":329,"path":330,"stem":331,"icon":332},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F4.cli\u002F4.add-surface","i-lucide-layers-2",{"title":334,"path":335,"stem":336,"icon":244},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F4.cli\u002F5.add-core",{"title":338,"path":339,"stem":340,"icon":341},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F4.cli\u002F6.add-handler","i-lucide-webhook",{"title":343,"path":344,"stem":345,"icon":346},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F4.cli\u002F7.new-migration","i-lucide-file-plus",{"title":348,"path":349,"stem":350,"icon":265},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F4.cli\u002F8.worker-generators",{"title":352,"path":353,"stem":354,"icon":355},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F4.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"title":357,"icon":227,"path":358,"stem":359,"children":360,"page":44},"errs","\u002Fen\u002Ferrs","en\u002F5.errs",[361,364,368],{"title":52,"path":362,"stem":363,"icon":227},"\u002Fen\u002Ferrs\u002Foverview","en\u002F5.errs\u002F1.overview",{"title":365,"path":366,"stem":367},"API","\u002Fen\u002Ferrs\u002Fapi","en\u002F5.errs\u002F2.api",{"title":369,"path":370,"stem":371,"icon":227},"Integration","\u002Fen\u002Ferrs\u002Fintegration","en\u002F5.errs\u002F3.integration",{"title":373,"icon":231,"path":374,"stem":375,"children":376,"page":44},"envconf","\u002Fen\u002Fenvconf","en\u002F6.envconf",[377,380],{"title":52,"path":378,"stem":379,"icon":231},"\u002Fen\u002Fenvconf\u002Foverview","en\u002F6.envconf\u002F1.overview",{"title":381,"path":382,"stem":383},"Recipes","\u002Fen\u002Fenvconf\u002Frecipes","en\u002F6.envconf\u002F2.recipes",{"title":385,"icon":386,"path":387,"stem":388,"children":389,"page":44},"httperr","i-lucide-globe","\u002Fen\u002Fhttperr","en\u002F7.httperr",[390,393,398],{"title":52,"path":391,"stem":392,"icon":386},"\u002Fen\u002Fhttperr\u002Foverview","en\u002F7.httperr\u002F1.overview",{"title":394,"path":395,"stem":396,"icon":397},"Error responses","\u002Fen\u002Fhttperr\u002Ferror-responses","en\u002F7.httperr\u002F2.error-responses","i-lucide-octagon-alert",{"title":399,"path":400,"stem":401,"icon":402},"Validation","\u002Fen\u002Fhttperr\u002Fvalidation","en\u002F7.httperr\u002F3.validation","i-lucide-badge-check",{"title":404,"icon":405,"path":406,"stem":407,"children":408,"page":44},"dbx","i-lucide-database","\u002Fen\u002Fdbx","en\u002F8.dbx",[409,412,416,420,425],{"title":52,"path":410,"stem":411,"icon":405},"\u002Fen\u002Fdbx\u002Foverview","en\u002F8.dbx\u002F1.overview",{"title":413,"path":414,"stem":415,"icon":106},"pgx","\u002Fen\u002Fdbx\u002Fpg","en\u002F8.dbx\u002F2.pg",{"title":417,"path":418,"stem":419,"icon":47},"bun","\u002Fen\u002Fdbx\u002Fbunx","en\u002F8.dbx\u002F3.bunx",{"title":421,"path":422,"stem":423,"icon":424},"Transactions","\u002Fen\u002Fdbx\u002Ftransactions","en\u002F8.dbx\u002F4.transactions","i-lucide-git-merge",{"title":426,"path":427,"stem":428,"icon":317},"Seeders","\u002Fen\u002Fdbx\u002Fseed","en\u002F8.dbx\u002F5.seed",{"title":430,"icon":55,"path":431,"stem":432,"children":433,"page":44},"paginate","\u002Fen\u002Fpaginate","en\u002F9.paginate",[434,437],{"title":52,"path":435,"stem":436,"icon":55},"\u002Fen\u002Fpaginate\u002Foverview","en\u002F9.paginate\u002F1.overview",{"title":438,"path":439,"stem":440,"icon":19},"Cursor pagination","\u002Fen\u002Fpaginate\u002Fcursor","en\u002F9.paginate\u002F2.cursor",{"id":442,"title":52,"body":443,"description":3065,"extension":3066,"links":3067,"meta":3068,"navigation":3069,"path":119,"seo":3070,"stem":120,"__hash__":3071},"docs_en\u002Fen\u002F13.mail\u002F1.overview.md",{"type":444,"value":445,"toc":3048},"minimark",[446,481,486,525,533,601,605,615,642,646,735,752,756,762,867,870,1048,1052,1068,1291,1314,1325,1329,1403,1443,1448,1452,1474,1752,1766,1862,1873,1878,2188,2197,2201,2214,2220,2229,2266,2316,2320,2323,2374,2703,2881,2903,2975,2990,3011,3015,3044],[447,448,449,452,453,456,457,456,460,463,464,467,468,472,473,476,477,480],"p",{},[450,451,113],"code",{}," is a standalone Go module (",[450,454,455],{},"github.com\u002Fgp-system\u002Fmail",", +",[450,458,459],{},"mail\u002Fsmtp",[450,461,462],{},"mail\u002Fmjml","): a ",[450,465,466],{},"Mailer"," interface, a fluent message builder, and a set of body types (plain text, HTML, Go templates, MJML). It works in any Go program, with or without the kit. In the ",[469,470,471],"a",{"href":41},"shop sample app",", the ",[450,474,475],{},"sendOrderConfirmation"," listener runs in the worker and sends an order confirmation rendered from MJML. Business code (the listener) sees only the ",[450,478,479],{},"mail.Mailer"," interface; whether SMTP, log output, or a test memory mailer sits behind it is decided by the wiring.",[482,483,485],"h2",{"id":484},"install","Install",[487,488,493],"pre",{"className":489,"code":490,"language":491,"meta":492,"style":492},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com\u002Fgp-system\u002Fmail@v0.1.0 # the kit itself uses this tag\ngo get github.com\u002Fgp-system\u002Fmail@latest\n","sh","",[450,494,495,515],{"__ignoreMap":492},[496,497,500,504,508,511],"span",{"class":498,"line":499},"line",1,[496,501,503],{"class":502},"sBMFI","go",[496,505,507],{"class":506},"sfazB"," get",[496,509,510],{"class":506}," github.com\u002Fgp-system\u002Fmail@v0.1.0",[496,512,514],{"class":513},"sHwdD"," # the kit itself uses this tag\n",[496,516,518,520,522],{"class":498,"line":517},2,[496,519,503],{"class":502},[496,521,507],{"class":506},[496,523,524],{"class":506}," github.com\u002Fgp-system\u002Fmail@latest\n",[447,526,527,528,532],{},"The root module has no dependency of its own beyond ",[469,529,530],{"href":362},[450,531,357],{},". The two optional subpackages each carry their own footprint, and a binary only pays for the one it imports:",[534,535,536,549],"table",{},[537,538,539],"thead",{},[540,541,542,546],"tr",{},[543,544,545],"th",{},"Import path",[543,547,548],{},"What it adds",[550,551,552,569,585],"tbody",{},[540,553,554,559],{},[555,556,557],"td",{},[450,558,455],{},[555,560,561,562,564,565,568],{},"the ",[450,563,466],{}," interface, ",[450,566,567],{},"Message",", body types, the dev mailers",[540,570,571,576],{},[555,572,573],{},[450,574,575],{},"github.com\u002Fgp-system\u002Fmail\u002Fsmtp",[555,577,578,579],{},"SMTP sending, via ",[469,580,584],{"href":581,"rel":582},"https:\u002F\u002Fgithub.com\u002Fwneessen\u002Fgo-mail",[583],"nofollow","wneessen\u002Fgo-mail",[540,586,587,592],{},[555,588,589],{},[450,590,591],{},"github.com\u002Fgp-system\u002Fmail\u002Fmjml",[555,593,594,595,600],{},"MJML-to-HTML compilation, via ",[469,596,599],{"href":597,"rel":598},"https:\u002F\u002Fgithub.com\u002FBoostport\u002Fmjml-go",[583],"Boostport\u002Fmjml-go"," (a WASM runtime)",[482,602,604],{"id":603},"why-mailer-is-an-interface","Why Mailer is an interface",[447,606,607,608,611,612,614],{},"The kit's design principle is ",[469,609,610],{"href":221},"\"centralize, don't abstract\"",": mail is, next to ",[469,613,147],{"href":153},", the other exception where the interface is deliberate:",[616,617,618,635],"ul",{},[619,620,621,622,626,627,631,632,634],"li",{},"The actual sending is done by the ",[469,623,624],{"href":123},[450,625,459],{}," subpackage, the MJML compilation by ",[469,628,629],{"href":127},[450,630,462],{},". The ",[450,633,113],{}," root package imports neither. Your service layer compiles without an SMTP client or a WASM runtime.",[619,636,637,638,641],{},"In tests, ",[450,639,640],{},"mail.NewMemory()"," replaces the driver in one line, so sent messages can be asserted on without a real SMTP connection.",[482,643,645],{"id":644},"the-interface","The interface",[487,647,650],{"className":648,"code":649,"language":503,"meta":492,"style":492},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import \"github.com\u002Fgp-system\u002Fmail\"\n\ntype Mailer interface {\n    Send(ctx context.Context, msg *Message) error\n}\n",[450,651,652,667,673,688,729],{"__ignoreMap":492},[496,653,654,658,662,664],{"class":498,"line":499},[496,655,657],{"class":656},"s7zQu","import",[496,659,661],{"class":660},"sMK4o"," \"",[496,663,455],{"class":502},[496,665,666],{"class":660},"\"\n",[496,668,669],{"class":498,"line":517},[496,670,672],{"emptyLinePlaceholder":671},true,"\n",[496,674,676,679,682,685],{"class":498,"line":675},3,[496,677,678],{"class":660},"type",[496,680,681],{"class":502}," Mailer",[496,683,684],{"class":660}," interface",[496,686,687],{"class":660}," {\n",[496,689,691,695,698,702,705,708,711,714,717,720,722,725],{"class":498,"line":690},4,[496,692,694],{"class":693},"s2Zo4","    Send",[496,696,697],{"class":660},"(",[496,699,701],{"class":700},"sHdIc","ctx",[496,703,704],{"class":502}," context",[496,706,707],{"class":660},".",[496,709,710],{"class":502},"Context",[496,712,713],{"class":660},",",[496,715,716],{"class":700}," msg",[496,718,719],{"class":660}," *",[496,721,567],{"class":502},[496,723,724],{"class":660},")",[496,726,728],{"class":727},"spNyl"," error\n",[496,730,732],{"class":498,"line":731},5,[496,733,734],{"class":660},"}\n",[447,736,737,740,741,744,745,748,749,707],{},[450,738,739],{},"Send"," is synchronous and calls ",[450,742,743],{},"Validate()"," first: no recipient returns ",[450,746,747],{},"ErrNoRecipient",", no body returns ",[450,750,751],{},"ErrNoBody",[482,753,755],{"id":754},"building-a-message","Building a Message",[447,757,758,759,761],{},"You build a ",[450,760,567],{}," fluently; construction never fails outright, omissions surface at send time:",[487,763,765],{"className":648,"code":764,"language":503,"meta":492,"style":492},"msg := mail.NewMessage().\n    WithFromNamed(\"Shop\", \"noreply@acme.test\").  \u002F\u002F optional: the driver's default sender steps in\n    WithTo(\"user@example.com\").\n    WithSubject(\"Your order confirmation\").\n    WithText(\"Thanks for your order!\")\n",[450,766,767,787,817,834,850],{"__ignoreMap":492},[496,768,769,773,776,779,781,784],{"class":498,"line":499},[496,770,772],{"class":771},"sTEyZ","msg ",[496,774,775],{"class":660},":=",[496,777,778],{"class":771}," mail",[496,780,707],{"class":660},[496,782,783],{"class":693},"NewMessage",[496,785,786],{"class":660},"().\n",[496,788,789,792,794,797,800,802,804,806,809,811,814],{"class":498,"line":517},[496,790,791],{"class":693},"    WithFromNamed",[496,793,697],{"class":660},[496,795,796],{"class":660},"\"",[496,798,799],{"class":506},"Shop",[496,801,796],{"class":660},[496,803,713],{"class":660},[496,805,661],{"class":660},[496,807,808],{"class":506},"noreply@acme.test",[496,810,796],{"class":660},[496,812,813],{"class":660},").",[496,815,816],{"class":513},"  \u002F\u002F optional: the driver's default sender steps in\n",[496,818,819,822,824,826,829,831],{"class":498,"line":675},[496,820,821],{"class":693},"    WithTo",[496,823,697],{"class":660},[496,825,796],{"class":660},[496,827,828],{"class":506},"user@example.com",[496,830,796],{"class":660},[496,832,833],{"class":660},").\n",[496,835,836,839,841,843,846,848],{"class":498,"line":690},[496,837,838],{"class":693},"    WithSubject",[496,840,697],{"class":660},[496,842,796],{"class":660},[496,844,845],{"class":506},"Your order confirmation",[496,847,796],{"class":660},[496,849,833],{"class":660},[496,851,852,855,857,859,862,864],{"class":498,"line":731},[496,853,854],{"class":693},"    WithText",[496,856,697],{"class":660},[496,858,796],{"class":660},[496,860,861],{"class":506},"Thanks for your order!",[496,863,796],{"class":660},[496,865,866],{"class":660},")\n",[447,868,869],{},"The full builder surface:",[534,871,872,882],{},[537,873,874],{},[540,875,876,879],{},[543,877,878],{},"Method",[543,880,881],{},"What it does",[550,883,884,906,919,932,945,955,965,978,997,1007,1021,1035],{},[540,885,886,895],{},[555,887,888,891,892],{},[450,889,890],{},"WithFrom(addr)"," \u002F ",[450,893,894],{},"WithFromNamed(name, addr)",[555,896,897,898,901,902,905],{},"sender; when omitted, the driver's configured default (",[450,899,900],{},"MAIL_FROM_ADDRESS","\u002F",[450,903,904],{},"MAIL_FROM_NAME",") applies",[540,907,908,916],{},[555,909,910,891,913],{},[450,911,912],{},"WithTo(addrs...)",[450,914,915],{},"WithToNamed(name, addr)",[555,917,918],{},"To recipient(s)",[540,920,921,929],{},[555,922,923,891,926],{},[450,924,925],{},"WithCc(addrs...)",[450,927,928],{},"WithCcNamed(name, addr)",[555,930,931],{},"Cc recipient(s)",[540,933,934,942],{},[555,935,936,891,939],{},[450,937,938],{},"WithBcc(addrs...)",[450,940,941],{},"WithBccNamed(name, addr)",[555,943,944],{},"Bcc recipient(s)",[540,946,947,952],{},[555,948,949],{},[450,950,951],{},"WithReplyTo(addr)",[555,953,954],{},"Reply-To",[540,956,957,962],{},[555,958,959],{},[450,960,961],{},"WithSubject(s)",[555,963,964],{},"subject",[540,966,967,972],{},[555,968,969],{},[450,970,971],{},"WithHeader(key, value)",[555,973,974,975,724],{},"custom header (e.g. ",[450,976,977],{},"List-Unsubscribe",[540,979,980,988],{},[555,981,982,891,985],{},[450,983,984],{},"WithText(s)",[450,986,987],{},"WithHTML(s)",[555,989,990,991,891,994],{},"shorthand for ",[450,992,993],{},"WithBody(mail.Text(s))",[450,995,996],{},"WithBody(mail.HTML(s))",[540,998,999,1004],{},[555,1000,1001],{},[450,1002,1003],{},"WithBody(b mail.Body)",[555,1005,1006],{},"any body (the last call wins)",[540,1008,1009,1014],{},[555,1010,1011],{},[450,1012,1013],{},"WithAttachment(filename, data, contentType)",[555,1015,1016,1017,1020],{},"attachment from memory (empty ",[450,1018,1019],{},"contentType",": the driver sniffs it)",[540,1022,1023,1028],{},[555,1024,1025],{},[450,1026,1027],{},"WithAttachmentReader(filename, r, contentType)",[555,1029,1030,1031,1034],{},"streamed attachment (",[450,1032,1033],{},"r"," is read once, at send time)",[540,1036,1037,1042],{},[555,1038,1039],{},[450,1040,1041],{},"WithEmbed(filename, data, contentType)",[555,1043,1044,1045],{},"inline resource, referenced from HTML as ",[450,1046,1047],{},"cid:\u003Cfilename>",[482,1049,1051],{"id":1050},"body-types","Body types",[447,1053,1054,1055,1058,1059,1062,1063,1067],{},"Every body implements the ",[450,1056,1057],{},"mail.Body"," interface (",[450,1060,1061],{},"Render(ctx) (Content, error)","; drivers render ",[1064,1065,1066],"strong",{},"inside"," the send span, so template errors are traced too):",[487,1069,1071],{"className":648,"code":1070,"language":503,"meta":492,"style":492},"\u002F\u002F 1. plain text\nmsg.WithText(\"Your account is ready.\")\n\n\u002F\u002F 2. raw HTML, with an optional text alternative\nmsg.WithBody(mail.HTML(\"\u003Cp>Your account is ready.\u003C\u002Fp>\").WithText(\"Your account is ready.\"))\n\n\u002F\u002F 3. html\u002Ftemplate + text\u002Ftemplate alternative from the same fs.FS\n\u002F\u002Fgo:embed templates\nvar tmpls embed.FS\n\nmsg.WithBody(mail.Template(tmpls, \"templates\u002Finvoice.html.tmpl\", inv).\n    WithTextTemplate(\"templates\u002Finvoice.txt.tmpl\"))\n\n\u002F\u002F 4. pre-parsed templates, for consumers with their own cache\nmsg.WithBody(mail.Parsed(htmlTpl, textTpl, data))\n",[450,1072,1073,1078,1099,1103,1108,1150,1155,1161,1167,1184,1189,1228,1245,1250,1256],{"__ignoreMap":492},[496,1074,1075],{"class":498,"line":499},[496,1076,1077],{"class":513},"\u002F\u002F 1. plain text\n",[496,1079,1080,1083,1085,1088,1090,1092,1095,1097],{"class":498,"line":517},[496,1081,1082],{"class":771},"msg",[496,1084,707],{"class":660},[496,1086,1087],{"class":693},"WithText",[496,1089,697],{"class":660},[496,1091,796],{"class":660},[496,1093,1094],{"class":506},"Your account is ready.",[496,1096,796],{"class":660},[496,1098,866],{"class":660},[496,1100,1101],{"class":498,"line":675},[496,1102,672],{"emptyLinePlaceholder":671},[496,1104,1105],{"class":498,"line":690},[496,1106,1107],{"class":513},"\u002F\u002F 2. raw HTML, with an optional text alternative\n",[496,1109,1110,1112,1114,1117,1119,1121,1123,1126,1128,1130,1133,1135,1137,1139,1141,1143,1145,1147],{"class":498,"line":731},[496,1111,1082],{"class":771},[496,1113,707],{"class":660},[496,1115,1116],{"class":693},"WithBody",[496,1118,697],{"class":660},[496,1120,113],{"class":771},[496,1122,707],{"class":660},[496,1124,1125],{"class":693},"HTML",[496,1127,697],{"class":660},[496,1129,796],{"class":660},[496,1131,1132],{"class":506},"\u003Cp>Your account is ready.\u003C\u002Fp>",[496,1134,796],{"class":660},[496,1136,813],{"class":660},[496,1138,1087],{"class":693},[496,1140,697],{"class":660},[496,1142,796],{"class":660},[496,1144,1094],{"class":506},[496,1146,796],{"class":660},[496,1148,1149],{"class":660},"))\n",[496,1151,1153],{"class":498,"line":1152},6,[496,1154,672],{"emptyLinePlaceholder":671},[496,1156,1158],{"class":498,"line":1157},7,[496,1159,1160],{"class":513},"\u002F\u002F 3. html\u002Ftemplate + text\u002Ftemplate alternative from the same fs.FS\n",[496,1162,1164],{"class":498,"line":1163},8,[496,1165,1166],{"class":513},"\u002F\u002Fgo:embed templates\n",[496,1168,1170,1173,1176,1179,1181],{"class":498,"line":1169},9,[496,1171,1172],{"class":660},"var",[496,1174,1175],{"class":771}," tmpls ",[496,1177,1178],{"class":502},"embed",[496,1180,707],{"class":660},[496,1182,1183],{"class":502},"FS\n",[496,1185,1187],{"class":498,"line":1186},10,[496,1188,672],{"emptyLinePlaceholder":671},[496,1190,1192,1194,1196,1198,1200,1202,1204,1207,1209,1212,1214,1216,1219,1221,1223,1226],{"class":498,"line":1191},11,[496,1193,1082],{"class":771},[496,1195,707],{"class":660},[496,1197,1116],{"class":693},[496,1199,697],{"class":660},[496,1201,113],{"class":771},[496,1203,707],{"class":660},[496,1205,1206],{"class":693},"Template",[496,1208,697],{"class":660},[496,1210,1211],{"class":771},"tmpls",[496,1213,713],{"class":660},[496,1215,661],{"class":660},[496,1217,1218],{"class":506},"templates\u002Finvoice.html.tmpl",[496,1220,796],{"class":660},[496,1222,713],{"class":660},[496,1224,1225],{"class":771}," inv",[496,1227,833],{"class":660},[496,1229,1231,1234,1236,1238,1241,1243],{"class":498,"line":1230},12,[496,1232,1233],{"class":693},"    WithTextTemplate",[496,1235,697],{"class":660},[496,1237,796],{"class":660},[496,1239,1240],{"class":506},"templates\u002Finvoice.txt.tmpl",[496,1242,796],{"class":660},[496,1244,1149],{"class":660},[496,1246,1248],{"class":498,"line":1247},13,[496,1249,672],{"emptyLinePlaceholder":671},[496,1251,1253],{"class":498,"line":1252},14,[496,1254,1255],{"class":513},"\u002F\u002F 4. pre-parsed templates, for consumers with their own cache\n",[496,1257,1259,1261,1263,1265,1267,1269,1271,1274,1276,1279,1281,1284,1286,1289],{"class":498,"line":1258},15,[496,1260,1082],{"class":771},[496,1262,707],{"class":660},[496,1264,1116],{"class":693},[496,1266,697],{"class":660},[496,1268,113],{"class":771},[496,1270,707],{"class":660},[496,1272,1273],{"class":693},"Parsed",[496,1275,697],{"class":660},[496,1277,1278],{"class":771},"htmlTpl",[496,1280,713],{"class":660},[496,1282,1283],{"class":771}," textTpl",[496,1285,713],{"class":660},[496,1287,1288],{"class":771}," data",[496,1290,1149],{"class":660},[447,1292,1293,1296,1297,1300,1301,1303,1304,1307,1308,1310,1311,1313],{},[450,1294,1295],{},"mail.Template"," can be extended with ",[450,1298,1299],{},"WithFuncs(template.FuncMap)"," before parsing. For MJML-sourced HTML, see ",[469,1302,126],{"href":127},": the compiled ",[450,1305,1306],{},"mjml.Body"," is a ",[450,1309,1057],{}," too, and slots into ",[450,1312,1116],{}," the same way.",[1315,1316,1317,1318,901,1321,1324],"note",{},"There is no automatic text\u002Fplain derivation from HTML or MJML: when you need a text alternative, set it explicitly with ",[450,1319,1320],{},".WithText()",[450,1322,1323],{},".WithTextTemplate()",", and an HTML-only message stays HTML-only.",[482,1326,1328],{"id":1327},"dev-mailers-memory-log-and-discard","Dev mailers: memory, log, and discard",[487,1330,1332],{"className":648,"code":1331,"language":503,"meta":492,"style":492},"mem := mail.NewMemory()               \u002F\u002F renders and captures, no real sending\nmailer := mail.NewLog(slog.Default()) \u002F\u002F renders and logs instead of sending (dev)\nmailer = mail.NewDiscard()            \u002F\u002F renders (so template bugs still surface) then drops\n",[450,1333,1334,1354,1384],{"__ignoreMap":492},[496,1335,1336,1339,1341,1343,1345,1348,1351],{"class":498,"line":499},[496,1337,1338],{"class":771},"mem ",[496,1340,775],{"class":660},[496,1342,778],{"class":771},[496,1344,707],{"class":660},[496,1346,1347],{"class":693},"NewMemory",[496,1349,1350],{"class":660},"()",[496,1352,1353],{"class":513},"               \u002F\u002F renders and captures, no real sending\n",[496,1355,1356,1359,1361,1363,1365,1368,1370,1373,1375,1378,1381],{"class":498,"line":517},[496,1357,1358],{"class":771},"mailer ",[496,1360,775],{"class":660},[496,1362,778],{"class":771},[496,1364,707],{"class":660},[496,1366,1367],{"class":693},"NewLog",[496,1369,697],{"class":660},[496,1371,1372],{"class":771},"slog",[496,1374,707],{"class":660},[496,1376,1377],{"class":693},"Default",[496,1379,1380],{"class":660},"())",[496,1382,1383],{"class":513}," \u002F\u002F renders and logs instead of sending (dev)\n",[496,1385,1386,1388,1391,1393,1395,1398,1400],{"class":498,"line":675},[496,1387,1358],{"class":771},[496,1389,1390],{"class":660},"=",[496,1392,778],{"class":771},[496,1394,707],{"class":660},[496,1396,1397],{"class":693},"NewDiscard",[496,1399,1350],{"class":660},[496,1401,1402],{"class":513},"            \u002F\u002F renders (so template bugs still surface) then drops\n",[447,1404,1405,1408,1409,1412,1413,1416,1417,1420,1421,1424,1425,1428,1429,1435,1436,1439,1440,1442],{},[450,1406,1407],{},"Memory"," renders every message and captures it as ",[450,1410,1411],{},"[]mail.SentMessage"," (",[450,1414,1415],{},"Messages()",", ",[450,1418,1419],{},"Reset()","); it is safe for concurrent use. The ",[450,1422,1423],{},"Log"," driver logs only the subject, recipient count, and rendered size at Info; the full body only at Debug. No PII in logs by default. ",[450,1426,1427],{},"Discard"," is what ",[469,1430,1432],{"href":1431},"\u002Fen\u002Fmail\u002Fsmtp#newifconfigured-the-dev-prod-switch",[450,1433,1434],{},"smtp.NewIfConfigured"," returns when ",[450,1437,1438],{},"MAIL_HOST"," is empty: it still validates and renders (so a broken template fails the same way it would with a real driver), it just never transmits or logs; the quietest possible \"mail is off\" state, distinct from ",[450,1441,1423],{},"'s \"mail is off, but tell me about it\".",[447,1444,1445,1446,707],{},"For the concrete SMTP driver, see ",[469,1447,122],{"href":123},[482,1449,1451],{"id":1450},"in-the-shop-the-sendorderconfirmation-listener","In the shop: the sendOrderConfirmation listener",[447,1453,1454,1455,1458,1459,1462,1463,1466,1467,1469,1470,1473],{},"The listener running in the ",[469,1456,1457],{"href":263},"worker"," subscribes to the ",[450,1460,1461],{},"orderPlaced"," ",[469,1464,1465],{"href":68},"event"," and sends via the ",[450,1468,479],{}," it gets from ",[450,1471,1472],{},"Dependencies","; it knows nothing about the driver:",[487,1475,1478],{"className":648,"code":1476,"filename":1477,"language":503,"meta":492,"style":492},"import (\n    \"github.com\u002Facme\u002Fshop\u002Finternal\u002Fmodules\u002Fshop\"\n    shopevents \"github.com\u002Facme\u002Fshop\u002Finternal\u002Fmodules\u002Fshop\u002Fevents\"\n)\n\n\u002F\u002Fgo:embed templates\nvar tmpls embed.FS\n\nfunc sendOrderConfirmation(deps shop.Dependencies) func(context.Context, shopevents.OrderPlaced) error {\n    return func(ctx context.Context, ev shopevents.OrderPlaced) error {\n        return deps.Mailer.Send(ctx, mail.NewMessage().\n            WithTo(ev.CustomerEmail).\n            WithSubject(\"Your order confirmation: \"+ev.OrderID).\n            WithBody(mjml.Template(tmpls, \"templates\u002Forder_confirmation.mjml.tmpl\", ev).\n                WithTextTemplate(\"templates\u002Forder_confirmation.txt.tmpl\")))\n    }\n}\n","internal\u002Fmodules\u002Fshop\u002Flisteners\u002Fsend_order_confirmation.go",[450,1479,1480,1487,1497,1509,1513,1517,1521,1533,1537,1586,1618,1648,1665,1691,1724,1741,1747],{"__ignoreMap":492},[496,1481,1482,1484],{"class":498,"line":499},[496,1483,657],{"class":656},[496,1485,1486],{"class":660}," (\n",[496,1488,1489,1492,1495],{"class":498,"line":517},[496,1490,1491],{"class":660},"    \"",[496,1493,1494],{"class":502},"github.com\u002Facme\u002Fshop\u002Finternal\u002Fmodules\u002Fshop",[496,1496,666],{"class":660},[496,1498,1499,1502,1504,1507],{"class":498,"line":675},[496,1500,1501],{"class":771},"    shopevents ",[496,1503,796],{"class":660},[496,1505,1506],{"class":502},"github.com\u002Facme\u002Fshop\u002Finternal\u002Fmodules\u002Fshop\u002Fevents",[496,1508,666],{"class":660},[496,1510,1511],{"class":498,"line":690},[496,1512,866],{"class":660},[496,1514,1515],{"class":498,"line":731},[496,1516,672],{"emptyLinePlaceholder":671},[496,1518,1519],{"class":498,"line":1152},[496,1520,1166],{"class":513},[496,1522,1523,1525,1527,1529,1531],{"class":498,"line":1157},[496,1524,1172],{"class":660},[496,1526,1175],{"class":771},[496,1528,1178],{"class":502},[496,1530,707],{"class":660},[496,1532,1183],{"class":502},[496,1534,1535],{"class":498,"line":1163},[496,1536,672],{"emptyLinePlaceholder":671},[496,1538,1539,1542,1545,1547,1550,1553,1555,1557,1559,1562,1565,1567,1569,1571,1574,1576,1579,1581,1584],{"class":498,"line":1169},[496,1540,1541],{"class":660},"func",[496,1543,1544],{"class":693}," sendOrderConfirmation",[496,1546,697],{"class":660},[496,1548,1549],{"class":700},"deps",[496,1551,1552],{"class":502}," shop",[496,1554,707],{"class":660},[496,1556,1472],{"class":502},[496,1558,724],{"class":660},[496,1560,1561],{"class":660}," func(",[496,1563,1564],{"class":502},"context",[496,1566,707],{"class":660},[496,1568,710],{"class":502},[496,1570,713],{"class":660},[496,1572,1573],{"class":502}," shopevents",[496,1575,707],{"class":660},[496,1577,1578],{"class":502},"OrderPlaced",[496,1580,724],{"class":660},[496,1582,1583],{"class":727}," error",[496,1585,687],{"class":660},[496,1587,1588,1591,1593,1595,1597,1599,1601,1603,1606,1608,1610,1612,1614,1616],{"class":498,"line":1186},[496,1589,1590],{"class":656},"    return",[496,1592,1561],{"class":660},[496,1594,701],{"class":700},[496,1596,704],{"class":502},[496,1598,707],{"class":660},[496,1600,710],{"class":502},[496,1602,713],{"class":660},[496,1604,1605],{"class":700}," ev",[496,1607,1573],{"class":502},[496,1609,707],{"class":660},[496,1611,1578],{"class":502},[496,1613,724],{"class":660},[496,1615,1583],{"class":727},[496,1617,687],{"class":660},[496,1619,1620,1623,1626,1628,1630,1632,1634,1636,1638,1640,1642,1644,1646],{"class":498,"line":1191},[496,1621,1622],{"class":656},"        return",[496,1624,1625],{"class":771}," deps",[496,1627,707],{"class":660},[496,1629,466],{"class":771},[496,1631,707],{"class":660},[496,1633,739],{"class":693},[496,1635,697],{"class":660},[496,1637,701],{"class":771},[496,1639,713],{"class":660},[496,1641,778],{"class":771},[496,1643,707],{"class":660},[496,1645,783],{"class":693},[496,1647,786],{"class":660},[496,1649,1650,1653,1655,1658,1660,1663],{"class":498,"line":1230},[496,1651,1652],{"class":693},"            WithTo",[496,1654,697],{"class":660},[496,1656,1657],{"class":771},"ev",[496,1659,707],{"class":660},[496,1661,1662],{"class":771},"CustomerEmail",[496,1664,833],{"class":660},[496,1666,1667,1670,1672,1674,1677,1679,1682,1684,1686,1689],{"class":498,"line":1247},[496,1668,1669],{"class":693},"            WithSubject",[496,1671,697],{"class":660},[496,1673,796],{"class":660},[496,1675,1676],{"class":506},"Your order confirmation: ",[496,1678,796],{"class":660},[496,1680,1681],{"class":660},"+",[496,1683,1657],{"class":771},[496,1685,707],{"class":660},[496,1687,1688],{"class":771},"OrderID",[496,1690,833],{"class":660},[496,1692,1693,1696,1698,1701,1703,1705,1707,1709,1711,1713,1716,1718,1720,1722],{"class":498,"line":1252},[496,1694,1695],{"class":693},"            WithBody",[496,1697,697],{"class":660},[496,1699,1700],{"class":771},"mjml",[496,1702,707],{"class":660},[496,1704,1206],{"class":693},[496,1706,697],{"class":660},[496,1708,1211],{"class":771},[496,1710,713],{"class":660},[496,1712,661],{"class":660},[496,1714,1715],{"class":506},"templates\u002Forder_confirmation.mjml.tmpl",[496,1717,796],{"class":660},[496,1719,713],{"class":660},[496,1721,1605],{"class":771},[496,1723,833],{"class":660},[496,1725,1726,1729,1731,1733,1736,1738],{"class":498,"line":1258},[496,1727,1728],{"class":693},"                WithTextTemplate",[496,1730,697],{"class":660},[496,1732,796],{"class":660},[496,1734,1735],{"class":506},"templates\u002Forder_confirmation.txt.tmpl",[496,1737,796],{"class":660},[496,1739,1740],{"class":660},")))\n",[496,1742,1744],{"class":498,"line":1743},16,[496,1745,1746],{"class":660},"    }\n",[496,1748,1750],{"class":498,"line":1749},17,[496,1751,734],{"class":660},[447,1753,1754,1755,1758,1759,1761,1762,1765],{},"The registration lives in ",[450,1756,1757],{},"listeners\u002Fregister.go",", routed to the ",[450,1760,113],{}," queue (generated by ",[450,1763,1764],{},"add listener shop orderPlaced sendOrderConfirmation --queue mail","):",[487,1767,1770],{"className":648,"code":1768,"filename":1769,"language":503,"meta":492,"style":492},"func Register(reg *events.Registry, deps shop.Dependencies) {\n    events.Listen(reg, \"sendOrderConfirmation\", sendOrderConfirmation(deps), queue.OnQueue(\"mail\"))\n}\n","internal\u002Fmodules\u002Fshop\u002Flisteners\u002Fregister.go",[450,1771,1772,1807,1858],{"__ignoreMap":492},[496,1773,1774,1776,1779,1781,1784,1786,1788,1790,1793,1795,1797,1799,1801,1803,1805],{"class":498,"line":499},[496,1775,1541],{"class":660},[496,1777,1778],{"class":693}," Register",[496,1780,697],{"class":660},[496,1782,1783],{"class":700},"reg",[496,1785,719],{"class":660},[496,1787,61],{"class":502},[496,1789,707],{"class":660},[496,1791,1792],{"class":502},"Registry",[496,1794,713],{"class":660},[496,1796,1625],{"class":700},[496,1798,1552],{"class":502},[496,1800,707],{"class":660},[496,1802,1472],{"class":502},[496,1804,724],{"class":660},[496,1806,687],{"class":660},[496,1808,1809,1812,1814,1817,1819,1821,1823,1825,1827,1829,1831,1833,1835,1837,1840,1843,1845,1848,1850,1852,1854,1856],{"class":498,"line":517},[496,1810,1811],{"class":771},"    events",[496,1813,707],{"class":660},[496,1815,1816],{"class":693},"Listen",[496,1818,697],{"class":660},[496,1820,1783],{"class":771},[496,1822,713],{"class":660},[496,1824,661],{"class":660},[496,1826,475],{"class":506},[496,1828,796],{"class":660},[496,1830,713],{"class":660},[496,1832,1544],{"class":693},[496,1834,697],{"class":660},[496,1836,1549],{"class":771},[496,1838,1839],{"class":660},"),",[496,1841,1842],{"class":771}," queue",[496,1844,707],{"class":660},[496,1846,1847],{"class":693},"OnQueue",[496,1849,697],{"class":660},[496,1851,796],{"class":660},[496,1853,113],{"class":506},[496,1855,796],{"class":660},[496,1857,1149],{"class":660},[496,1859,1860],{"class":498,"line":675},[496,1861,734],{"class":660},[447,1863,1864,1865,1868,1869,1872],{},"Delivery is at-least-once, so the listener should be idempotent: the ",[450,1866,1867],{},"Meta.ID"," read from ",[450,1870,1871],{},"events.MetaFromContext(ctx)"," is a stable idempotency key across retries.",[1874,1875,1877],"h3",{"id":1876},"the-test-memory-mailer","The test: Memory mailer",[487,1879,1881],{"className":648,"code":1880,"language":503,"meta":492,"style":492},"func TestSendOrderConfirmation(t *testing.T) {\n    mem := mail.NewMemory()\n    handle := sendOrderConfirmation(shop.Dependencies{Mailer: mem})\n\n    err := handle(t.Context(), shopevents.OrderPlaced{\n        OrderID: \"o-1001\", CustomerEmail: \"user@example.com\",\n    })\n\n    require.NoError(t, err)\n    sent := mem.Messages()\n    require.Len(t, sent, 1)\n    require.Equal(t, \"user@example.com\", sent[0].To[0].Address)\n    require.Contains(t, sent[0].Content.HTML, \"o-1001\") \u002F\u002F Memory really renders\n}\n",[450,1882,1883,1909,1925,1957,1961,1991,2021,2026,2030,2051,2067,2093,2141,2184],{"__ignoreMap":492},[496,1884,1885,1887,1890,1892,1895,1897,1900,1902,1905,1907],{"class":498,"line":499},[496,1886,1541],{"class":660},[496,1888,1889],{"class":693}," TestSendOrderConfirmation",[496,1891,697],{"class":660},[496,1893,1894],{"class":700},"t",[496,1896,719],{"class":660},[496,1898,1899],{"class":502},"testing",[496,1901,707],{"class":660},[496,1903,1904],{"class":502},"T",[496,1906,724],{"class":660},[496,1908,687],{"class":660},[496,1910,1911,1914,1916,1918,1920,1922],{"class":498,"line":517},[496,1912,1913],{"class":771},"    mem ",[496,1915,775],{"class":660},[496,1917,778],{"class":771},[496,1919,707],{"class":660},[496,1921,1347],{"class":693},[496,1923,1924],{"class":660},"()\n",[496,1926,1927,1930,1932,1934,1936,1939,1941,1943,1946,1948,1951,1954],{"class":498,"line":675},[496,1928,1929],{"class":771},"    handle ",[496,1931,775],{"class":660},[496,1933,1544],{"class":693},[496,1935,697],{"class":660},[496,1937,1938],{"class":502},"shop",[496,1940,707],{"class":660},[496,1942,1472],{"class":502},[496,1944,1945],{"class":660},"{",[496,1947,466],{"class":771},[496,1949,1950],{"class":660},":",[496,1952,1953],{"class":771}," mem",[496,1955,1956],{"class":660},"})\n",[496,1958,1959],{"class":498,"line":690},[496,1960,672],{"emptyLinePlaceholder":671},[496,1962,1963,1966,1968,1971,1973,1975,1977,1979,1982,1984,1986,1988],{"class":498,"line":731},[496,1964,1965],{"class":771},"    err ",[496,1967,775],{"class":660},[496,1969,1970],{"class":693}," handle",[496,1972,697],{"class":660},[496,1974,1894],{"class":771},[496,1976,707],{"class":660},[496,1978,710],{"class":693},[496,1980,1981],{"class":660},"(),",[496,1983,1573],{"class":502},[496,1985,707],{"class":660},[496,1987,1578],{"class":502},[496,1989,1990],{"class":660},"{\n",[496,1992,1993,1996,1998,2000,2003,2005,2007,2010,2012,2014,2016,2018],{"class":498,"line":1152},[496,1994,1995],{"class":771},"        OrderID",[496,1997,1950],{"class":660},[496,1999,661],{"class":660},[496,2001,2002],{"class":506},"o-1001",[496,2004,796],{"class":660},[496,2006,713],{"class":660},[496,2008,2009],{"class":771}," CustomerEmail",[496,2011,1950],{"class":660},[496,2013,661],{"class":660},[496,2015,828],{"class":506},[496,2017,796],{"class":660},[496,2019,2020],{"class":660},",\n",[496,2022,2023],{"class":498,"line":1157},[496,2024,2025],{"class":660},"    })\n",[496,2027,2028],{"class":498,"line":1163},[496,2029,672],{"emptyLinePlaceholder":671},[496,2031,2032,2035,2037,2040,2042,2044,2046,2049],{"class":498,"line":1169},[496,2033,2034],{"class":771},"    require",[496,2036,707],{"class":660},[496,2038,2039],{"class":693},"NoError",[496,2041,697],{"class":660},[496,2043,1894],{"class":771},[496,2045,713],{"class":660},[496,2047,2048],{"class":771}," err",[496,2050,866],{"class":660},[496,2052,2053,2056,2058,2060,2062,2065],{"class":498,"line":1186},[496,2054,2055],{"class":771},"    sent ",[496,2057,775],{"class":660},[496,2059,1953],{"class":771},[496,2061,707],{"class":660},[496,2063,2064],{"class":693},"Messages",[496,2066,1924],{"class":660},[496,2068,2069,2071,2073,2076,2078,2080,2082,2085,2087,2091],{"class":498,"line":1191},[496,2070,2034],{"class":771},[496,2072,707],{"class":660},[496,2074,2075],{"class":693},"Len",[496,2077,697],{"class":660},[496,2079,1894],{"class":771},[496,2081,713],{"class":660},[496,2083,2084],{"class":771}," sent",[496,2086,713],{"class":660},[496,2088,2090],{"class":2089},"sbssI"," 1",[496,2092,866],{"class":660},[496,2094,2095,2097,2099,2102,2104,2106,2108,2110,2112,2114,2116,2118,2121,2124,2127,2130,2132,2134,2136,2139],{"class":498,"line":1230},[496,2096,2034],{"class":771},[496,2098,707],{"class":660},[496,2100,2101],{"class":693},"Equal",[496,2103,697],{"class":660},[496,2105,1894],{"class":771},[496,2107,713],{"class":660},[496,2109,661],{"class":660},[496,2111,828],{"class":506},[496,2113,796],{"class":660},[496,2115,713],{"class":660},[496,2117,2084],{"class":771},[496,2119,2120],{"class":660},"[",[496,2122,2123],{"class":2089},"0",[496,2125,2126],{"class":660},"].",[496,2128,2129],{"class":771},"To",[496,2131,2120],{"class":660},[496,2133,2123],{"class":2089},[496,2135,2126],{"class":660},[496,2137,2138],{"class":771},"Address",[496,2140,866],{"class":660},[496,2142,2143,2145,2147,2150,2152,2154,2156,2158,2160,2162,2164,2167,2169,2171,2173,2175,2177,2179,2181],{"class":498,"line":1247},[496,2144,2034],{"class":771},[496,2146,707],{"class":660},[496,2148,2149],{"class":693},"Contains",[496,2151,697],{"class":660},[496,2153,1894],{"class":771},[496,2155,713],{"class":660},[496,2157,2084],{"class":771},[496,2159,2120],{"class":660},[496,2161,2123],{"class":2089},[496,2163,2126],{"class":660},[496,2165,2166],{"class":771},"Content",[496,2168,707],{"class":660},[496,2170,1125],{"class":771},[496,2172,713],{"class":660},[496,2174,661],{"class":660},[496,2176,2002],{"class":506},[496,2178,796],{"class":660},[496,2180,724],{"class":660},[496,2182,2183],{"class":513}," \u002F\u002F Memory really renders\n",[496,2185,2186],{"class":498,"line":1252},[496,2187,734],{"class":660},[447,2189,2190,2191,2193,2194,2196],{},"Because ",[450,2192,1407],{}," actually renders, the test catches template errors too: a misspelled field fails on the ",[450,2195,2166],{}," assertion, not in production.",[482,2198,2200],{"id":2199},"queued-asynchronous-sending","Queued (asynchronous) sending",[447,2202,2203,2204,2206,2207,2210,2211,2213],{},"The ",[450,2205,466],{}," stays synchronous; asynchronous sending follows the pattern above: the event goes to the queue through the ",[469,2208,2209],{"href":73},"outbox",", and the listener sends the email in the worker, with retries. There is no separate built-in \"mail queue\": the ",[469,2212,61],{"href":68}," system is it.",[482,2215,2217,2218],{"id":2216},"where-the-content-lives-add-mail","Where the content lives: ",[450,2219,299],{},[447,2221,2203,2222,2224,2225,2228],{},[450,2223,475],{}," example above calls ",[450,2226,2227],{},"deps.Mailer.Send"," directly from the listener: fine for a single, simple message. Once a module sends more than one related notification, or the message composition (multiple recipients, a computed subject, several templates) is worth naming and testing on its own, generate a notifier instead:",[487,2230,2232],{"className":489,"code":2231,"language":491,"meta":492,"style":492},"gpsystem add mail \u003Cmodule> \u003Cname>\n",[450,2233,2234],{"__ignoreMap":492},[496,2235,2236,2239,2242,2244,2247,2250,2253,2256,2258,2261,2263],{"class":498,"line":499},[496,2237,2238],{"class":502},"gpsystem",[496,2240,2241],{"class":506}," add",[496,2243,778],{"class":506},[496,2245,2246],{"class":660}," \u003C",[496,2248,2249],{"class":506},"modul",[496,2251,2252],{"class":771},"e",[496,2254,2255],{"class":660},">",[496,2257,2246],{"class":660},[496,2259,2260],{"class":506},"nam",[496,2262,2252],{"class":771},[496,2264,2265],{"class":660},">\n",[447,2267,2268,2269,2272,2273,2277,2278,2282,2283,2285,2286,2289,2290,2293,2294,2296,2297,2300,2301,2303,2304,901,2306,2309,2310,2315],{},"This is the same split the kit uses everywhere: ",[1064,2270,2271],{},"the transport is infrastructure, the content is the module's business logic"," (see ",[469,2274,2276],{"href":2275},"\u002Fen\u002Fgetting-started\u002Fproject-structure#what-can-live-inside-a-module","Project structure: what can live inside a module"," and the ",[469,2279,2281],{"href":2280},"\u002Fen\u002Fconcepts\u002Farchitecture#shared-code-a-decision-ladder","shared-code decision ladder","). ",[450,2284,299],{}," generates ",[450,2287,2288],{},"internal\u002Fmodules\u002F\u003Cmodule>\u002Fmail\u002F\u003Cname>.go",": a payload struct, a ",[450,2291,2292],{},"\u003CName>Notifier"," composing an injected ",[450,2295,479],{}," with the module's own templates, and wires a ",[450,2298,2299],{},"Mailer mail.Mailer"," field into the module's ",[450,2302,1472],{},", constructed once, in every entry point, via ",[450,2305,1434],{},[450,2307,2308],{},"MustNewIfConfigured",". See the ",[469,2311,2312,2314],{"href":300},[450,2313,299],{}," reference"," for the exact generated shape.",[1874,2317,2319],{"id":2318},"worked-example-the-contact-email-flow","Worked example: the contact-email flow",[447,2321,2322],{},"A concrete, end-to-end version of this pattern: a web form that notifies both an admin inbox and the requester:",[2324,2325,2326,2339,2349,2358],"ol",{},[619,2327,2328,2331,2332,2335,2336,2338],{},[450,2329,2330],{},"POST \u002Fweb\u002Fcontact",": the web surface's service persists the submission and dispatches a ",[450,2333,2334],{},"ContactSubmitted"," event through the ",[469,2337,2209],{"href":73},", inside the same transaction.",[619,2340,2341,2344,2345,2348],{},[450,2342,2343],{},"cmd\u002Fworker","'s outbox relay delivers the event to the ",[450,2346,2347],{},"sendContactEmail"," listener.",[619,2350,2351,2352,2355,2356,707],{},"The listener calls ",[450,2353,2354],{},"internal\u002Fmodules\u002Fcontact\u002Fmail.Notifier.Notify",", which renders both templates (an admin notification and a requester confirmation) and sends each through the injected ",[450,2357,479],{},[619,2359,2360,2361,2363,2364,2367,2368,2370,2371,2373],{},"That ",[450,2362,479],{}," is SMTP in production; ",[450,2365,2366],{},"mail.NewDiscard()"," in dev when ",[450,2369,1438],{}," is empty; and ",[450,2372,640],{}," in the notifier's own tests (see above).",[487,2375,2378],{"className":648,"code":2376,"filename":2377,"language":503,"meta":492,"style":492},"package mail\n\n\u002F\u002Fgo:embed templates\u002F*.tmpl\nvar templatesFS embed.FS\n\ntype ContactNotification struct {\n    Name, Email string\n    \u002F\u002F ... the rest of the submitted form\n}\n\ntype Notifier interface {\n    Notify(ctx context.Context, n ContactNotification) error\n}\n\nfunc New(m kitmail.Mailer, cfg Config) Notifier {\n    return &notifier{mailer: m, adminTo: cfg.To}\n}\n\nfunc (n *notifier) Notify(ctx context.Context, p ContactNotification) error {\n    adminErr := n.sendAdminNotification(ctx, p)      \u002F\u002F -> Config.To\n    requesterErr := n.sendRequesterConfirmation(ctx, p) \u002F\u002F -> p.Email\n    return errors.Join(adminErr, requesterErr)\n}\n","internal\u002Fmodules\u002Fcontact\u002Fmail\u002Fnotifier.go",[450,2379,2380,2388,2392,2397,2410,2414,2426,2439,2444,2448,2452,2463,2489,2493,2497,2530,2565,2569,2574,2617,2645,2673,2698],{"__ignoreMap":492},[496,2381,2382,2385],{"class":498,"line":499},[496,2383,2384],{"class":660},"package",[496,2386,2387],{"class":502}," mail\n",[496,2389,2390],{"class":498,"line":517},[496,2391,672],{"emptyLinePlaceholder":671},[496,2393,2394],{"class":498,"line":675},[496,2395,2396],{"class":513},"\u002F\u002Fgo:embed templates\u002F*.tmpl\n",[496,2398,2399,2401,2404,2406,2408],{"class":498,"line":690},[496,2400,1172],{"class":660},[496,2402,2403],{"class":771}," templatesFS ",[496,2405,1178],{"class":502},[496,2407,707],{"class":660},[496,2409,1183],{"class":502},[496,2411,2412],{"class":498,"line":731},[496,2413,672],{"emptyLinePlaceholder":671},[496,2415,2416,2418,2421,2424],{"class":498,"line":1152},[496,2417,678],{"class":660},[496,2419,2420],{"class":502}," ContactNotification",[496,2422,2423],{"class":660}," struct",[496,2425,687],{"class":660},[496,2427,2428,2431,2433,2436],{"class":498,"line":1157},[496,2429,2430],{"class":771},"    Name",[496,2432,713],{"class":660},[496,2434,2435],{"class":771}," Email ",[496,2437,2438],{"class":727},"string\n",[496,2440,2441],{"class":498,"line":1163},[496,2442,2443],{"class":513},"    \u002F\u002F ... the rest of the submitted form\n",[496,2445,2446],{"class":498,"line":1169},[496,2447,734],{"class":660},[496,2449,2450],{"class":498,"line":1186},[496,2451,672],{"emptyLinePlaceholder":671},[496,2453,2454,2456,2459,2461],{"class":498,"line":1191},[496,2455,678],{"class":660},[496,2457,2458],{"class":502}," Notifier",[496,2460,684],{"class":660},[496,2462,687],{"class":660},[496,2464,2465,2468,2470,2472,2474,2476,2478,2480,2483,2485,2487],{"class":498,"line":1230},[496,2466,2467],{"class":693},"    Notify",[496,2469,697],{"class":660},[496,2471,701],{"class":700},[496,2473,704],{"class":502},[496,2475,707],{"class":660},[496,2477,710],{"class":502},[496,2479,713],{"class":660},[496,2481,2482],{"class":700}," n",[496,2484,2420],{"class":502},[496,2486,724],{"class":660},[496,2488,728],{"class":727},[496,2490,2491],{"class":498,"line":1247},[496,2492,734],{"class":660},[496,2494,2495],{"class":498,"line":1252},[496,2496,672],{"emptyLinePlaceholder":671},[496,2498,2499,2501,2504,2506,2509,2512,2514,2516,2518,2521,2524,2526,2528],{"class":498,"line":1258},[496,2500,1541],{"class":660},[496,2502,2503],{"class":693}," New",[496,2505,697],{"class":660},[496,2507,2508],{"class":700},"m",[496,2510,2511],{"class":502}," kitmail",[496,2513,707],{"class":660},[496,2515,466],{"class":502},[496,2517,713],{"class":660},[496,2519,2520],{"class":700}," cfg",[496,2522,2523],{"class":502}," Config",[496,2525,724],{"class":660},[496,2527,2458],{"class":502},[496,2529,687],{"class":660},[496,2531,2532,2534,2537,2540,2542,2545,2547,2550,2552,2555,2557,2559,2561,2563],{"class":498,"line":1743},[496,2533,1590],{"class":656},[496,2535,2536],{"class":660}," &",[496,2538,2539],{"class":502},"notifier",[496,2541,1945],{"class":660},[496,2543,2544],{"class":771},"mailer",[496,2546,1950],{"class":660},[496,2548,2549],{"class":771}," m",[496,2551,713],{"class":660},[496,2553,2554],{"class":771}," adminTo",[496,2556,1950],{"class":660},[496,2558,2520],{"class":771},[496,2560,707],{"class":660},[496,2562,2129],{"class":771},[496,2564,734],{"class":660},[496,2566,2567],{"class":498,"line":1749},[496,2568,734],{"class":660},[496,2570,2572],{"class":498,"line":2571},18,[496,2573,672],{"emptyLinePlaceholder":671},[496,2575,2577,2579,2581,2584,2587,2589,2591,2594,2596,2598,2600,2602,2604,2606,2609,2611,2613,2615],{"class":498,"line":2576},19,[496,2578,1541],{"class":660},[496,2580,1412],{"class":660},[496,2582,2583],{"class":700},"n ",[496,2585,2586],{"class":660},"*",[496,2588,2539],{"class":502},[496,2590,724],{"class":660},[496,2592,2593],{"class":693}," Notify",[496,2595,697],{"class":660},[496,2597,701],{"class":700},[496,2599,704],{"class":502},[496,2601,707],{"class":660},[496,2603,710],{"class":502},[496,2605,713],{"class":660},[496,2607,2608],{"class":700}," p",[496,2610,2420],{"class":502},[496,2612,724],{"class":660},[496,2614,1583],{"class":727},[496,2616,687],{"class":660},[496,2618,2620,2623,2625,2627,2629,2632,2634,2636,2638,2640,2642],{"class":498,"line":2619},20,[496,2621,2622],{"class":771},"    adminErr ",[496,2624,775],{"class":660},[496,2626,2482],{"class":771},[496,2628,707],{"class":660},[496,2630,2631],{"class":693},"sendAdminNotification",[496,2633,697],{"class":660},[496,2635,701],{"class":771},[496,2637,713],{"class":660},[496,2639,2608],{"class":771},[496,2641,724],{"class":660},[496,2643,2644],{"class":513},"      \u002F\u002F -> Config.To\n",[496,2646,2648,2651,2653,2655,2657,2660,2662,2664,2666,2668,2670],{"class":498,"line":2647},21,[496,2649,2650],{"class":771},"    requesterErr ",[496,2652,775],{"class":660},[496,2654,2482],{"class":771},[496,2656,707],{"class":660},[496,2658,2659],{"class":693},"sendRequesterConfirmation",[496,2661,697],{"class":660},[496,2663,701],{"class":771},[496,2665,713],{"class":660},[496,2667,2608],{"class":771},[496,2669,724],{"class":660},[496,2671,2672],{"class":513}," \u002F\u002F -> p.Email\n",[496,2674,2676,2678,2681,2683,2686,2688,2691,2693,2696],{"class":498,"line":2675},22,[496,2677,1590],{"class":656},[496,2679,2680],{"class":771}," errors",[496,2682,707],{"class":660},[496,2684,2685],{"class":693},"Join",[496,2687,697],{"class":660},[496,2689,2690],{"class":771},"adminErr",[496,2692,713],{"class":660},[496,2694,2695],{"class":771}," requesterErr",[496,2697,866],{"class":660},[496,2699,2701],{"class":498,"line":2700},23,[496,2702,734],{"class":660},[487,2704,2707],{"className":648,"code":2705,"filename":2706,"language":503,"meta":492,"style":492},"func sendContactEmail(deps contact.Dependencies) func(context.Context, contactevents.ContactSubmitted) error {\n    notifier := contactmail.New(deps.Mailer, deps.Mail)\n    return func(ctx context.Context, ev contactevents.ContactSubmitted) error {\n        return notifier.Notify(ctx, contactmail.ContactNotification{Name: ev.Name, Email: ev.Email \u002F* ... *\u002F})\n    }\n}\n","internal\u002Fmodules\u002Fcontact\u002Flisteners\u002Fsendcontactemail.go",[450,2708,2709,2752,2786,2816,2873,2877],{"__ignoreMap":492},[496,2710,2711,2713,2716,2718,2720,2723,2725,2727,2729,2731,2733,2735,2737,2739,2742,2744,2746,2748,2750],{"class":498,"line":499},[496,2712,1541],{"class":660},[496,2714,2715],{"class":693}," sendContactEmail",[496,2717,697],{"class":660},[496,2719,1549],{"class":700},[496,2721,2722],{"class":502}," contact",[496,2724,707],{"class":660},[496,2726,1472],{"class":502},[496,2728,724],{"class":660},[496,2730,1561],{"class":660},[496,2732,1564],{"class":502},[496,2734,707],{"class":660},[496,2736,710],{"class":502},[496,2738,713],{"class":660},[496,2740,2741],{"class":502}," contactevents",[496,2743,707],{"class":660},[496,2745,2334],{"class":502},[496,2747,724],{"class":660},[496,2749,1583],{"class":727},[496,2751,687],{"class":660},[496,2753,2754,2757,2759,2762,2764,2767,2769,2771,2773,2775,2777,2779,2781,2784],{"class":498,"line":517},[496,2755,2756],{"class":771},"    notifier ",[496,2758,775],{"class":660},[496,2760,2761],{"class":771}," contactmail",[496,2763,707],{"class":660},[496,2765,2766],{"class":693},"New",[496,2768,697],{"class":660},[496,2770,1549],{"class":771},[496,2772,707],{"class":660},[496,2774,466],{"class":771},[496,2776,713],{"class":660},[496,2778,1625],{"class":771},[496,2780,707],{"class":660},[496,2782,2783],{"class":771},"Mail",[496,2785,866],{"class":660},[496,2787,2788,2790,2792,2794,2796,2798,2800,2802,2804,2806,2808,2810,2812,2814],{"class":498,"line":675},[496,2789,1590],{"class":656},[496,2791,1561],{"class":660},[496,2793,701],{"class":700},[496,2795,704],{"class":502},[496,2797,707],{"class":660},[496,2799,710],{"class":502},[496,2801,713],{"class":660},[496,2803,1605],{"class":700},[496,2805,2741],{"class":502},[496,2807,707],{"class":660},[496,2809,2334],{"class":502},[496,2811,724],{"class":660},[496,2813,1583],{"class":727},[496,2815,687],{"class":660},[496,2817,2818,2820,2823,2825,2828,2830,2832,2834,2836,2838,2841,2843,2846,2848,2850,2852,2854,2856,2859,2861,2863,2865,2868,2871],{"class":498,"line":690},[496,2819,1622],{"class":656},[496,2821,2822],{"class":771}," notifier",[496,2824,707],{"class":660},[496,2826,2827],{"class":693},"Notify",[496,2829,697],{"class":660},[496,2831,701],{"class":771},[496,2833,713],{"class":660},[496,2835,2761],{"class":502},[496,2837,707],{"class":660},[496,2839,2840],{"class":502},"ContactNotification",[496,2842,1945],{"class":660},[496,2844,2845],{"class":771},"Name",[496,2847,1950],{"class":660},[496,2849,1605],{"class":771},[496,2851,707],{"class":660},[496,2853,2845],{"class":771},[496,2855,713],{"class":660},[496,2857,2858],{"class":771}," Email",[496,2860,1950],{"class":660},[496,2862,1605],{"class":771},[496,2864,707],{"class":660},[496,2866,2867],{"class":771},"Email ",[496,2869,2870],{"class":513},"\u002F* ... *\u002F",[496,2872,1956],{"class":660},[496,2874,2875],{"class":498,"line":731},[496,2876,1746],{"class":660},[496,2878,2879],{"class":498,"line":1152},[496,2880,734],{"class":660},[447,2882,2883,2884,2887,2888,2891,2892,2895,2896,2899,2900,2902],{},"Both the recipient config (",[450,2885,2886],{},"Config.To",", module content) and the shared SMTP config (",[450,2889,2890],{},"smtp.Config",", kit-level transport) compose under the same ",[450,2893,2894],{},"MAIL_"," prefix in platform config: ",[450,2897,2898],{},"MAIL_TO"," and ",[450,2901,1438],{}," are just two fields under one env namespace, with no naming collision:",[487,2904,2907],{"className":648,"code":2905,"filename":2906,"language":503,"meta":492,"style":492},"type Config struct {\n    \u002F\u002F ...\n    Mail        smtp.Config        `envPrefix:\"MAIL_\"` \u002F\u002F MAIL_HOST, MAIL_FROM_ADDRESS, ...\n    ContactMail contactmail.Config `envPrefix:\"MAIL_\"` \u002F\u002F MAIL_TO\n}\n","internal\u002Fplatform\u002Fconfig\u002Fconfig.go",[450,2908,2909,2919,2924,2949,2971],{"__ignoreMap":492},[496,2910,2911,2913,2915,2917],{"class":498,"line":499},[496,2912,678],{"class":660},[496,2914,2523],{"class":502},[496,2916,2423],{"class":660},[496,2918,687],{"class":660},[496,2920,2921],{"class":498,"line":517},[496,2922,2923],{"class":513},"    \u002F\u002F ...\n",[496,2925,2926,2929,2932,2934,2937,2940,2943,2946],{"class":498,"line":675},[496,2927,2928],{"class":771},"    Mail        ",[496,2930,2931],{"class":502},"smtp",[496,2933,707],{"class":660},[496,2935,2936],{"class":502},"Config",[496,2938,2939],{"class":660},"        `",[496,2941,2942],{"class":506},"envPrefix:\"MAIL_\"",[496,2944,2945],{"class":660},"`",[496,2947,2948],{"class":513}," \u002F\u002F MAIL_HOST, MAIL_FROM_ADDRESS, ...\n",[496,2950,2951,2954,2957,2959,2961,2964,2966,2968],{"class":498,"line":690},[496,2952,2953],{"class":771},"    ContactMail ",[496,2955,2956],{"class":502},"contactmail",[496,2958,707],{"class":660},[496,2960,2936],{"class":502},[496,2962,2963],{"class":660}," `",[496,2965,2942],{"class":506},[496,2967,2945],{"class":660},[496,2969,2970],{"class":513}," \u002F\u002F MAIL_TO\n",[496,2972,2973],{"class":498,"line":731},[496,2974,734],{"class":660},[1315,2976,2977,2979,2980,2982,2983,2985,2986,2989],{},[450,2978,299],{}," does not scaffold the wiring shown in ",[450,2981,2347],{}," above by hand: the generator produces the notifier, its templates and the ",[450,2984,1472],{},"\u002Fconfig wiring; the payload fields, the subject line, and the call from a service or listener are business logic you write, the same way ",[450,2987,2988],{},"service\u002Fservice.go"," ships as a minimal seam.",[447,2991,2992,2993,2899,2996,3000,3001,3004,3005,901,3008,3010],{},"When a module's content is addressed to a recipient rather than a bare address, especially once it needs more than mail (a persisted inbox, a live push), see ",[469,2994,2995],{"href":136},"Notifications",[469,2997,2998],{"href":305},[450,2999,304],{}," instead; the two generators compose, and a notification's ",[450,3002,3003],{},"ToMail"," reuses the same ",[450,3006,3007],{},"mail.Message",[450,3009,1295],{}," builders shown here.",[482,3012,3014],{"id":3013},"related-pages","Related pages",[616,3016,3017,3026,3031,3036],{},[619,3018,3019,3021,3022,3025],{},[469,3020,122],{"href":123},": the SMTP driver, its configuration and the ",[450,3023,3024],{},"NewIfConfigured"," dev\u002Fprod switch.",[619,3027,3028,3030],{},[469,3029,126],{"href":127},": compiling MJML templates into responsive HTML.",[619,3032,3033,3035],{},[469,3034,2995],{"href":136},": content addressed to a recipient, possibly on several channels including mail.",[619,3037,3038,3040,3041,3043],{},[469,3039,220],{"href":221},": where ",[450,3042,113],{}," sits among the other standalone modules.",[3045,3046,3047],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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 .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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":492,"searchDepth":517,"depth":517,"links":3049},[3050,3051,3052,3053,3054,3055,3056,3059,3060,3064],{"id":484,"depth":517,"text":485},{"id":603,"depth":517,"text":604},{"id":644,"depth":517,"text":645},{"id":754,"depth":517,"text":755},{"id":1050,"depth":517,"text":1051},{"id":1327,"depth":517,"text":1328},{"id":1450,"depth":517,"text":1451,"children":3057},[3058],{"id":1876,"depth":675,"text":1877},{"id":2199,"depth":517,"text":2200},{"id":2216,"depth":517,"text":3061,"children":3062},"Where the content lives: add mail",[3063],{"id":2318,"depth":675,"text":2319},{"id":3013,"depth":517,"text":3014},"Email sending: fluent Message builder, MJML templates, a memory mailer for tests.","md",null,{},{"icon":114},{"title":52,"description":3065},"WhoEwgSh0my-nfXJnLFFpxCNFdKbDF_zDmNXnDZN-jo",[3073,3075],{"title":108,"path":109,"stem":110,"description":3074,"icon":111,"children":-1},"Wiring markbates\u002Fgoth (Discord, Facebook, Google) into a plain net\u002Fhttp app that only depends on gp-system\u002Fauth for tokens, no gpsystem tooling involved.",{"title":122,"path":123,"stem":124,"description":3076,"icon":114,"children":-1},"SMTP transport configuration for mail.",1785445889238]