feat: send mail

This commit is contained in:
2025-08-24 13:11:31 +02:00
parent 5b664234e9
commit 0f3da902dc
8 changed files with 144 additions and 17 deletions

3
go.mod
View File

@@ -5,7 +5,6 @@ go 1.24.5
require (
code.gitea.io/sdk/gitea v0.21.0
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a
github.com/google/uuid v1.6.0
github.com/jedib0t/go-pretty/v6 v6.6.8
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/starwalkn/gotenberg-go-client/v8 v8.11.0
@@ -16,9 +15,11 @@ require (
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
)

8
go.sum
View File

@@ -10,12 +10,12 @@ github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a h1:l7A0loSszR5zHd/qK53ZIHMO8b3bBSmENnQ6eKnUT0A=
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc=
github.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -49,5 +49,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -224,7 +224,7 @@ article {
</div>
<div class="invoice-info">
<p>
<strong>Rechnung:</strong> 20 25082 32224 30630 09759 35659 <br />
<strong>Rechnung:</strong> 20 25082 41057 10271 08692 82635 <br />
<strong>Datum:</strong> 24.08.2025 <br />
</p>
</div>
@@ -392,7 +392,7 @@ article {
</div>
<div>
<h4>Referenz</h4>
<p>20 25082 32224 30630 09759 35659</p>
<p>20 25082 41057 10271 08692 82635</p>
</div>
<div>
<h4>Zahlbar durch</h4>
@@ -481,7 +481,7 @@ article {
<div class="qr-code">
<div class="qr-section-img">
<image src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIAAQMAAADOtka5AAAABlBMVEX///8AAABVwtN&#43;AAAGg0lEQVR42uydvY2tQAyFjQgmpARKoTQojVIogZAArZ/sc2y4&#43;yq4syZaIe63yci/xx5RPMd0ij2j/oiKbrLoLjLp2a6mtwyqKrLqvughIk2vkT8sQAEA2Ox3swGa2qcGkHUD4BRpl/jvVUTW3T6eTgPc/g8L0A1gVd3nQ2S62j3eAz9V3WU&#43;ZbqkGXXAsdmXY/Y/Ln5ZgAJ8AqZzuprGs627LKpupZreBvCDJ4ses31ZgE4BMqnqbY7JH3dC8bidwpdqnmnSSwrQGwCeyQDx2oMR1WNW90yj/gzkLvhXl8h/rq0AfxyQoS49E0LddV/CTJnxMYuk27rZS1qk/2PlAnwxIJ4Jbmj0dx6L6MFQl9/KuuW3dpR&#43;PQX42wBzWDuiGfvULJInTfuyyxy&#43;LSJdC3UtAEbOZIdWC9AJwFyTJTLnZCGO&#43;RsPdS3jOeLIDK&#43;DZO7K8unR3VUBOgF42OFvpyiMMPWFQWkO8ABldaw8nukHvy9AAYShLssl5pj8eK1RLjHrc8uAVCqLspZQu5nKWLkA3w7QbfFQ1769eZIiQsG3tzx5kLsrVuzFCysFKIADNrNIaOgo&#43;jlufAQAL8qOPzKE8ZrREGqo16DLU4AOAB7iML0R5s6f5ZKIZjYvyqodJPXWzytGKsC3A/z14Z295kZC6YNwvK44MxLdPkQowgi4AAWgJmDD6ZIJ7kbornZPj1CARVF2pROzLNtbPwiLC9ADwIORp1ziFknDTMFdqRfn8VJmlOyd&#43;tTdClCATXi8pF3jPeqrGEeThNzZD633gxD/ZhWnAH0AmMicACB&#43;RaH2eFfhM&#43;U5aLuYBxWgCwBKrayWhH7APdNC0xFnRtZt3SOWucTzoAIUgABIj1h/RS0OeVTEvxJBLVRKXrez38NdFaAXAOpmyIhDeeTJzaKMdL0VrJHyIKP2wgprKAUoAFIetAvZ0EFn0F&#43;zW4iGjgVDa8bEl5kuVnUL0APgJT0KGQmlAq6CdrUaGnsrNWw8XlmEKUAHAJ4Ze8tyibLb56YDx8uF7&#43;z62km6ouxWgAIQ4Cfx1e1jtQQd4gkBcPSSUViB/PUeNZOuAnw/IBt7U2rkNYrz84kA&#43;H7ZHvgwTNKgZF&#43;AAkhoWlXzePEk7ijKairbsoc4x9AOguICdAHwVvCe54j&#43;ZkNyk4JnKOcl34a7kgJ0AshQV0/IVyVfinCCQp9aGhVGmmMVBSjA8Mz2RdltZDNYtwWDn&#43;j9DLRTr4aQhOqxAF0AEKHQNeW8nayaZkpDVOC1NPNMwqFgn6AoQBeA9/CUYDxGuERi9qKqS9Cy67scEl4sDEoBCpAAJzS9x9weIAvnw1/1V8HUuL3LPQMF6AMQozTYP8OU52kFe3YDjbuHusiSr/BMESwX4M8DNFWL0mK2j/IBTJI/0iMv0bkuLXWuUoBOAGjwPtNXfmRCPoCi7D1G7pz7JlzoGkWUAnQAiLmIU1iGT&#43;V77EDLbh9Nz&#43;HrzlxHoioFKEDo0hDMwDPdaWeoaW1Xu59vfXmAW6TxUZwUoAeAcCOA5TxYIsEjsxzz&#43;VqVSG3JrLERixMYBShARKoxdcOcKaYiorGHUZzcQkGR/XtovADfD9jWbPe1WzjBC3fFNVcYoUixGnQkrNgXoBcAttLE1rvxU1tyTrGf6FmqePxee1eAAjBuiRiH2dHnZokLU3g4s/RkbpGemn8BugBwzVUcJOTDSxbnuVyTNRQuD0iNUQEK8BrJgvG5ud7IV5T4BMWn0FUQDcVGAZb8C9ABIJaRYG2z3rmt1w7SyQsFoBT4pYJ&#43;thMV4PsBz1Lvs1GXliOamPmORUShUz0iH8YezQIUgAtZd4gCcBdJbox/Le1lnryylzxxmR6VsgXoAUBNgBmfdsWCZjohhC0SG622kA9gzZUMec1AAQqQW2mgnL9Z3F8VewIsmtHYfhWrfXPerwA9ATR&#43;r3l1QObOTwklun3wVrn/uwB9AHLz/0mA5nJ3bswMf&#43;UGxVNfhZwIC2wKUAB530XCzWa54VnpmV450zPjiQtK0iIV4PsBr9sTrxA8s51zxO2JT9f3Y4JCU4dSgALkbWloF&#43;ZIlgPytjR9z/bpM&#43;OpBegLMLGGkqsl9hAKxIUCvE8Aa67a/Wx4LkBPgDNNx6MxCtUiJijWGM1jPpxFmAIU4HV74tmeSivvNXNNQSNANy5knXAtL8TVBegE8HF74sfd3VhZ5FLE1KVRRtLedw8U4PsB/wIAAP//irIo4ZGkhQwAAAAASUVORK5CYII="></image>
<image src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIAAQMAAADOtka5AAAABlBMVEX///8AAABVwtN&#43;AAAGaklEQVR42uydzbGkOgyF1cXCS0IgFEKD0AiFEFiyoNAr6xzJ7vsiaI96NXXLfLNx6fdIFsXvnC&#43;pv0lfUdFdVj1EZr3KXfSRj6qKbHqseopI0XvihwlIAAB7/W6pgKL1aAXItgNwiZRb7HsVke2oh&#43;erAh77DxMwDGBTPZZTZL7LMz0fHlU9ZLlkvqVU6gfX5ljPxf5x82QCEvANmK/5xlUk4FhVzUoVfSrALp6sei71ZAIGBcistwjsTDVJ9Xv/mZ3CSa2eqR5NwGgAeCYH0AltsB3mmSZ9K8Du0Z//KgEJcECEuvRMCHW3Y3UzVY1PtUi6b3v9Iy3S/2PlBPwwwH8z3NDUApT1ZKjLs7LtcbZepT&#43;/BPzbgOqwGM3UozXEoRc7ZHHf5pGuUZdLkDPVS6sJGANg7sacEAMXy50tSzYnZFfm012k6q5qPj2Zu0rASID61/mS8kzubDY3KMUAFqBs9c6JNM&#43;E/ysBCaDxOeBjCo2PiFpV9lyqoUJN9nWqFWXtbDVTTLoS8PsANcdkBqkwjanfix&#43;1WprnQXBXrNibE0tAAgBg/Drjzv3t51hRdnrl48ZrQUOoGq/307o8Cfh1QNwky3ge1mT3rlzi0cxuRdl6tKi1fl6voSTg9wE7Q11VK857k&#43;ZYD0H51e4Mbpt1&#43;xChMO9NQAIAsA6x6lmzo/JM4ZssqEUixZsom&#43;JozbK99ZOAQQBmkJDzqDf78P1aqRfj35dxiywo2RvV7lwCEmCAXXi9pNyTJc9sF0Jzop4726W1ygziXy/CJGAQABOZC4DX3A0KtWdfhY&#43;U55Q5tGp9ATcBvwxgVItqiTIYUd1X7/rGnZFt3471FFbYLA9KQAII0H07xOuvk/r3NT3CWfuhy7Mz1LXv6a4SMAhAva93lVAeWXKzKiNdaSV77/3cVlixkwlIgHyFumzooDOIpAmuDQ2dGgxt0Uq&#43;q&#43;nyYlwCBgDsG6KZmvI8wlDXAKaCrogJjb2NGjZeryjOJ2AMAFu5lvlO7PZ5UIvrZcJ3dn2t6ibl8TuXgAS0&#43;utyQpdmka4FM9bmmREAwyBZ6mzNZHZ5mr9KwM8DosFbww5F7hzu6kIA3EbzFN8XTNK8IglIgAM4beG9YLZueBM1lG2hnF8wtMPrmYAxAF1Uy4aO0iJh8JMZtSnnmfJ07koSMAjADIp5JstnqQ2JqV5IEVstzRVsMVaRgAR0AC&#43;7AdDiHtxEc0KqXw0hkd61JeDXAVacV7qmKYRpvkSCM54SMpLqmRD&#43;WkKtCRgDwMk8sx0SGncDLFZUNQladH3XU8h9Pm83opmABFBkZDUUlN3UK3QoxqH&#43;6uEQpUvdTUzAAAAan5rl2qBV/FVM4s7NEuKhLrLku19Vk4AEYOoGqfN8&#43;dGQUdvQeJMeWSZlurTSGa8EDAFAg/dE2cyvDEZpTkFRFomQz1qw66vPJ4ooCfh9gM9FXG00j9uJfAdadPs4zXnaurPIjhKQAOrSVoS6pjJ6ov5KTWuJygoy6lNgkaamOEnACAAGI9UiyRSTNBivubpVia4piKiYcqIEJEAYqR6ovyJnalMR3tjDKE5soaDIntuREjAKQJtS4HH9ANwV11whqg2xGnQkU1&#43;MS8DPA5D8xlLEyccqmliN9ddYrnnOFC2yQZyABFAo4AsjbMbzw7EK8et5o5fc7y3BUkVx&#43;UACBgCgQIax3q7Lw/GaOXYxU1RAiXzTGCUgARqbjL4WcYZrw&#43;6rTuhqDeZFfaMA19okYADAt2cKsVnLg7BZ4pVuxJPDFpKAgQA17ICZQKiLCQob0cTMt6sWXad6ej7MPZoJSADaxrJ65BLru1sw06mUoFg5fSMWa3EJGAJATQAFI1OnNcOGEZoejuZxKXjRu&#43;/2JSABX1tpeFh9eQDXXHkzOVb7xrxfAkYCcBmJNYj96QDx3VdRQvFuH2dufP93AsYAxOb/iwCN5e7cmOlCVzMoFstokxMlIAF/3iJpl86imVXpmbqcqc14xiNXkoBBAN3riZ7H9H&#43;UWJnJUKZNUHQ6lAQkIF5Lg0nyXXiY4ovX0rSf7dM246kJGAsQ&#43;kZYJI0XeKOz5w8W2Zqr8rQNzwkYDPDn5aolVIuYoNh8NK/N3HQGJQH/OsBl0FdplVa&#43;a2Z1lUKA7lzIOuNZXoirEzAI4Ov1xK&#43;3u7mySJ9Ol0YZSfl&#43;eyABvw74LwAA//9wYD7CB3CrcAAAAABJRU5ErkJggg=="></image>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 20.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
@@ -592,7 +592,7 @@ article {
</div>
<div>
<h4>Referenz</h4>
<p>20 25082 32224 30630 09759 35659</p>
<p>20 25082 41057 10271 08692 82635</p>
</div>
<div>
<h4>Zahlbar durch</h4>

15
mailer/model.go Normal file
View File

@@ -0,0 +1,15 @@
package mailer
import "io"
type Mail struct {
TO string
Subject string
Body string
Attachments []Attachment
}
type Attachment struct {
Name string
MimeType string
Content io.Reader
}

52
mailer/resource.go Normal file
View File

@@ -0,0 +1,52 @@
package mailer
import (
"crypto/tls"
"net/smtp"
"github.com/jordan-wright/email"
)
type Config struct {
SMTP SMTPConfig
From string
}
type SMTPConfig struct {
Host string
Port string
User string
Password string
}
type Service struct {
from string
pool *email.Pool
cfg Config
}
func New(cfg Config) (s *Service, err error) {
p, err := email.NewPool(
cfg.SMTP.Host+":"+cfg.SMTP.Port,
1,
smtp.PlainAuth(
"",
cfg.SMTP.User,
cfg.SMTP.Password,
cfg.SMTP.Host,
), &tls.Config{
InsecureSkipVerify: true, // set false in production
ServerName: cfg.SMTP.Host, // must match the SMTP host
})
if err != nil {
return
}
s = &Service{
from: cfg.From,
pool: p,
cfg: cfg,
}
return
}

33
mailer/send.go Normal file
View File

@@ -0,0 +1,33 @@
package mailer
import (
"crypto/tls"
"net/smtp"
"github.com/jordan-wright/email"
)
func (s Service) Send(m Mail) (err error) {
e := email.NewEmail()
e.To = []string{m.TO}
e.Bcc = []string{"niklas@sunway.ch"}
e.From = s.from
e.Subject = m.Subject
e.Text = []byte(m.Body)
for _, a := range m.Attachments {
e.Attach(a.Content, a.Name, a.MimeType)
}
err = e.SendWithTLS(s.cfg.SMTP.Host+":"+s.cfg.SMTP.Port, smtp.PlainAuth(
s.cfg.SMTP.User,
s.cfg.SMTP.User,
s.cfg.SMTP.Password,
s.cfg.SMTP.Host,
), &tls.Config{
ServerName: s.cfg.SMTP.Host,
})
// err = s.pool.Send(e, time.Second*5)
return
}

35
main.go
View File

@@ -1,12 +1,12 @@
package main
import (
"io"
"os"
"time"
"code.gitea.io/sdk/gitea"
"git.schreifuchs.ch/lou-taylor/accounting/issue"
"git.schreifuchs.ch/lou-taylor/accounting/mailer"
"git.schreifuchs.ch/lou-taylor/accounting/model"
"git.schreifuchs.ch/lou-taylor/accounting/pdf"
"git.schreifuchs.ch/lou-taylor/accounting/report"
@@ -99,9 +99,38 @@ func main() {
panic(err)
}
ducument, err := pdfs.HtmlToPdf(html)
document, err := pdfs.HtmlToPdf(html)
if err != nil {
panic(err)
}
io.Copy(os.Stdout, ducument)
mlr, err := mailer.New(mailer.Config{
SMTP: mailer.SMTPConfig{
Host: "mail.your-server.de",
Port: "465",
User: "test@schreifuchs.ch",
Password: "xV27D1nj33dNz8B4",
},
From: "test@schreifuchs.ch",
})
if err != nil {
panic(err)
}
err = mlr.Send(mailer.Mail{
TO: "kontakt@schreifuchs.ch",
Subject: "test",
Body: "Hallo",
Attachments: []mailer.Attachment{
{
Name: "invoice.pdf",
MimeType: "pdf",
Content: document,
},
},
})
if err != nil {
panic(err)
}
}
func Filter[T any](slice []T, ok func(T) bool) []T {

View File

@@ -25,13 +25,6 @@ func (s Service) HtmlToPdf(html string) (pdf io.ReadCloser, err error) {
}
req := gotenberg.NewHTMLRequest(index)
req.PaperSize(gotenberg.A4)
// req.Margins(gotenberg.PageMargins{
// Top: 0.5,
// Bottom: 0.5,
// Left: 0.5,
// Right: 0.6,
// Unit: gotenberg.IN,
// })
req.Margins(gotenberg.NoMargins)
// Skips the IDLE events for faster PDF conversion.