ronantakizawa/codereview-bench huggingface dataset:
Repository: gogs/gogs
File: internal/database/repo_test.go (Go)

Code before:

	t.Run("an external issue tracker is configured", func(t *testing.T) {
		repo.ExternalMetas = nil
		repo.EnableExternalTracker = true

		// Default to numeric issue style
		assert.Equal(t, markup.IssueNameStyleNumeric, repo.ComposeMetas()["style"])
		repo.ExternalMetas = nil

		repo.ExternalTrackerStyle = markup.IssueNameStyleNumeric
		assert.Equal(t, markup.IssueNameStyleNumeric, repo.ComposeMetas()["style"])
		repo.ExternalMetas = nil

		repo.ExternalTrackerStyle = markup.IssueNameStyleAlphanumeric
		assert.Equal(t, markup.IssueNameStyleAlphanumeric, repo.ComposeMetas()["style"])
		repo.ExternalMetas = nil

		metas := repo.ComposeMetas()
		assert.Equal(t, "testuser", metas["user"])
		assert.Equal(t, "testrepo", metas["repo"])
		assert.Equal(t, "https://someurl.com/{user}/{repo}/{issue}", metas["format"])
	})
}
func Test_CreateRepository_PreventDeletion(t *testing.T) {
	owner := &User{Name: "testuser"}
	opts := CreateRepoOptionsLegacy{Name: "safety-test"}

	repoPath := RepoPath(owner.Name, opts.Name)

	// Check the error for MkdirAll
	err := os.MkdirAll(repoPath, os.ModePerm)
	assert.NoError(t, err)

	canary := filepath.Join(repoPath, "canary.txt")

	// Check the error for WriteFile
	err = os.WriteFile(canary, []byte("should survive"), 0644)
	assert.NoError(t, err)

	_, err = CreateRepository(owner, owner, opts)
	if err == nil {
		t.Fatal("Expected error when directory exists, but got nil")
	}

	if !osutil.IsExist(canary) {
		t.Error("CRITICAL: The existing directory was deleted during CreateRepository failure!")
	}
}


Review comment (suggestion):
```suggestion
}

func Test_CreateRepository_PreventDeletion(t *testing.T) {
```

Code after:
	})

	t.Run("an external issue tracker is configured", func(t *testing.T) {
		repo.ExternalMetas = nil
		repo.EnableExternalTracker = true

		// Default to numeric issue style
		assert.Equal(t, markup.IssueNameStyleNumeric, repo.ComposeMetas()["style"])
		repo.ExternalMetas = nil

		repo.ExternalTrackerStyle = markup.IssueNameStyleNumeric
		assert.Equal(t, markup.IssueNameStyleNumeric, repo.ComposeMetas()["style"])
		repo.ExternalMetas = nil

		repo.ExternalTrackerStyle = markup.IssueNameStyleAlphanumeric
		assert.Equal(t, markup.IssueNameStyleAlphanumeric, repo.ComposeMetas()["style"])
		repo.ExternalMetas = nil

		metas := repo.ComposeMetas()
		assert.Equal(t, "testuser", metas["user"])
		assert.Equal(t, "testrepo", metas["repo"])
		assert.Equal(t, "https://someurl.com/{user}/{repo}/{issue}", metas["format"])
	})
}

func Test_CreateRepository_PreventDeletion(t *testing.T) {
	owner := &User{Name: "testuser"}
	opts := CreateRepoOptionsLegacy{Name: "safety-test"}
	repoPath := RepoPath(owner.Name, opts.Name)
	require.NoError(t, os.MkdirAll(repoPath, os.ModePerm))

	canary := filepath.Join(repoPath, "canary.txt")
	require.NoError(t, os.WriteFile(canary, []byte("should survive"), 0644))

	_, err := CreateRepository(owner, owner, opts)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "repository directory already exists")
	assert.True(t, osutil.IsExist(canary))
}
Repository: gogs/gogs
File: internal/database/schemadoc/main.go (Go)

Code before:
package main

import (
	"fmt"
	"log"
	"os"
	"sort"
	"strings"

	"github.com/cockroachdb/errors"
	"github.com/glebarez/sqlite"
	"github.com/olekukonko/tablewriter"
	"github.com/olekukonko/tablewriter/renderer"
	"github.com/olekukonko/tablewriter/tw"
	"gopkg.in/DATA-DOG/go-sqlmock.v2"
	"gorm.io/driver/mysql"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/schema"

	"gogs.io/gogs/internal/database"
)

//go:generate go run main.go ../../../docs-old/dev/database_schema.md

