Skip to content

Commit

Permalink
all it is left is template one; refine config management
Browse files Browse the repository at this point in the history
  • Loading branch information
crossphoton authored Nov 30, 2021
1 parent 22fb7ec commit 2da27c9
Show file tree
Hide file tree
Showing 10 changed files with 751 additions and 50 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
# Dependency directories (remove the comment below to include it)
# vendor/

*.pb.go
*.pb.go
app.env
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
# email-microservice
gRPC based emailing service

## Environment Variables
```
SMTP_HOST: SMTP host
SMTP_PORT: SMTP port
SMTP_SENDER: SMTP user
SMTP_PASSWORD: SMTP password
PORT: Port to listen on
```
37 changes: 29 additions & 8 deletions email.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ syntax = "proto3";

option go_package= "/src";

message SendEmailRequest {
repeated string receipients = 1;
string subject = 2;
string body = 3;
repeated Attachment attachments = 4;
}

message Attachment {
string base64Data = 1;
}
Expand All @@ -18,6 +11,34 @@ message ResponseMessage {
string ack = 2;
}

message Recipients {
repeated string to = 1;
repeated string cc = 2;
repeated string bcc = 3;
}

message SendEmailRequest {
Recipients recipients = 1;
string subject = 2;
string body = 3;
string contentType = 4;
repeated Attachment attachments = 5;
}

message RawSendEmailRequest {
repeated string recipients = 1;
bytes body = 3;
}

message SendEmailWithTemplateRequest {
Recipients recipients = 1;
string template_name = 2;
repeated Attachment attachments = 3;
map<string, string> template_params = 4;
}

service EmailService {
rpc SendEmail(SendEmailRequest) returns(ResponseMessage) {}
}
rpc SendRawEmail(RawSendEmailRequest) returns(ResponseMessage) {}
rpc SendEmailWithTemplate(SendEmailWithTemplateRequest) returns(ResponseMessage) {}
}
7 changes: 3 additions & 4 deletions examples/email_client.go → examples/raw.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"google.golang.org/grpc"
)

const address = "localhost:55055"
const address = "localhost:5555"

var client src.EmailServiceClient

Expand All @@ -21,19 +21,18 @@ func init() {
if err != nil {
log.Fatalf("error connecting to server: %v", err)
}
defer connection.Close()
client = src.NewEmailServiceClient(connection)
}

func main() {
sendEmail("Hey there!!", []string{"crossphoton@gmail.com"})
sendEmail("Hey there!!", []string{"adiag1200@gmail.com", "cs19b1003@iiitr.ac.in"})
}

func sendEmail(body string, receivers []string) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

res, err := client.SendEmail(ctx, &src.SendEmailRequest{Receipients: receivers, Body: body})
res, err := client.SendRawEmail(ctx, &src.RawSendEmailRequest{Recipients: receivers, Body: []byte(body)})
if err != nil {
log.Fatalf("request failed: %v", err)
} else {
Expand Down
30 changes: 23 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,29 @@ module github.com/crossphoton/email-microservice

go 1.17

require google.golang.org/grpc v1.42.0
require (
github.com/spf13/viper v1.9.0
github.com/xhit/go-simple-mail/v2 v2.10.0
google.golang.org/grpc v1.42.0
google.golang.org/protobuf v1.27.1
)

require (
github.com/golang/protobuf v1.5.0 // indirect
golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
golang.org/x/text v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
google.golang.org/protobuf v1.27.1 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mitchellh/mapstructure v1.4.2 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 // indirect
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
562 changes: 556 additions & 6 deletions go.sum

Large diffs are not rendered by default.

32 changes: 26 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
package main

import (
"fmt"
"log"
"net"

"github.com/crossphoton/email-microservice/src"
"github.com/spf13/viper"
grpc "google.golang.org/grpc"
)

const (
PORT = ":55055"
)
type Config struct {
Port int `mapstructure:"PORT"`
}

var config Config

func init() {
viper.AddConfigPath(".")
viper.SetConfigType("env")
viper.SetConfigName("app")
viper.AutomaticEnv()
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
} else {
log.Fatalf("error reading config file: %v", err)
}
}

viper.Unmarshal(&config)
}

