Repository: gogs/gogs
File: internal/email/message.go (Go)

Code before:

func (cw *countingWriter) Write(p []byte) (int, error) {
	n, err := cw.w.Write(p)
	cw.n += int64(n)
	return n, err
}

func (m *message) WriteTo(w io.Writer) (int64, error) {
	cw := &countingWriter{w: w}

	for _, field := range []string{"From", "To", "Subject", "Date"} {
		vals := m.header[field]
		if len(vals) == 0 {
			continue
		}

		var encoded string
		if field == "Subject" {
			encoded = mime.QEncoding.Encode("utf-8", vals[0])
		} else {
			encoded = sanitizeHeaderValue(strings.Join(vals, ", "))
		}
		if _, err := fmt.Fprintf(cw, "%s: %s\r\n", field, encoded); err != nil {
			return cw.n, errors.Wrap(err, "write header")
		}
	}

	if len(m.altParts) > 0 {
		mw := multipart.NewWriter(cw)
		if _, err := fmt.Fprintf(cw, "MIME-Version: 1.0\r\n"); err != nil {
			return cw.n, errors.Wrap(err, "write MIME version")
		}
		if _, err := fmt.Fprintf(cw, "Content-Type: multipart/alternative; boundary=%s\r\n\r\n", mw.Boundary()); err != nil {
			return cw.n, errors.Wrap(err, "write content type")
		}

		mainHeader := textproto.MIMEHeader{}
		mainHeader.Set("Content-Type", m.contentType+"; charset=UTF-8")
		mainHeader.Set("Content-Transfer-Encoding", "quoted-printable")
		part, err := mw.CreatePart(mainHeader)
		if err != nil {
			return cw.n, errors.Wrap(err, "create main part")
		}
		if err := qpWrite(part, m.body); err != nil {
			return cw.n, errors.Wrap(err, "write main body")
		}

		for _, alt := range m.altParts {
			altHeader := textproto.MIMEHeader{}
			altHeader.Set("Content-Type", alt.contentType+"; charset=UTF-8")
			altHeader.Set("Content-Transfer-Encoding", "quoted-printable")

Review comment (suggestion):
`WriteTo` manually formats the `To`/`From`/etc headers and joins address lists onto a single line. For issue notifications the `To` list can be very large (watchers/participants), which can easily exceed RFC 5322 line length limits and cause deliverability issues or outright rejection by strict MTAs. Consider writing message headers via `textproto.Writer.WriteMIMEHeader` (which handles folding) or otherwise implement proper header folding for long address lists before writing.

Code after:
	return &message{
		msg:         m,
		confirmChan: make(chan struct{}),
	}, nil
}

func newMessage(to []string, subject, body string) (*message, error) {
	return newMessageFrom(to, conf.Email.From, subject, body)
}

func newSMTPClient() (*gomail.Client, error) {
	opts := conf.Email

	host, portStr, err := net.SplitHostPort(opts.Host)
	if err != nil {
		return nil, err
	}
	port, err := strconv.Atoi(portStr)
	if err != nil {
		return nil, err
	}

	clientOpts := []gomail.Option{
		gomail.WithPort(port),
	}

	if port == 465 {
		clientOpts = append(clientOpts, gomail.WithSSL())
	} else {
		clientOpts = append(clientOpts, gomail.WithTLSPolicy(gomail.TLSOpportunistic))
	}

	if opts.HELOHostname != "" {
		clientOpts = append(clientOpts, gomail.WithHELO(opts.HELOHostname))
	}

	tlsconfig := &tls.Config{
		InsecureSkipVerify: opts.SkipVerify,
		ServerName:         host,
	}
	if opts.UseCertificate {
		cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile)
		if err != nil {
			return nil, err
		}
		tlsconfig.Certificates = []tls.Certificate{cert}
	}
	clientOpts = append(clientOpts, gomail.WithTLSConfig(tlsconfig))

	if len(opts.User) > 0 {
		clientOpts = append(clientOpts,