Skip to content

Commit

Permalink
add email service
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartaccent committed Apr 12, 2023
1 parent 66fc364 commit 0f7a82e
Show file tree
Hide file tree
Showing 12 changed files with 1,545 additions and 5 deletions.
11 changes: 10 additions & 1 deletion .github/workflows/go-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,13 @@ jobs:
goversion: "https://dl.google.com/go/go1.20.3.linux-amd64.tar.gz"
project_path: "./services/auth/cmd/server"
binary_name: "auth-server"
extra_files: LICENSE ./services/auth/README.md
extra_files: LICENSE ./services/auth/README.md
- uses: wangyoucao577/go-release-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
goversion: "https://dl.google.com/go/go1.20.3.linux-amd64.tar.gz"
project_path: "./services/email/cmd/server"
binary_name: "email-server"
extra_files: LICENSE ./services/email/README.md
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@ Services hosted within Accent Design using Google Remote Procedure Call.

A list of all the service built in this library:

| Service | Description | |
|-------------------------|-----------------------------------|----------------------------------------------------------------|
| [Auth](./services/auth) | Email and password authentication | [Dockerhub](https://hub.docker.com/r/accent/grpc-service-auth) |
| Service | Description | |
|---------------------------|------------------------------------|-----------------------------------------------------------------|
| [Auth](./services/auth) | Email and password authentication | [Dockerhub](https://hub.docker.com/r/accent/grpc-service-auth) |
| [Email](./services/email) | SMTP email with file attachments | [Dockerhub](https://hub.docker.com/r/accent/grpc-service-email) |

## Tools

Some useful external tools:

#### FastAPI Boilerplate

A boilerplate written in Python, Pydantic and SQLAlchemy using the Auth service here.
A boilerplate written in Python, Pydantic and SQLAlchemy using the following services:

* Auth
* Email

https://github.com/stuartaccent/fastapi-boilerplate

Expand Down
48 changes: 48 additions & 0 deletions services/email/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Email Service

An email service that implements:

* SendEmail
* Plain & HTML
* Attachments

A connection is attempted during the init process of the server to test valid credentials.

## Arguments

Command line arguments the service accepts:

| Argument | Description |
|-----------------------------------------|--------------------------------------------|
| `-h`, `--help` | Show help message and exit |
| `-reflection`, `--reflection` | Used to allow gRPC Web UI tools to connect |
| `-port`, `--port` | Port to bind to |

## Environment

A list of the environment variables:

| Variable | Description |
|-----------------|----------------------------------------------------|
| `SMTP_HOST` | SMTP server host (e.g. smtp.sendgrid.net) |
| `SMTP_PORT` | SMTP server port (e.g. 587) |
| `SMTP_USERNAME` | SMTP server username (e.g. apikey) |
| `SMTP_PASSWORD` | SMTP server password (e.g. my-sendgrid-key) |
| `SMTP_STARTTLS` | SMTP server start TLS (e.g. t,1,true or f,0,false) |

## Building in Go

Build the binary using GO locally, this will create an executable file.

cd services/email && go build -o bin/server cmd/server/main.go

## Regenerate gRPC Code

cd services/email/pkg/api/email

protoc \
--go_out=. \
--go_opt=paths=source_relative \
--go-grpc_out=. \
--go-grpc_opt=paths=source_relative \
email.proto
100 changes: 100 additions & 0 deletions services/email/cmd/server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"context"
"flag"
"fmt"
"log"
"net"
"os"
"strconv"

"google.golang.org/grpc"
"google.golang.org/grpc/reflection"

pb "github.com/accentdesign/grpc/services/email/pkg/api/email"
"github.com/accentdesign/grpc/services/email/service"
)

var (
helpFlag = flag.Bool("help", false, "Display help information")
enableReflection = flag.Bool("reflection", false, "Enable reflection")
port = flag.Int("port", 50051, "The server port")
smtpHost = os.Getenv("SMTP_HOST")
smtpPort = os.Getenv("SMTP_PORT")
smtpUsername = os.Getenv("SMTP_USERNAME")
smtpPassword = os.Getenv("SMTP_PASSWORD")
smtpStartTLS = os.Getenv("SMTP_STARTTLS")
)

func displayHelp() {
flag.PrintDefaults()
fmt.Println("Environment variables:")
}

func main() {
// get runtime flags
flag.Parse()

// display help
if *helpFlag {
displayHelp()
os.Exit(0)
}

// log errors
errHandler := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
resp, err := handler(ctx, req)
if err != nil {
log.Printf("method: %q error: %s", info.FullMethod, err)
}
return resp, err
}

// create the server
grpcServer := grpc.NewServer(
grpc.UnaryInterceptor(errHandler),
)

// ensure env vars convert to their proper types
sTLS := false
if smtpStartTLS != "" {
var err error
sTLS, err = strconv.ParseBool(smtpStartTLS)
if err != nil {
log.Fatalf("Invalid value for SMTP_STARTTLS: %v", err.Error())
}
}
sPort, err := strconv.ParseInt(smtpPort, 10, 64)
if err != nil {
log.Fatalf("Invalid value for SMTP_PORT: %v", err.Error())
}

// define the service
log.Print("checking email server settings..")
emailService, err := service.NewEmailServer(smtpHost, sPort, smtpUsername, smtpPassword, sTLS)
if err != nil {
log.Fatalf("failed to initialize email service: %v", err)
}

// register the services
pb.RegisterEmailServiceServer(grpcServer, emailService)

// enable reflection
if *enableReflection {
reflection.Register(grpcServer)
log.Print("reflection enabled")
}

// configure the listen address
listen, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}

// serve
log.Printf("server listening at %v", listen.Addr())
if err := grpcServer.Serve(listen); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
22 changes: 22 additions & 0 deletions services/email/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module github.com/accentdesign/grpc/services/email

go 1.20

require (
github.com/google/uuid v1.3.0
github.com/mocktools/go-smtp-mock/v2 v2.0.5
github.com/stretchr/testify v1.8.2
google.golang.org/grpc v1.54.0
google.golang.org/protobuf v1.30.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
42 changes: 42 additions & 0 deletions services/email/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mocktools/go-smtp-mock/v2 v2.0.5 h1:4Y6ZZkumWad8c9YSQjcInSgGbmwOQ2QhwLImywBzuPo=
github.com/mocktools/go-smtp-mock/v2 v2.0.5/go.mod h1:n8aNpDYncZHH/cZHtJKzQyeYT/Dut00RghVM+J1Ed94=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
17 changes: 17 additions & 0 deletions services/email/internal/boundary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package internal

import "github.com/google/uuid"

type BoundaryGenerator interface {
GetBoundary() (string, error)
}

type DefaultBoundaryGenerator struct{}

func (g *DefaultBoundaryGenerator) GetBoundary() (string, error) {
id, err := uuid.NewRandom()
if err != nil {
return "", err
}
return id.String(), nil
}
Loading

0 comments on commit 0f7a82e

Please sign in to comment.