Skip to content

Commit

Permalink
impl Email Gmail Sender
Browse files Browse the repository at this point in the history
  • Loading branch information
aradwann committed Jan 7, 2024
1 parent f4b1d7f commit ec3677d
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 9 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ createmigration:
migrate create -ext sql -dir $(MIGRATIONS_PATH) -seq "$(filter-out $@,$(MAKECMDGOALS))"

test:
go test -v -cover ./...
go test -short -v -cover ./...

testci:
go test -race -covermode atomic -coverprofile=covprofile ./...
go test -short -race -covermode atomic -coverprofile=covprofile ./...

server:
go run main.go
Expand Down
5 changes: 4 additions & 1 deletion app.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ GRPC_SERVER_ADDRESS=0.0.0.0:9090
TOKEN_SYMMETRIC_KEY=B51FE30989F143F6F07D2CB828495D69
ACCESS_TOKEN_DURATION=15m
REFRESH_TOKEN_DURATION=24h
REDIS_ADDRESS=0.0.0.0:6379
REDIS_ADDRESS=0.0.0.0:6379
EMAIL_SENDER_NAME=eenergy
EMAIL_SENDER_ADDRESS=ahmedradwan9966@gmail.com
EMAIL_SENDER_PASSWORD=
2 changes: 1 addition & 1 deletion gapi/rpc_create_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (server *Server) CreateUser(ctx context.Context, req *pb.CreateUserRequest)
taskPayload := &worker.PayloadSendVerifyEmail{Username: user.Username}
opts := []asynq.Option{
asynq.MaxRetry(10),
asynq.ProcessIn(10 * time.Second),
asynq.ProcessIn(10 * time.Second), // make room for the DB to commit the transaction before the task is picked up by the worker, otherwise the worker might not find the record
asynq.Queue(worker.QueueCritical),
}
return server.taskDistributor.DistributeTaskSendVerifyEmail(ctx, taskPayload, opts...)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0
github.com/hibiken/asynq v0.24.1
github.com/jackc/pgx/v5 v5.5.1
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/o1egl/paseto v1.0.0
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.8.4
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
Expand Down
65 changes: 65 additions & 0 deletions mail/sender.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package mail

import (
"fmt"
"net/smtp"

"github.com/jordan-wright/email"
)

const (
smtpAuthAddress = "smtp.gmail.com"
smtpServerAddress = "smtp.gmail.com:587"
)

type EmailSender interface {
SendEmail(
subject string,
content string,
to []string,
cc []string,
bcc []string,
attachFiles []string,
) error
}

type GmailSender struct {
name string
fromEmailAddress string
fromEmailPassword string
}

func NewGmailSender(name string, fromEmailAddress string, fromEmailPassword string) EmailSender {
return &GmailSender{
name: name,
fromEmailAddress: fromEmailAddress,
fromEmailPassword: fromEmailPassword,
}
}

func (sender *GmailSender) SendEmail(
subject string,
content string,
to []string,
cc []string,
bcc []string,
attachFiles []string,
) error {
e := email.NewEmail()
e.From = fmt.Sprintf("%s <%s>", sender.name, sender.fromEmailAddress)
e.Subject = subject
e.HTML = []byte(content)
e.To = to
e.Cc = cc
e.Bcc = bcc

for _, f := range attachFiles {
_, err := e.AttachFile(f)
if err != nil {
return fmt.Errorf("failed to attach file %s: %w", f, err)
}
}

smtpAuth := smtp.PlainAuth("", sender.fromEmailAddress, sender.fromEmailPassword, smtpAuthAddress)
return e.Send(smtpServerAddress, smtpAuth)
}
31 changes: 31 additions & 0 deletions mail/sender_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package mail

import (
"testing"

"github.com/aradwann/eenergy/util"
"github.com/stretchr/testify/require"
)

func TestSendEmailWithGmail(t *testing.T) {
// skip when the flag is set to prevent the CI from sending emails every time it runs
if testing.Short() {
t.Skip()
}

config, err := util.LoadConfig("..", "app")
require.NoError(t, err)

sender := NewGmailSender(config.EmailSenderName, config.EmailSenderAddress, config.EmailSenderPassword)

subject := "A test email"
content := `
<h1>Hello world</h1>
<p>This is a test message from Eenergy</a></p>
`
to := []string{"aradwann@proton.me"}
attachFiles := []string{"../README.md"}

err = sender.SendEmail(subject, content, to, nil, nil, attachFiles)
require.NoError(t, err)
}
3 changes: 3 additions & 0 deletions util/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ type Config struct {
TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"`
AccessTokenDuration time.Duration `mapstructure:"ACCESS_TOKEN_DURATION"`
RefreshTokenDuration time.Duration `mapstructure:"REFRESH_TOKEN_DURATION"`
EmailSenderName string `mapstructure:"EMAIL_SENDER_NAME"`
EmailSenderAddress string `mapstructure:"EMAIL_SENDER_ADDRESS"`
EmailSenderPassword string `mapstructure:"EMAIL_SENDER_PASSWORD"`
}

// LoadConfig read configuration from the file or environment variables
Expand Down
9 changes: 4 additions & 5 deletions worker/task_send_verify_email.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package worker

import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"log/slog"

Expand Down Expand Up @@ -50,10 +48,11 @@ func (processor *RedisTaskProcessor) ProcessTaskSendVerifyEmail(ctx context.Cont

user, err := processor.store.GetUser(ctx, payload.Username)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("user doesn't exist: %w", asynq.SkipRetry)
// if the user is not found try again later, as the creation might be not commited yet
// if errors.Is(err, sql.ErrNoRows) {
// return fmt.Errorf("user doesn't exist: %w", asynq.SkipRetry)

}
// }
return fmt.Errorf("failed to get user: %w", asynq.SkipRetry)
}
// TODO: send email to user
Expand Down

0 comments on commit ec3677d

Please sign in to comment.