Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,10 @@ FROM alpine:3 AS stage

RUN apk update && apk add --no-cache ca-certificates

# Create non-root user
# RUN adduser -D -g '' appuser

WORKDIR /app

COPY --from=builder /smaila ./

# Optional: Copy static files, configs, etc. (but not .env)
# COPY static/ ./static/

# USER appuser

EXPOSE 8080

ENTRYPOINT ["./smaila"]
63 changes: 48 additions & 15 deletions handler/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,24 @@ package handler
import (
"fmt"
"io"
"log/slog"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/CactusBros/smaila/config"
_ "github.com/CactusBros/smaila/docs"
"github.com/CactusBros/smaila/internal/mail"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/swagger"
_ "github.com/CactusBros/smaila/docs"
)

const (
KiB = 1024 << (iota * 10)
MiB
GiB
)

// Run initial routes and serve HTTP requests.
Expand Down Expand Up @@ -61,26 +71,18 @@ func newMailHandler(cfg config.SMTPConfig) fiber.Handler {
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
}

var attachments []mail.Attachment
var attachments []string
if form != nil {
files := form.File["attachments"]
for _, fileHeader := range files {
file, err := fileHeader.Open()
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to open attachment")
if fileHeader.Size > 25*MiB {
return fiber.NewError(fiber.StatusBadRequest, "attachment must be less than 25MiB")
}
defer file.Close()

content, err := io.ReadAll(file)
filePath, err := SaveFileTemp(fileHeader)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to read attachment")
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}

attachments = append(attachments, mail.Attachment{
Filename: fileHeader.Filename,
ContentType: fileHeader.Header.Get("Content-Type"),
Content: content,
})
attachments = append(attachments, filePath)
}
}

Expand All @@ -100,6 +102,13 @@ func newMailHandler(cfg config.SMTPConfig) fiber.Handler {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}

// Clean temp files
for _, file := range attachments {
if err = os.RemoveAll(filepath.Dir(file)); err != nil {
slog.Error("failed to remove temp file", "file path", file, "error", err)
}
}

return c.SendStatus(fiber.StatusOK)
}
}
Expand All @@ -114,3 +123,27 @@ func filterEmpty(input []string) []string {
}
return result
}

func SaveFileTemp(header *multipart.FileHeader) (path string, err error) {
file, err := header.Open()
if err != nil {
return "", fmt.Errorf("failed to open attachment: %v", header.Filename)
}
defer file.Close()

data, err := io.ReadAll(file)
if err != nil {
return "", fmt.Errorf("failed to read attachment: %v", header.Filename)
}

dir, err := os.MkdirTemp(os.TempDir(), "*")
if err != nil {
return "", fmt.Errorf("failed to make temp dir")
}

path = filepath.Join(dir, header.Filename)
if err = os.WriteFile(path, data, 0644); err != nil {
return "", fmt.Errorf("failed to save attachment: %v", header.Filename)
}
return
}
18 changes: 9 additions & 9 deletions internal/mail/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import (
)

type Message struct {
To []string // primary recipients
Cc []string // carbon copy
Bcc []string // blind carbon copy
Subject string // email subject
Body string // main message body (usually plain text or HTML)
IsHTML bool // true if Body is HTML
Attachments []Attachment // optional file attachments
To []string // primary recipients
Cc []string // carbon copy
Bcc []string // blind carbon copy
Subject string // email subject
Body string // main message body (usually plain text or HTML)
IsHTML bool // true if Body is HTML
Attachments []string // optional path to file attachments
}

type Attachment struct {
Expand Down Expand Up @@ -42,8 +42,8 @@ func Send(cfg config.SMTPConfig, msg Message) error {
}

// Attach files
for _, a := range msg.Attachments {
m.Attach(a.Filename)
for _, att := range msg.Attachments {
m.Attach(att)
}

// Send email
Expand Down