func main() {
	w, err := os.Create(os.Args[1])
	if err != nil {
		log.Fatalf("Failed to create file: %v", err)
	}
	defer func() { _ = w.Close() }()

	conn, _, err := sqlmock.New()
	if err != nil {
		log.Fatalf("Failed to get mock connection: %v", err)
	}
	defer func() { _ = conn.Close() }()

	dialectors := []gorm.Dialector{
		postgres.New(postgres.Config{
			Conn: conn,
		}),
		mysql.New(mysql.Config{
			Conn:                      conn,
			SkipInitializeWithVersion: true,
		}),
		sqlite.Open(""),
	}
	collected := make([][]*tableInfo, 0, len(dialectors))

Review comment (suggestion):
The PR description says this is only migrating the go-sqlmock import path, but this hunk also changes the go:generate output location (docs-old -> docs). If this is intentional (docs-old is gone), consider updating the PR description/title or splitting this into a separate PR to keep the change scoped and easier to review/audit.

Code after:
package main

import (
	"fmt"
	"log"
	"os"
	"sort"
	"strings"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/cockroachdb/errors"
	"github.com/glebarez/sqlite"
	"github.com/olekukonko/tablewriter"
	"github.com/olekukonko/tablewriter/renderer"
	"github.com/olekukonko/tablewriter/tw"
	"gorm.io/driver/mysql"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/schema"

	"gogs.io/gogs/internal/database"
)

//go:generate go run main.go ../../../docs/dev/database_schema.md

func main() {
	w, err := os.Create(os.Args[1])
	if err != nil {
		log.Fatalf("Failed to create file: %v", err)
	}
	defer func() { _ = w.Close() }()

	conn, _, err := sqlmock.New()
	if err != nil {
		log.Fatalf("Failed to get mock connection: %v", err)
	}
	defer func() { _ = conn.Close() }()

	dialectors := []gorm.Dialector{
		postgres.New(postgres.Config{
			Conn: conn,
		}),
		mysql.New(mysql.Config{
			Conn:                      conn,
			SkipInitializeWithVersion: true,
		}),
		sqlite.Open(""),
	}
	collected := make([][]*tableInfo, 0, len(dialectors))
Repository: gogs/gogs
File: internal/database/schemadoc/main.go (Go)

Code before:
package main

import (
	"fmt"
	"log"
	"os"
	"sort"
	"strings"

	"github.com/cockroachdb/errors"
	"github.com/glebarez/sqlite"
	"github.com/olekukonko/tablewriter"
	"github.com/olekukonko/tablewriter/renderer"
	"github.com/olekukonko/tablewriter/tw"
	"gopkg.in/DATA-DOG/go-sqlmock.v2"
	"gorm.io/driver/mysql"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/schema"

	"gogs.io/gogs/internal/database"
)

//go:generate go run main.go ../../../docs/dev/database_schema.md

func main() {
	w, err := os.Create(os.Args[1])
	if err != nil {
		log.Fatalf("Failed to create file: %v", err)
	}
	defer func() { _ = w.Close() }()

	conn, _, err := sqlmock.New()
	if err != nil {
		log.Fatalf("Failed to get mock connection: %v", err)
	}
	defer func() { _ = conn.Close() }()

	dialectors := []gorm.Dialector{
		postgres.New(postgres.Config{
			Conn: conn,
		}),
		mysql.New(mysql.Config{
			Conn:                      conn,
			SkipInitializeWithVersion: true,
		}),
		sqlite.Open(""),
	}
	collected := make([][]*tableInfo, 0, len(dialectors))
	for i, dialector := range dialectors {

Review comment (suggestion):
This PR is described as migrating from json-iterator to `encoding/json`, but this hunk also changes the `go:generate` output path for the schema doc. If this is intentional cleanup due to `docs-old/` removal, consider updating the PR description/title to reflect the additional scope, or splitting it into a separate PR to keep changes focused.

Code after:
package main

import (
	"fmt"
	"log"
	"os"
	"sort"
	"strings"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/cockroachdb/errors"
	"github.com/glebarez/sqlite"
	"github.com/olekukonko/tablewriter"
	"github.com/olekukonko/tablewriter/renderer"
	"github.com/olekukonko/tablewriter/tw"
	"gorm.io/driver/mysql"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/schema"

	"gogs.io/gogs/internal/database"
)

//go:generate go run main.go ../../../docs/dev/database_schema.md

func main() {
	w, err := os.Create(os.Args[1])
	if err != nil {
		log.Fatalf("Failed to create file: %v", err)
	}
	defer func() { _ = w.Close() }()

	conn, _, err := sqlmock.New()
	if err != nil {
		log.Fatalf("Failed to get mock connection: %v", err)
	}
	defer func() { _ = conn.Close() }()

	dialectors := []gorm.Dialector{
		postgres.New(postgres.Config{
			Conn: conn,
		}),
		mysql.New(mysql.Config{
			Conn:                      conn,
			SkipInitializeWithVersion: true,
		}),
		sqlite.Open(""),
	}
	collected := make([][]*tableInfo, 0, len(dialectors))
	for i, dialector := range dialectors {
Repository: gogs/gogs
File: cmd/gogs/admin.go (Go)

Code before:

func runCreateUser(_ context.Context, cmd *cli.Command) error {
	if !cmd.IsSet("name") {
		return errors.New("Username is not specified")
	} else if !cmd.IsSet("password") {
		return errors.New("Password is not specified")
	} else if !cmd.IsSet("email") {
		return errors.New("Email is not specified")
	}

	err := conf.Init(cmd.String("config"))
	if err != nil {
		return errors.Wrap(err, "init configuration")
	}
	conf.InitLogging(true)

	if _, err = database.SetEngine(); err != nil {
		return errors.Wrap(err, "set engine")
	}

	user, err := database.Handle.Users().Create(
		context.Background(),
		cmd.String("name"),
		cmd.String("email"),
		database.CreateUserOptions{
			Password:  cmd.String("password"),
			Activated: true,
			Admin:     cmd.Bool("admin"),
		},
	)
	if err != nil {
		return errors.Wrap(err, "create user")
	}

	fmt.Printf("New user %q has been successfully created!\n", user.Name)
	return nil
}

func adminDashboardOperation(operation func() error, successMessage string) func(context.Context, *cli.Command) error {
	return func(_ context.Context, cmd *cli.Command) error {
		err := conf.Init(cmd.String("config"))
		if err != nil {
			return errors.Wrap(err, "init configuration")
		}
		conf.InitLogging(true)

		if _, err = database.SetEngine(); err != nil {
			return errors.Wrap(err, "set engine")
		}

		if err := operation(); err != nil {

Review comment (performance):
`runCreateUser` ignores the action context and uses `context.Background()` for the DB call. This prevents cancellation/timeout propagation (e.g., on SIGINT) during potentially slow DB operations. Prefer threading the `ctx` parameter through and passing it to `database.Handle.Users().Create` (and avoid naming it `_`).

Code after:

func runCreateUser(ctx context.Context, cmd *cli.Command) error {
	if !cmd.IsSet("name") {
		return errors.New("Username is not specified")
	} else if !cmd.IsSet("password") {
		return errors.New("Password is not specified")
	} else if !cmd.IsSet("email") {
		return errors.New("Email is not specified")
	}

	err := conf.Init(configFromLineage(cmd))
	if err != nil {
		return errors.Wrap(err, "init configuration")
	}
	conf.InitLogging(true)

	if _, err = database.SetEngine(); err != nil {
		return errors.Wrap(err, "set engine")
	}

	user, err := database.Handle.Users().Create(
		ctx,
		cmd.String("name"),
		cmd.String("email"),
		database.CreateUserOptions{
			Password:  cmd.String("password"),
			Activated: true,
			Admin:     cmd.Bool("admin"),
		},
	)
	if err != nil {
		return errors.Wrap(err, "create user")
	}

	fmt.Printf("New user %q has been successfully created!\n", user.Name)
	return nil
}

func adminDashboardOperation(operation func() error, successMessage string) func(context.Context, *cli.Command) error {
	return func(_ context.Context, cmd *cli.Command) error {
		err := conf.Init(configFromLineage(cmd))
		if err != nil {
			return errors.Wrap(err, "init configuration")
		}
		conf.InitLogging(true)

		if _, err = database.SetEngine(); err != nil {
			return errors.Wrap(err, "set engine")
		}

		if err := operation(); err != nil {
Repository: gogs/gogs
File: internal/database/repo_editor.go (Go)

Code before:
package database

import (
	"fmt"
	"io"
	"mime/multipart"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/cockroachdb/errors"
	gouuid "github.com/satori/go.uuid"

	"github.com/gogs/git-module"

	"gogs.io/gogs/internal/conf"
	"gogs.io/gogs/internal/cryptoutil"
	"gogs.io/gogs/internal/gitutil"
	"gogs.io/gogs/internal/ioutil"
	"gogs.io/gogs/internal/osutil"
	"gogs.io/gogs/internal/pathutil"
	"gogs.io/gogs/internal/process"
)

// BranchAlreadyExists represents an error when branch already exists.
type BranchAlreadyExists struct {
	Name string
}

// IsBranchAlreadyExists returns true if the error is BranchAlreadyExists.
func IsBranchAlreadyExists(err error) bool {
	_, ok := err.(BranchAlreadyExists)
	return ok
}

func (err BranchAlreadyExists) Error() string {
	return fmt.Sprintf("branch already exists [name: %s]", err.Name)

Review comment (suggestion):
This file has multiple functions with parameters named `uuid` (e.g. `UploadLocalPath(uuid string)`, `GetUploadByUUID(uuid string)`), which will shadow the imported `uuid` package name inside those functions. Consider aliasing the import (e.g. `uuidpkg "github.com/google/uuid"`) to avoid shadowing and make future changes less error-prone.
```suggestion
	uuidpkg "github.com/google/uuid"
```

Code after:
package database

import (
	"fmt"
	"io"
	"mime/multipart"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/cockroachdb/errors"
	"github.com/google/uuid"

	"github.com/gogs/git-module"

	"gogs.io/gogs/internal/conf"
	"gogs.io/gogs/internal/cryptoutil"
	"gogs.io/gogs/internal/gitutil"
	"gogs.io/gogs/internal/ioutil"
	"gogs.io/gogs/internal/osutil"
	"gogs.io/gogs/internal/pathutil"
	"gogs.io/gogs/internal/process"
)

// BranchAlreadyExists represents an error when branch already exists.
type BranchAlreadyExists struct {
	Name string
}

// IsBranchAlreadyExists returns true if the error is BranchAlreadyExists.
func IsBranchAlreadyExists(err error) bool {
	_, ok := err.(BranchAlreadyExists)
	return ok
}

func (err BranchAlreadyExists) Error() string {
	return fmt.Sprintf("branch already exists [name: %s]", err.Name)
Repository: gogs/gogs
File: internal/database/webhook.go (Go)

Code before:
package database

import (
	"crypto/hmac"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"fmt"
	"io"
	"net/url"
	"strings"
	"time"

	"github.com/cockroachdb/errors"
	jsoniter "github.com/json-iterator/go"
	gouuid "github.com/satori/go.uuid"
	log "unknwon.dev/clog/v2"
	"xorm.io/xorm"

	api "github.com/gogs/go-gogs-client"

	"gogs.io/gogs/internal/conf"
	"gogs.io/gogs/internal/errutil"
	"gogs.io/gogs/internal/httplib"
	"gogs.io/gogs/internal/netutil"
	"gogs.io/gogs/internal/sync"
	"gogs.io/gogs/internal/testutil"
)

var HookQueue = sync.NewUniqueQueue(1000)

type HookContentType int

const (
	JSON HookContentType = iota + 1
	FORM
)

var hookContentTypes = map[string]HookContentType{
	"json": JSON,

Review comment (suggestion):
This file declares functions that take a `uuid` parameter (e.g. `GetHookTaskOfWebhookByUUID(..., uuid string)`), which will shadow the imported `uuid` package name within those scopes. Consider importing `github.com/google/uuid` with an alias to avoid identifier shadowing and keep UUID package calls unambiguous.
```suggestion
	uuidpkg "github.com/google/uuid"
```

Code after:
package database

import (
	"crypto/hmac"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"fmt"
	"io"
	"net/url"
	"strings"
	"time"

	"github.com/cockroachdb/errors"
	"github.com/google/uuid"
	jsoniter "github.com/json-iterator/go"
	log "unknwon.dev/clog/v2"
	"xorm.io/xorm"

	api "github.com/gogs/go-gogs-client"

	"gogs.io/gogs/internal/conf"
	"gogs.io/gogs/internal/errutil"
	"gogs.io/gogs/internal/httplib"
	"gogs.io/gogs/internal/netutil"
	"gogs.io/gogs/internal/sync"
	"gogs.io/gogs/internal/testutil"
)

var HookQueue = sync.NewUniqueQueue(1000)

type HookContentType int

const (
	JSON HookContentType = iota + 1
	FORM
)

var hookContentTypes = map[string]HookContentType{
	"json": JSON,
Repository: gogs/gogs
File: internal/email/message.go (Go)

Code before:

	if len(m.altParts) > 0 {
		mw := multipart.NewWriter(&buf)
		fmt.Fprintf(&buf, "MIME-Version: 1.0\r\n")
		fmt.Fprintf(&buf, "Content-Type: multipart/alternative; boundary=%s\r\n\r\n", mw.Boundary())

		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 0, errors.Wrap(err, "create main part")
		}
		qpWrite(part, m.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")
			part, err := mw.CreatePart(altHeader)
			if err != nil {
				return 0, errors.Wrap(err, "create alternative part")
			}
			qpWrite(part, alt.body)
		}
		mw.Close()
	} else {
		fmt.Fprintf(&buf, "MIME-Version: 1.0\r\n")
		fmt.Fprintf(&buf, "Content-Type: %s; charset=UTF-8\r\n", m.contentType)
		fmt.Fprintf(&buf, "Content-Transfer-Encoding: quoted-printable\r\n\r\n")
		qpWrite(&buf, m.body)
	}

	n, err := w.Write(buf.Bytes())
	return int64(n), err
}

func qpWrite(w io.Writer, s string) {
	for i := 0; i < len(s); i++ {
		b := s[i]
		switch {
		case b == '\r' || b == '\n':
			fmt.Fprintf(w, "%c", b)
		case b == '=' || b > 126 || (b < 32 && b != '\t'):
			fmt.Fprintf(w, "=%02X", b)
		default:
			fmt.Fprintf(w, "%c", b)
		}
	}
}


Review comment (suggestion):
multipart.Writer.Close() can return an error (e.g., when writing the final boundary). The return value is currently ignored, which can lead to silently truncated/invalid MIME messages. Capture and return the Close() error.
```suggestion
		if err := mw.Close(); err != nil {
			return 0, errors.Wrap(err, "close multipart writer")
		}
```

Code after:

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,
			gomail.WithSMTPAuth(gomail.SMTPAuthAutoDiscover),
			gomail.WithUsername(opts.User),
			gomail.WithPassword(opts.Password),
		)
	}
Repository: gogs/gogs
File: internal/email/message.go (Go)

Code before:
	log "unknwon.dev/clog/v2"

	"gogs.io/gogs/internal/conf"
)

type Message struct {
	Info        string
	header      map[string][]string
	contentType string
	body        string
	altParts    []altPart
	confirmChan chan struct{}
}

type altPart struct {
	contentType string
	body        string
}

func (m *Message) GetHeader(field string) []string {
	return m.header[field]
}

func (m *Message) WriteTo(w io.Writer) (int64, error) {
	var buf bytes.Buffer

	for _, field := range []string{"From", "To", "Subject", "Date"} {
		vals := m.header[field]
		for _, v := range vals {
			encoded := v
			if field == "Subject" {
				encoded = mime.QEncoding.Encode("utf-8", v)
			}
			fmt.Fprintf(&buf, "%s: %s\r\n", field, encoded)
		}
	}

	if len(m.altParts) > 0 {
		mw := multipart.NewWriter(&buf)
		fmt.Fprintf(&buf, "MIME-Version: 1.0\r\n")
		fmt.Fprintf(&buf, "Content-Type: multipart/alternative; boundary=%s\r\n\r\n", mw.Boundary())

		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 0, errors.Wrap(err, "create main part")
		}
		qpWrite(part, m.body)


Review comment (suggestion):
WriteTo buffers the entire message into a bytes.Buffer and then does a single w.Write. This defeats the benefit of io.WriterTo (streaming) and can cause large allocations for bigger notification emails. Consider writing headers and MIME parts directly to w (and tracking bytes written) instead of staging the full message in memory.

Code after:
	confirmChan chan struct{}
}

func newMessageFrom(to []string, from, subject, htmlBody string) (*message, error) {
	log.Trace("NewMessageFrom (htmlBody):\n%s", htmlBody)

	m := gomail.NewMsg()
	if err := m.From(from); err != nil {
		return nil, errors.Wrapf(err, "set From address %q", from)
	}
	if err := m.To(to...); err != nil {
		return nil, errors.Wrap(err, "set To addresses")
	}
	m.Subject(conf.Email.SubjectPrefix + subject)
	m.SetDate()

	if conf.Email.UsePlainText || conf.Email.AddPlainTextAlt {
		plainBody, err := html2text.FromString(htmlBody)
		if err != nil {
			return nil, errors.Wrap(err, "convert HTML to plain text")
		}
		if conf.Email.UsePlainText {
			m.SetBodyString(gomail.TypeTextPlain, plainBody)
		} else {
			m.SetBodyString(gomail.TypeTextPlain, plainBody)
			m.AddAlternativeString(gomail.TypeTextHTML, htmlBody)
		}
	} else {
		m.SetBodyString(gomail.TypeTextHTML, htmlBody)
	}

	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