func main() {
// Registering service
Expand All @@ -19,14 +39,14 @@ func main() {
src.RegisterEmailServiceServer(server, &emailServer)

// Listening to port
listener, err := net.Listen("tcp", PORT)
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", config.Port))
if err != nil {
log.Fatalf("cannot listen on %s : %v", PORT, err)
log.Fatalf("cannot listen on %s : %v", config.Port, err)
}
defer listener.Close()

// Starting server
log.Printf("starting server on %v", PORT)
log.Printf("starting server on %v", config.Port)
if err := server.Serve(listener); err != nil {
log.Fatalf("cannor start server: %v", err)
}
Expand Down
5 changes: 5 additions & 0 deletions sample.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_EMAIL=crossphoton@gmail.com
SMTP_PASSWORD=password
PORT=5555
55 changes: 55 additions & 0 deletions src/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package src

import (
"crypto/tls"
"log"
"time"

"github.com/spf13/viper"
mail "github.com/xhit/go-simple-mail/v2"
)

type Config struct {
SMTPHost string `mapstructure:"SMTP_HOST"`
SMTPPort int `mapstructure:"SMTP_PORT"`
SMTPEmail string `mapstructure:"SMTP_EMAIL"`
SMTPPassword string `mapstructure:"SMTP_PASSWORD"`
}

var (
config Config
)

func init() {
viper.AddConfigPath(".")
viper.AddConfigPath("../")
viper.SetConfigType("env")
viper.SetConfigName("app")
viper.AutomaticEnv()
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
} else {
log.Fatalf("error reading config file: %v", err)
}
}

viper.Unmarshal(&config)

smtpServer := mail.NewSMTPClient()
smtpServer.Host = config.SMTPHost
smtpServer.Port = config.SMTPPort
smtpServer.Username = config.SMTPEmail
smtpServer.Password = config.SMTPPassword
smtpServer.Encryption = mail.EncryptionSTARTTLS

smtpServer.KeepAlive = true
smtpServer.ConnectTimeout = 10 * time.Second
smtpServer.SendTimeout = 10 * time.Second
smtpServer.TLSConfig = &tls.Config{InsecureSkipVerify: true}

smtpClient, err = smtpServer.Connect()
if err != nil {
log.Fatalf("connection to remote smtp server failed: %v", err)
}
}
61 changes: 43 additions & 18 deletions src/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,59 @@ import (
"context"
"fmt"
"log"
"net/smtp"

mail "github.com/xhit/go-simple-mail/v2"
)

var smtpClient *mail.SMTPClient

type EmailServer struct {
UnimplementedEmailServiceServer
}

func (s *EmailServer) SendEmail(ctx context.Context, req *SendEmailRequest) (*ResponseMessage, error) {
smtpHost := "smtp.gmail.com"
smtpPort := "587"

// Sender data.
from := "email@gmail.com"
password := "password"

auth := smtp.PlainAuth("", from, password, smtpHost)
err := smtp.SendMail(
fmt.Sprintf("%s:%s", smtpHost, smtpPort),
auth,
from,
req.GetReceipients(),
[]byte(req.GetBody()),
)
email := mail.NewMSG()

// Add receivers
email.
AddTo(req.Recipients.To...).
AddCc(req.Recipients.Cc...).
AddBcc(req.Recipients.Bcc...).
SetSubject(req.Subject)

// Set content type
if req.ContentType == "text/html" {
email.SetBody(mail.TextHTML, req.GetBody())
} else {
email.SetBody(mail.TextPlain, req.GetBody())
}

// Check for errors
if email.Error != nil {
return nil, fmt.Errorf("email format error: %v", email.Error)
}

// Send the email
err := email.Send(smtpClient)
if err != nil {
log.Println(err)
return nil, err
log.Printf("err: error sending email: %v", err)
return nil, fmt.Errorf("error sending email: %v", err)
}

return &ResponseMessage{Success: true, Ack: "mail sent successfully"}, nil
}

func (s *EmailServer) SendRawEmail(ctx context.Context, req *RawSendEmailRequest) (*ResponseMessage, error) {
// Sending email
err := mail.SendMessage(config.SMTPEmail, req.Recipients, string(req.Body), smtpClient)
if err != nil {
log.Printf("err: error sending email: %v", err)
return nil, fmt.Errorf("error sending email: %v", err)
}

return &ResponseMessage{Ack: "email sent successfully", Success: true}, nil
}

func (s *EmailServer) SendEmailWithTemplate(ctx context.Context, req *SendEmailWithTemplateRequest) (*ResponseMessage, error) {
return nil, fmt.Errorf("not implemented")
}

0 comments on commit 2da27c9

Please sign in to comment.