refactor: project structure
This commit is contained in:
parent
1bbdbf751b
commit
fc005a74c3
|
@ -1,3 +1,5 @@
|
|||
# dotenv environment variables file
|
||||
.env
|
||||
.env*
|
||||
|
||||
coverage
|
|
@ -0,0 +1,13 @@
|
|||
COVERAGE_DIR=coverage
|
||||
|
||||
lint:
|
||||
golangci-lint run ./...
|
||||
goreportcard:
|
||||
goreportcard-cli -v
|
||||
test:
|
||||
go test ./...
|
||||
test-coverage:
|
||||
rm -rf ${COVERAGE_DIR}
|
||||
mkdir ${COVERAGE_DIR}
|
||||
go test -v -coverprofile ${COVERAGE_DIR}/cover.out ./...
|
||||
go tool cover -html ${COVERAGE_DIR}/cover.out -o ${COVERAGE_DIR}/cover.html
|
42
README.md
42
README.md
|
@ -1,8 +1,8 @@
|
|||
# Mail Sender
|
||||
# Email Sender
|
||||
|
||||
## Description
|
||||
|
||||
`mail-sender` is a simple Go library designed to send emails with optional attachments. It's built on top of the standard Go `net/smtp` library with additional support for sending HTML emails and handling multiple attachments.
|
||||
`email-sender` is a simple Go library designed to send emails with optional attachments. It's built on top of the standard Go `net/smtp` library with additional support for sending HTML emails and handling multiple attachments.
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -15,45 +15,13 @@
|
|||
|
||||
Clone this repository:
|
||||
|
||||
```
|
||||
git clone https://gitea.urkob.com/urko/mail-sender.git
|
||||
```bash
|
||||
git clone https://gitea.urkob.com/urko/emailsender.git
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Here's a basic example on how to use the `mail-sender`:
|
||||
|
||||
1. **Initialize the Email Service**
|
||||
|
||||
```go
|
||||
config := email.MailServiceConfig{
|
||||
Auth: smtp.PlainAuth("", "your@email.com", "your-password", "smtp.youremail.com"),
|
||||
Host: "smtp.youremail.com",
|
||||
Port: "587",
|
||||
From: "your@email.com",
|
||||
}
|
||||
mailService := email.NewMailService(config)
|
||||
```
|
||||
|
||||
2. **Send an Email with an Attachment**
|
||||
|
||||
```go
|
||||
emailData := email.EmailMessage{
|
||||
To: "receiver@email.com",
|
||||
Subject: "Test Email",
|
||||
Body: "<h1>Hello!</h1><p>This is a test email.</p>",
|
||||
Attachments: []email.EmailAttachment{
|
||||
{
|
||||
File: attachmentFile, // This is an io.Reader
|
||||
Title: "document.pdf",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := mailService.SendEmail(emailData)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
Check examples in [examples](https://gitea.urkob.com/urko/emailsender/examples)
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/smtp"
|
||||
|
||||
"gitea.urkob.com/urko/emailsender/pkg/email"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Here fill with real data
|
||||
emailService := email.NewMailService(email.MailServiceConfig{
|
||||
Auth: smtp.PlainAuth("", "your@email.com", "your-password", "smtp.youremail.com"),
|
||||
Host: "smtp.youremail.com",
|
||||
Port: "587",
|
||||
From: "your@email.com",
|
||||
})
|
||||
|
||||
emailService.SendEmail(email.EmailMessage{
|
||||
To: "other@email.com",
|
||||
Subject: "Test Email",
|
||||
Body: "<html><body><p>Here your body, you can attach as html<p/></body></html>",
|
||||
Attachments: []email.EmailAttachment{
|
||||
{
|
||||
File: bytes.NewBuffer([]byte("test")), // This is an io.Reader
|
||||
Title: "document.pdf",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package attachments
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const delimeter = "**=myohmy689407924327"
|
||||
|
||||
type EmailAttachment struct {
|
||||
File io.Reader
|
||||
Title string
|
||||
}
|
||||
|
||||
func (e EmailAttachment) ReadContent() ([]byte, error) {
|
||||
bts, err := io.ReadAll(e.File)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading attachment: %s", err)
|
||||
}
|
||||
return bts, nil
|
||||
}
|
||||
|
||||
func AttachmentsToBytes(body string, attachments []EmailAttachment) ([]byte, error) {
|
||||
var message bytes.Buffer
|
||||
message.WriteString("Content-Type: text/html; charset=\"UTF-8\"\r\n\r\n")
|
||||
message.WriteString(body + "\r\n\r\n")
|
||||
for _, attachment := range attachments {
|
||||
attachmentRawFile, err := attachment.ReadContent()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
message.WriteString("--" + delimeter + "\r\n")
|
||||
message.WriteString("Content-Disposition: attachment; filename=\"" + attachment.Title + "\"\r\n")
|
||||
message.WriteString("Content-Type: application/octet-stream\r\n")
|
||||
message.WriteString("Content-Transfer-Encoding: base64\r\n\r\n")
|
||||
message.WriteString(base64.StdEncoding.EncodeToString(attachmentRawFile) + "\r\n")
|
||||
}
|
||||
message.WriteString("--" + delimeter + "--") // End the message
|
||||
return message.Bytes(), nil
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package smtpclient
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
type SMTPClient struct {
|
||||
client smtp.Client
|
||||
}
|
||||
|
||||
func (c *SMTPClient) Auth(a smtp.Auth) error {
|
||||
return c.client.Auth(a)
|
||||
}
|
||||
|
||||
func (c *SMTPClient) Close() error {
|
||||
return c.client.Close()
|
||||
}
|
||||
|
||||
func (c *SMTPClient) Data() (io.WriteCloser, error) {
|
||||
return c.client.Data()
|
||||
}
|
||||
|
||||
func (c *SMTPClient) Mail(from string) error {
|
||||
return c.client.Mail(from)
|
||||
}
|
||||
|
||||
func (c *SMTPClient) Quit() error {
|
||||
return c.client.Quit()
|
||||
}
|
||||
|
||||
func (c *SMTPClient) Rcpt(to string) error {
|
||||
return c.client.Rcpt(to)
|
||||
}
|
||||
|
||||
func (c *SMTPClient) StartTLS(config *tls.Config) error {
|
||||
return c.client.StartTLS(config)
|
||||
}
|
|
@ -14,7 +14,7 @@ const (
|
|||
delimeter = "**=myohmy689407924327"
|
||||
)
|
||||
|
||||
type smtpClient interface {
|
||||
type SMTPClientIface interface {
|
||||
StartTLS(*tls.Config) error
|
||||
Auth(a smtp.Auth) error
|
||||
Close() error
|
||||
|
@ -24,7 +24,7 @@ type smtpClient interface {
|
|||
Rcpt(to string) error
|
||||
}
|
||||
|
||||
type SmtpDialFn func(hostPort string) (smtpClient, error)
|
||||
type SmtpDialFn func(hostPort string) (SMTPClientIface, error)
|
||||
|
||||
type EmailService struct {
|
||||
auth smtp.Auth
|
||||
|
@ -35,69 +35,6 @@ type EmailService struct {
|
|||
dial SmtpDialFn
|
||||
}
|
||||
|
||||
type SMTPClient struct {
|
||||
client smtp.Client
|
||||
}
|
||||
|
||||
func (c *SMTPClient) Auth(a smtp.Auth) error {
|
||||
return c.client.Auth(a)
|
||||
}
|
||||
func (c *SMTPClient) Close() error {
|
||||
return c.client.Close()
|
||||
}
|
||||
func (c *SMTPClient) Data() (io.WriteCloser, error) {
|
||||
return c.client.Data()
|
||||
}
|
||||
func (c *SMTPClient) Mail(from string) error {
|
||||
return c.client.Mail(from)
|
||||
}
|
||||
|
||||
func (c *SMTPClient) Quit() error {
|
||||
return c.client.Quit()
|
||||
}
|
||||
func (c *SMTPClient) Rcpt(to string) error {
|
||||
return c.client.Rcpt(to)
|
||||
}
|
||||
|
||||
func (c *SMTPClient) StartTLS(config *tls.Config) error {
|
||||
return c.client.StartTLS(config)
|
||||
}
|
||||
|
||||
type EmailMessage struct {
|
||||
To string
|
||||
Subject string
|
||||
Body string
|
||||
Attachments []EmailAttachment
|
||||
}
|
||||
|
||||
type EmailAttachment struct {
|
||||
File io.Reader
|
||||
Title string
|
||||
}
|
||||
|
||||
func (e EmailAttachment) ReadContent() ([]byte, error) {
|
||||
bts, err := io.ReadAll(e.File)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading attachment: %s", err)
|
||||
}
|
||||
return bts, nil
|
||||
}
|
||||
|
||||
type MailServiceConfig struct {
|
||||
Auth smtp.Auth
|
||||
Host string
|
||||
Port string
|
||||
From string // Sender email address
|
||||
}
|
||||
|
||||
func dial(hostPort string) (smtpClient, error) {
|
||||
client, err := smtp.Dial(hostPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func NewMailService(config MailServiceConfig) *EmailService {
|
||||
return &EmailService{
|
||||
auth: config.Auth,
|
||||
|
@ -112,6 +49,21 @@ func NewMailService(config MailServiceConfig) *EmailService {
|
|||
}
|
||||
}
|
||||
|
||||
type MailServiceConfig struct {
|
||||
Auth smtp.Auth
|
||||
Host string
|
||||
Port string
|
||||
From string // Sender email address
|
||||
}
|
||||
|
||||
func dial(hostPort string) (SMTPClientIface, error) {
|
||||
client, err := smtp.Dial(hostPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (e *EmailService) SendEmail(emailData EmailMessage) error {
|
||||
msg, err := newMessage(e.from, emailData.To, emailData.Subject).
|
||||
withAttachments(emailData.Body, emailData.Attachments)
|
|
@ -74,6 +74,10 @@ func (m *mockSMTP) Rcpt(to string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func TestNewConfig_MissingEnvFile(t *testing.T) {
|
||||
assert.Panics(t, func() { newConfig(".missing_env_file") })
|
||||
}
|
||||
|
||||
func TestMockSendEmail(t *testing.T) {
|
||||
service := &EmailService{
|
||||
auth: smtp.PlainAuth("", "", "", ""),
|
||||
|
@ -84,7 +88,7 @@ func TestMockSendEmail(t *testing.T) {
|
|||
InsecureSkipVerify: true,
|
||||
ServerName: "",
|
||||
},
|
||||
dial: func(hostPort string) (smtpClient, error) {
|
||||
dial: func(hostPort string) (SMTPClientIface, error) {
|
||||
return &mockSMTP{}, nil
|
||||
},
|
||||
}
|
||||
|
@ -119,6 +123,41 @@ func TestSendEmail(t *testing.T) {
|
|||
require.NoError(t, mailSrv.SendEmail(data))
|
||||
}
|
||||
|
||||
func TestSendEmail_FailedAuthentication(t *testing.T) {
|
||||
cfg := newConfig(".env.test")
|
||||
// set up authentication to fail
|
||||
mailSrv := NewMailService(MailServiceConfig{
|
||||
Auth: smtp.PlainAuth("", "wronguser", "wrongpassword", cfg.MailHost),
|
||||
Host: cfg.MailHost,
|
||||
Port: cfg.MailPort,
|
||||
From: cfg.MailFrom,
|
||||
})
|
||||
data := EmailMessage{
|
||||
To: cfg.MailTo,
|
||||
Subject: "Test Email",
|
||||
Body: "This is a test email.",
|
||||
}
|
||||
err := mailSrv.SendEmail(data)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSendEmail_InvalidRecipient(t *testing.T) {
|
||||
cfg := newConfig(".env.test")
|
||||
mailSrv := NewMailService(MailServiceConfig{
|
||||
Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost),
|
||||
Host: cfg.MailHost,
|
||||
Port: cfg.MailPort,
|
||||
From: cfg.MailFrom,
|
||||
})
|
||||
data := EmailMessage{
|
||||
To: "invalid_email",
|
||||
Subject: "Test Email",
|
||||
Body: "This is a test email.",
|
||||
}
|
||||
err := mailSrv.SendEmail(data)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSendEmailWithAttachments(t *testing.T) {
|
||||
cfg := newConfig(".env.test")
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package email
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type EmailMessage struct {
|
||||
To string
|
||||
Subject string
|
||||
Body string
|
||||
Attachments []EmailAttachment
|
||||
}
|
||||
|
||||
type EmailAttachment struct {
|
||||
File io.Reader
|
||||
Title string
|
||||
}
|
||||
|
||||
func (e EmailAttachment) ReadContent() ([]byte, error) {
|
||||
bts, err := io.ReadAll(e.File)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading attachment: %s", err)
|
||||
}
|
||||
return bts, nil
|
||||
}
|
Loading…
Reference in New Issue