Kaynağa Gözat

add SMTP error handling and email templates; update logger configuration

lblt 2 hafta önce
ebeveyn
işleme
7f0e35fb11
9 değiştirilmiş dosya ile 719 ekleme ve 11 silme
  1. 186 0
      README.md
  2. 5 3
      examples/main.go
  3. 1 0
      go.mod
  4. 66 0
      go.sum
  5. 1 1
      pkg/config/config.go
  6. 22 0
      pkg/errors/errors.go
  7. 3 7
      pkg/logger/logrus.go
  8. 220 0
      pkg/smtp/service.go
  9. 215 0
      pkg/smtp/templates.go

+ 186 - 0
README.md

@@ -0,0 +1,186 @@
+# BYOM Golang Library
+
+A production-ready Go library that provides pre-configured components for building robust web applications. This library simplifies the integration of common services and follows best practices for production environments.
+
+## Features
+
+- **Web Server**: Pre-configured Gin server with middleware support
+- **Authentication**: JWT-based authentication
+- **Database**: SQLite integration with connection management
+- **Object Storage**: MinIO client for handling file storage
+- **Logging**: Structured logging using Logrus
+- **Error Handling**: Standardized error types and handling
+- **Configuration**: Centralized configuration management
+
+## Installation
+
+```bash
+go get git.linuxforward.com/byom/byom-golang-lib
+```
+
+## Quick Start
+
+Here's a simple example that demonstrates how to use the main components:
+
+```go
+package main
+
+import (
+    "time"
+    "git.linuxforward.com/byom/byom-golang-lib/pkg/logger"
+    "git.linuxforward.com/byom/byom-golang-lib/pkg/server"
+    "git.linuxforward.com/byom/byom-golang-lib/pkg/database"
+)
+
+func main() {
+    // Initialize logger
+    log, err := logger.NewLogger(logger.LogConfig{
+        Level:     "info",
+        Formatter: "text",
+    })
+    if err != nil {
+        panic(err)
+    }
+
+    // Initialize database
+    db, err := database.NewSQLiteDB(database.SQLiteConfig{
+        Path: "app.db",
+    })
+    if err != nil {
+        log.Fatal(err)
+    }
+    defer db.Close()
+
+    // Initialize server
+    srv, err := server.NewGinServer(log, server.ServerConfig{
+        AllowedOrigins: []string{"http://localhost:3000"},
+        RequestTimeout: 30 * time.Second,
+    })
+    if err != nil {
+        log.Fatal(err)
+    }
+
+    // Add routes and start server
+    srv.AddHealthCheck()
+    if err := srv.Router().Run(":8080"); err != nil {
+        log.Fatal(err)
+    }
+}
+```
+
+## Component Usage
+
+### Logger
+
+```go
+log, err := logger.NewLogger(logger.LogConfig{
+    Level:     "info",    // debug, info, warn, error
+    Formatter: "text",    // text or json
+})
+```
+
+### Database (SQLite)
+
+```go
+db, err := database.NewSQLiteDB(database.SQLiteConfig{
+    Path: "app.db",
+})
+defer db.Close()
+```
+
+### MinIO Storage
+
+```go
+minioClient, err := storage.NewMinioClient(storage.MinioConfig{
+    Endpoint:        "localhost:9000",
+    AccessKeyID:     "minioadmin",
+    SecretAccessKey: "minioadmin",
+    UseSSL:          false,
+    BucketName:      "mybucket",
+})
+```
+
+### JWT Authentication
+
+```go
+token, err := auth.GenerateJWT(auth.Claims{
+    UserID: "123",
+    Role:   "admin",
+})
+```
+
+## Best Practices
+
+### Error Handling
+
+Use the provided error types for consistent error handling:
+
+```go
+if err != nil {
+    return errors.NewDatabaseError("query", err)
+}
+```
+
+### Configuration
+
+Use the config package for managing environment-specific configurations:
+
+```go
+cfg, err := config.Load("config.yaml")
+```
+
+### Middleware
+
+The server package includes common middleware for security and monitoring:
+
+- CORS configuration
+- Request timeout
+- Logging
+- Panic recovery
+- Request ID tracking
+
+## Maintenance
+
+### Versioning
+
+This library follows [Semantic Versioning](https://semver.org/). Version numbers follow the format: MAJOR.MINOR.PATCH.
+
+### Contributing
+
+1. Fork the repository
+2. Create a feature branch (`git checkout -b feature/amazing-feature`)
+3. Commit your changes (`git commit -m 'Add amazing feature'`)
+4. Push to the branch (`git push origin feature/amazing-feature`)
+5. Open a Pull Request
+
+### Testing
+
+Run the test suite:
+
+```bash
+go test ./...
+```
+
+For coverage report:
+
+```bash
+go test ./... -coverprofile=coverage.out
+go tool cover -html=coverage.out
+```
+
+### Logging Practices
+
+- Use appropriate log levels (DEBUG, INFO, WARN, ERROR)
+- Include relevant context in log messages
+- Use structured logging fields for machine-readable logs
+
+### Error Handling Practices
+
+- Use appropriate error types from the errors package
+- Include context when wrapping errors
+- Log errors at the appropriate level
+- Don't expose internal errors to API clients
+
+## License
+
+This project is licensed under the MIT License - see the LICENSE file for details.

+ 5 - 3
examples/main.go

@@ -4,6 +4,7 @@ import (
 	"net/http"
 	"time"
 
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/config"
 	"git.linuxforward.com/byom/byom-golang-lib/pkg/database"
 	"git.linuxforward.com/byom/byom-golang-lib/pkg/errors"
 	"git.linuxforward.com/byom/byom-golang-lib/pkg/logger"
@@ -15,9 +16,10 @@ import (
 
 func main() {
 	// Initialize logger
-	log, err := logger.NewLogger(logger.LogConfig{
-		Level:     "info",
-		Formatter: "text",
+	log, err := logger.NewLogger(&config.Log{
+		Level:   "info",
+		Format:  "text",
+		NoColor: false,
 	})
 	if err != nil {
 		panic(err)

+ 1 - 0
go.mod

@@ -26,6 +26,7 @@ require (
 	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.12 // indirect
+	github.com/wneessen/go-mail v0.6.1 // indirect
 	golang.org/x/arch v0.14.0 // indirect
 	google.golang.org/protobuf v1.36.5 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect

+ 66 - 0
go.sum

@@ -29,6 +29,7 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
 github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
 github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 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=
@@ -80,18 +81,83 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
 github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/wneessen/go-mail v0.6.1 h1:cDGqlGuEEhdILRe53VFzmM9WBk8Xh/QMvbO0oxrNJB4=
+github.com/wneessen/go-mail v0.6.1/go.mod h1:G702XlFhzHV0Z4w9j2VsH5K9dJDvj0hx+yOOp1oX9vc=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
 golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
 golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
 golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
 golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
 golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
 golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
 google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

+ 1 - 1
pkg/config/config.go

@@ -121,7 +121,7 @@ func (c *Database) UnmarshalYAML(unmarshal func(interface{}) error) error {
 
 type Log struct {
 	Level   string `yaml:"level"`
-	Format  string `yaml:"format"`
+	Format  string `yaml:"format"` // "json" or "text"
 	NoColor bool   `yaml:"no_color"`
 }
 

+ 22 - 0
pkg/errors/errors.go

@@ -115,3 +115,25 @@ func NewConfigError(section string, err error) *ConfigError {
 		Err:     err,
 	}
 }
+
+// SMTPError represents an email-related error
+type SMTPError struct {
+	Operation string
+	Err       error
+}
+
+func (e *SMTPError) Error() string {
+	return fmt.Sprintf("smtp error during %s: %v", e.Operation, e.Err)
+}
+
+func (e *SMTPError) Unwrap() error {
+	return e.Err
+}
+
+// NewSMTPError creates a new SMTPError
+func NewSMTPError(operation string, err error) *SMTPError {
+	return &SMTPError{
+		Operation: operation,
+		Err:       err,
+	}
+}

+ 3 - 7
pkg/logger/logrus.go

@@ -3,23 +3,19 @@ package logger
 import (
 	"os"
 
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/config"
 	"git.linuxforward.com/byom/byom-golang-lib/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
 
-type LogConfig struct {
-	Level     string
-	Formatter string // "json" or "text"
-}
-
-func NewLogger(config LogConfig) (*logrus.Logger, error) {
+func NewLogger(config *config.Log) (*logrus.Logger, error) {
 	logger := logrus.New()
 
 	// Set output
 	logger.SetOutput(os.Stdout)
 
 	// Validate and set formatter
-	switch config.Formatter {
+	switch config.Format {
 	case "json":
 		logger.SetFormatter(&logrus.JSONFormatter{})
 	case "text":

+ 220 - 0
pkg/smtp/service.go

@@ -0,0 +1,220 @@
+package smtp
+
+import (
+	"bytes"
+	"html/template"
+	"strings"
+
+	"github.com/sirupsen/logrus"
+	"github.com/wneessen/go-mail"
+
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/errors"
+)
+
+// SMTPConfig holds the configuration for the SMTP service
+type SMTPConfig struct {
+	Host     string
+	Port     int
+	Username string
+	Password string
+	From     string
+}
+
+// EmailData holds the data for the default email template
+type EmailData struct {
+	Subject string
+	Body    string
+}
+
+// VerificationData holds the data for the verification email template
+type VerificationData struct {
+	VerificationURL string
+}
+
+// WelcomeData holds the data for the welcome email template
+type WelcomeData struct {
+	Username  string
+	Password  string
+	WebAppURL string
+}
+
+// SMTPService handles email sending operations
+type SMTPService struct {
+	client *mail.Client
+	logger *logrus.Logger
+	from   string
+}
+
+// NewSMTPService creates a new SMTP service instance
+func NewSMTPService(config SMTPConfig, logger *logrus.Logger) (*SMTPService, error) {
+	if logger == nil {
+		return nil, errors.NewSMTPError("initialization", errors.ErrInvalidInput)
+	}
+
+	// Validate config
+	if config.Host == "" || config.Port == 0 || config.Username == "" || config.Password == "" || config.From == "" {
+		return nil, errors.NewSMTPError("initialization", errors.ErrInvalidInput)
+	}
+
+	// Create mail client with proper configuration
+	client, err := mail.NewClient(config.Host,
+		mail.WithPort(config.Port),
+		mail.WithSMTPAuth(mail.SMTPAuthPlain),
+		mail.WithUsername(config.Username),
+		mail.WithPassword(config.Password),
+		mail.WithTLSPolicy(mail.TLSMandatory), // Enforce TLS
+	)
+	if err != nil {
+		logger.WithError(err).Error("Failed to create SMTP client")
+		return nil, errors.NewSMTPError("client creation", err)
+	}
+
+	return &SMTPService{
+		client: client,
+		logger: logger,
+		from:   config.From,
+	}, nil
+}
+
+// SendEmail sends a basic email using the default template
+func (s *SMTPService) SendEmail(to string, data EmailData) error {
+	if strings.TrimSpace(to) == "" {
+		return errors.NewSMTPError("send", errors.ErrInvalidInput)
+	}
+
+	// Parse and execute template
+	tmpl, err := template.New("email").Parse(defaultTemplate)
+	if err != nil {
+		s.logger.WithError(err).Error("Failed to parse email template")
+		return errors.NewSMTPError("template parsing", err)
+	}
+
+	var body bytes.Buffer
+	if err := tmpl.Execute(&body, data); err != nil {
+		s.logger.WithError(err).Error("Failed to execute email template")
+		return errors.NewSMTPError("template execution", err)
+	}
+
+	// Create and configure message
+	msg := mail.NewMsg()
+	if err := msg.From(s.from); err != nil {
+		return errors.NewSMTPError("message creation", err)
+	}
+	if err := msg.To(to); err != nil {
+		return errors.NewSMTPError("message creation", err)
+	}
+	msg.Subject(data.Subject)
+	msg.SetBodyString(mail.TypeTextHTML, body.String())
+
+	// Send email
+	if err := s.client.DialAndSend(msg); err != nil {
+		s.logger.WithError(err).Error("Failed to send email")
+		return errors.NewSMTPError("sending", err)
+	}
+
+	s.logger.WithFields(logrus.Fields{
+		"to":      to,
+		"subject": data.Subject,
+	}).Info("Email sent successfully")
+
+	return nil
+}
+
+// SendVerificationEmail sends an email verification message
+func (s *SMTPService) SendVerificationEmail(to string, data VerificationData) error {
+	if strings.TrimSpace(to) == "" || strings.TrimSpace(data.VerificationURL) == "" {
+		return errors.NewSMTPError("send verification", errors.ErrInvalidInput)
+	}
+
+	// Parse and execute template
+	tmpl, err := template.New("verification").Parse(verificationTemplate)
+	if err != nil {
+		s.logger.WithError(err).Error("Failed to parse verification template")
+		return errors.NewSMTPError("template parsing", err)
+	}
+
+	var body bytes.Buffer
+	if err := tmpl.Execute(&body, data); err != nil {
+		s.logger.WithError(err).Error("Failed to execute verification template")
+		return errors.NewSMTPError("template execution", err)
+	}
+
+	// Create and configure message
+	msg := mail.NewMsg()
+	if err := msg.From(s.from); err != nil {
+		return errors.NewSMTPError("message creation", err)
+	}
+	if err := msg.To(to); err != nil {
+		return errors.NewSMTPError("message creation", err)
+	}
+	msg.Subject("Verify Your Email Address")
+	msg.SetBodyString(mail.TypeTextHTML, body.String())
+
+	// Send email
+	if err := s.client.DialAndSend(msg); err != nil {
+		s.logger.WithError(err).Error("Failed to send verification email")
+		return errors.NewSMTPError("sending", err)
+	}
+
+	s.logger.WithFields(logrus.Fields{
+		"to": to,
+	}).Info("Verification email sent successfully")
+
+	return nil
+}
+
+// SendWelcomeEmail sends a welcome email with credentials
+func (s *SMTPService) SendWelcomeEmail(to string, data WelcomeData) error {
+	if strings.TrimSpace(to) == "" || strings.TrimSpace(data.Username) == "" {
+		return errors.NewSMTPError("send welcome", errors.ErrInvalidInput)
+	}
+
+	// Parse and execute template
+	tmpl, err := template.New("welcome").Parse(welcomeTemplate)
+	if err != nil {
+		s.logger.WithError(err).Error("Failed to parse welcome template")
+		return errors.NewSMTPError("template parsing", err)
+	}
+
+	var body bytes.Buffer
+	if err := tmpl.Execute(&body, data); err != nil {
+		s.logger.WithError(err).Error("Failed to execute welcome template")
+		return errors.NewSMTPError("template execution", err)
+	}
+
+	// Create and configure message
+	msg := mail.NewMsg()
+	if err := msg.From(s.from); err != nil {
+		return errors.NewSMTPError("message creation", err)
+	}
+	if err := msg.To(to); err != nil {
+		return errors.NewSMTPError("message creation", err)
+	}
+	msg.Subject("Welcome to Our Platform!")
+	msg.SetBodyString(mail.TypeTextHTML, body.String())
+
+	// Send email
+	if err := s.client.DialAndSend(msg); err != nil {
+		s.logger.WithError(err).Error("Failed to send welcome email")
+		return errors.NewSMTPError("sending", err)
+	}
+
+	s.logger.WithFields(logrus.Fields{
+		"to":       to,
+		"username": data.Username,
+	}).Info("Welcome email sent successfully")
+
+	return nil
+}
+
+// Close closes the SMTP client connection
+func (s *SMTPService) Close() error {
+	if s.client != nil {
+		if err := s.client.Close(); err != nil {
+			s.logger.WithError(err).Error("Failed to close SMTP client")
+			return errors.NewSMTPError("close", err)
+		}
+		s.logger.Info("SMTP client closed successfully")
+	}
+	return nil
+}

+ 215 - 0
pkg/smtp/templates.go

@@ -0,0 +1,215 @@
+package smtp
+
+const (
+	// Common CSS styles shared across all email templates
+	commonStyles = `
+		body {
+			font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+			line-height: 1.6;
+			color: #333;
+			max-width: 600px;
+			margin: 0 auto;
+			padding: 20px;
+		}
+		.container {
+			background-color: #ffffff;
+			border: 1px solid #e1e1e1;
+			border-radius: 5px;
+			padding: 25px;
+			margin-top: 20px;
+		}
+		.header {
+			margin-bottom: 20px;
+			padding-bottom: 20px;
+			border-bottom: 1px solid #e1e1e1;
+		}
+		.content {
+			margin-bottom: 20px;
+		}
+		.footer {
+			margin-top: 20px;
+			padding-top: 20px;
+			border-top: 1px solid #e1e1e1;
+			font-size: 0.9em;
+			color: #666;
+		}
+		.button {
+			display: inline-block;
+			padding: 10px 20px;
+			background-color: #007bff;
+			color: #ffffff;
+			text-decoration: none;
+			border-radius: 5px;
+			margin: 15px 0;
+		}
+		.button:hover {
+			background-color: #0056b3;
+		}
+		@media only screen and (max-width: 600px) {
+			body {
+				padding: 10px;
+			}
+			.container {
+				padding: 15px;
+			}
+		}
+	`
+
+	// Template-specific styles
+	defaultStyles = ``
+
+	verificationStyles = `
+		.content {
+			text-align: center;
+		}
+		.button {
+			padding: 12px 24px;
+			background-color: #28a745;
+			font-weight: bold;
+		}
+		.button:hover {
+			background-color: #218838;
+		}
+		.verification-link {
+			margin: 20px 0;
+			padding: 15px;
+			background-color: #f8f9fa;
+			border: 1px solid #e1e1e1;
+			border-radius: 5px;
+			word-break: break-all;
+		}
+	`
+
+	welcomeStyles = `
+		.credentials {
+			background-color: #f8f9fa;
+			border: 1px solid #e1e1e1;
+			border-radius: 5px;
+			padding: 20px;
+			margin: 20px 0;
+		}
+		.credential-item {
+			margin: 10px 0;
+		}
+		.label {
+			font-weight: bold;
+			color: #495057;
+		}
+		.value {
+			font-family: monospace;
+			background-color: #e9ecef;
+			padding: 2px 6px;
+			border-radius: 3px;
+		}
+	`
+
+	// Email templates with shared and specific styles
+	defaultTemplate = `
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>{{.Subject}}</title>
+    <style>
+        ` + commonStyles + defaultStyles + `
+    </style>
+</head>
+<body>
+    <div class="container">
+        <div class="header">
+            <h1>{{.Subject}}</h1>
+        </div>
+        <div class="content">
+            {{.Body}}
+        </div>
+        <div class="footer">
+            <p>This is an automated message, please do not reply directly to this email.</p>
+        </div>
+    </div>
+</body>
+</html>
+`
+
+	verificationTemplate = `
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Email Verification</title>
+    <style>
+        ` + commonStyles + verificationStyles + `
+    </style>
+</head>
+<body>
+    <div class="container">
+        <div class="header">
+            <h1>Verify Your Email</h1>
+        </div>
+        <div class="content">
+            <p>Thank you for registering. Please click the button below to verify your email address:</p>
+            <a href="{{.VerificationURL}}" class="button">Verify Email</a>
+            <div class="verification-link">
+                <p>If the button doesn't work, copy and paste this link into your browser:</p>
+                <code>{{.VerificationURL}}</code>
+            </div>
+        </div>
+        <div class="footer">
+            <p>If you didn't create an account, you can safely ignore this email.</p>
+        </div>
+    </div>
+</body>
+</html>
+`
+
+	welcomeTemplate = `
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Welcome!</title>
+    <style>
+        ` + commonStyles + welcomeStyles + `
+    </style>
+</head>
+<body>
+    <div class="container">
+        <div class="header">
+            <h1>Welcome!</h1>
+        </div>
+        <div class="content">
+            <p>Your account has been successfully created. Here are your access details:</p>
+            
+            <div class="credentials">
+                <div class="credential-item">
+                    <span class="label">Username:</span>
+                    <span class="value">{{.Username}}</span>
+                </div>
+                <div class="credential-item">
+                    <span class="label">Password:</span>
+                    <span class="value">{{.Password}}</span>
+                </div>
+                {{if .WebAppURL}}
+                <div class="credential-item">
+                    <span class="label">Application URL:</span>
+                    <span class="value">{{.WebAppURL}}</span>
+                </div>
+                {{end}}
+            </div>
+
+            {{if .WebAppURL}}
+            <a href="{{.WebAppURL}}" class="button">Access Your Account</a>
+            {{end}}
+            
+            <p><strong>Important:</strong> Please change your password upon your first login.</p>
+        </div>
+        <div class="footer">
+            <p>If you need any assistance, please contact our support team.</p>
+        </div>
+    </div>
+</body>
+</html>
+`
+)