Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.

Commit 460b2ed

Browse files
committed
common, docker, test: Send outgoing email via SMTP2Go
Sending email using a reputable commercial service, rather than having to maintain our own mail servers, seems like the way to go.
1 parent 5df77a2 commit 460b2ed

File tree

9 files changed

+76
-45
lines changed

9 files changed

+76
-45
lines changed

common/config.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,18 @@ func ReadConfig() error {
150150
Conf.Event.EmailQueueProcessingDelay = 10
151151
}
152152

153-
// Warn if the email queue directory isn't set in the config file
154-
if Conf.Event.EmailQueueDir == "" {
155-
log.Printf("WARN: Email queue directory isn't set in the config file. Defaulting to /tmp.")
156-
Conf.Event.EmailQueueDir = "/tmp"
153+
// If an SMTP2Go environment variable is already set, don't mess with it.
154+
tempString = os.Getenv("SMTP2GO_API_KEY")
155+
if tempString != "" {
156+
Conf.Event.Smtp2GoKey = tempString
157+
} else {
158+
// If this is a production environment, and the SMTP2Go env variable wasn't set, we'd better
159+
// warn when the key isn't in the config file either
160+
if Conf.Event.Smtp2GoKey == "" && Conf.Environment.Environment == "production" {
161+
log.Printf("WARN: SMTP2Go API key isn't set in the config file. Event emails won't be sent.")
162+
} else {
163+
os.Setenv("SMTP2GO_API_KEY", Conf.Event.Smtp2GoKey)
164+
}
157165
}
158166

159167
// Check cache directory exists

common/cypress.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package common
33
/* Shared backend code we need that is specific to testing with Cypress */
44

55
import (
6+
"fmt"
67
"log"
78
"net/http"
89
"os"
910
"path"
11+
"strings"
1012
"time"
1113
)
1214

@@ -27,6 +29,14 @@ func CypressSeed(w http.ResponseWriter, r *http.Request) {
2729
// Switch to the default user
2830
Conf.Environment.UserOverride = "default"
2931

32+
// Change the email address of the default user to match the local server
33+
serverName := strings.Split(Conf.Web.ServerName, ":")
34+
err := SetUserPreferences("default", 10, "Default system user", fmt.Sprintf("default@%s", serverName[0]))
35+
if err != nil {
36+
http.Error(w, err.Error(), http.StatusInternalServerError)
37+
return
38+
}
39+
3040
// Add test SQLite databases
3141
testDB, err := os.Open(path.Join(Conf.Web.BaseDir, "cypress", "test_data", "Assembly Election 2017.sqlite"))
3242
if err != nil {
@@ -58,17 +68,17 @@ func CypressSeed(w http.ResponseWriter, r *http.Request) {
5868
}
5969

6070
// Add some test users
61-
err = AddUser("auth0first", "first", RandomString(32), "first@example.org", "First test user", "")
71+
err = AddUser("auth0first", "first", RandomString(32), fmt.Sprintf("first@%s", serverName[0]), "First test user", "")
6272
if err != nil {
6373
http.Error(w, err.Error(), http.StatusInternalServerError)
6474
return
6575
}
66-
err = AddUser("auth0second", "second", RandomString(32), "second@example.org", "Second test user", "")
76+
err = AddUser("auth0second", "second", RandomString(32), fmt.Sprintf("second@%s", serverName[0]), "Second test user", "")
6777
if err != nil {
6878
http.Error(w, err.Error(), http.StatusInternalServerError)
6979
return
7080
}
71-
err = AddUser("auth0third", "third", RandomString(32), "third@example.org", "Third test user", "")
81+
err = AddUser("auth0third", "third", RandomString(32), fmt.Sprintf("third@%s", serverName[0]), "Third test user", "")
7282
if err != nil {
7383
http.Error(w, err.Error(), http.StatusInternalServerError)
7484
return

common/postgresql.go

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import (
1414
"strings"
1515
"time"
1616

17-
"github.com/hectane/hectane/email"
18-
"github.com/hectane/hectane/queue"
17+
"github.com/aquilax/truncate"
1918
"github.com/jackc/pgx"
19+
smtp2go "github.com/smtp2go-oss/smtp2go-go"
2020
gfm "github.com/sqlitebrowser/github_flavored_markdown"
2121
"golang.org/x/crypto/bcrypt"
2222
)
@@ -2981,18 +2981,10 @@ func SaveDBSettings(userName, dbFolder, dbName, oneLineDesc, fullDesc, defaultTa
29812981

29822982
// SendEmails sends status update emails to people watching databases
29832983
func SendEmails() {
2984-
// Create Hectane email queue
2985-
cfg := &queue.Config{
2986-
Directory: Conf.Event.EmailQueueDir,
2987-
DisableSSLVerification: true,
2988-
}
2989-
q, err := queue.NewQueue(cfg)
2990-
if err != nil {
2991-
log.Printf("Couldn't start Hectane queue: %s", err.Error())
2984+
// If the SMTP2Go API key hasn't been configured, there's no use in trying to send emails
2985+
if Conf.Event.Smtp2GoKey == "" && os.Getenv("SMTP2GO_API_KEY") == "" {
29922986
return
29932987
}
2994-
log.Printf("Created Hectane email queue in '%s'. Queue processing loop refreshes every %d seconds",
2995-
Conf.Event.EmailQueueDir, Conf.Event.EmailQueueProcessingDelay)
29962988

29972989
for {
29982990
// Retrieve unsent emails from the email_queue
@@ -3026,22 +3018,22 @@ func SendEmails() {
30263018

30273019
// Send emails
30283020
for _, j := range emailList {
3029-
e := &email.Email{
3030-
3031-
To: []string{j.Address},
3032-
Subject: j.Subject,
3033-
Text: j.Body,
3021+
e := smtp2go.Email{
3022+
3023+
To: []string{j.Address},
3024+
Subject: j.Subject,
3025+
TextBody: j.Body,
3026+
HtmlBody: j.Body,
30343027
}
3035-
msgs, err := e.Messages(q.Storage)
3028+
_, err = smtp2go.Send(&e)
30363029
if err != nil {
3037-
log.Printf("Queuing email in Hectane failed: %v", err.Error())
3038-
return // Abort, as we don't want to continuously resend the same emails
3039-
}
3040-
for _, m := range msgs {
3041-
q.Deliver(m)
3030+
log.Println(err)
30423031
}
30433032

3044-
// Mark message as sent
3033+
log.Printf("Email with subject '%v' sent to '%v'",
3034+
truncate.Truncate(j.Subject, 35, "...", truncate.PositionEnd), j.Address)
3035+
3036+
// We only attempt delivery via smtp2go once (retries are handled on their end), so mark message as sent
30453037
dbQuery := `
30463038
UPDATE email_queue
30473039
SET sent = true, sent_timestamp = now()
@@ -3199,7 +3191,7 @@ func StatusUpdatesLoop() {
31993191
// For each event, add a status update to the status_updates list for each watcher it's for
32003192
for id, ev := range evList {
32013193
// Retrieve the list of watchers for the database the event occurred on
3202-
dbQuery := `
3194+
dbQuery = `
32033195
SELECT user_id
32043196
FROM watchers
32053197
WHERE db_id = $1`
@@ -3228,19 +3220,26 @@ func StatusUpdatesLoop() {
32283220
for _, u := range users {
32293221
// Retrieve the current status updates list for the user
32303222
var eml pgx.NullString
3231-
dbQuery := `
3223+
dbQuery = `
32323224
SELECT user_name, email, status_updates
32333225
FROM users
32343226
WHERE user_id = $1`
32353227
userEvents := make(map[string][]StatusUpdateEntry)
32363228
var userName string
3237-
err := tx.QueryRow(dbQuery, u).Scan(&userName, &eml, &userEvents)
3229+
err = tx.QueryRow(dbQuery, u).Scan(&userName, &eml, &userEvents)
32383230
if err != nil {
32393231
log.Printf("Database query failed: %v\n", err)
32403232
tx.Rollback()
32413233
continue
32423234
}
32433235

3236+
// If the user generated this event themselves, skip them
3237+
if userName == ev.details.UserName {
3238+
log.Printf("User '%v' generated this event (id: %v), so not adding it to their event list",
3239+
userName, ev.eventID)
3240+
continue
3241+
}
3242+
32443243
// * Add the new event to the users status updates list *
32453244

32463245
// Group the status updates by database, and coalesce multiple updates for the same discussion or MR
@@ -3281,7 +3280,7 @@ func StatusUpdatesLoop() {
32813280
}
32823281
if numRows := commandTag.RowsAffected(); numRows != 1 {
32833282
log.Printf("Wrong number of rows affected (%v) when adding status update for database ID "+
3284-
"'%d' to user '%s'", numRows, ev.dbID, u)
3283+
"'%d' to user '%v'", numRows, ev.dbID, u)
32853284
tx.Rollback()
32863285
continue
32873286
}
@@ -3324,19 +3323,27 @@ func StatusUpdatesLoop() {
33243323
log.Printf("Unknown message type when creating email message")
33253324
}
33263325
if eml.Valid {
3327-
// TODO: Check if the email is username@thisserver, which indicates a non-functional email address
3326+
// If the email address is of the form username@this_server (which indicates a non-functional email address), then skip it
3327+
serverName := strings.Split(Conf.Web.ServerName, ":")
3328+
if strings.HasSuffix(eml.String, serverName[0]) {
3329+
log.Printf("Skipping email '%v' to destination '%v', as it ends in '%v'",
3330+
truncate.Truncate(subj, 35, "...", truncate.PositionEnd), eml.String, serverName[0])
3331+
continue
3332+
}
3333+
3334+
// Add the email to the queue
33283335
dbQuery = `
33293336
INSERT INTO email_queue (mail_to, subject, body)
33303337
VALUES ($1, $2, $3)`
33313338
commandTag, err = tx.Exec(dbQuery, eml.String, subj, msg)
33323339
if err != nil {
3333-
log.Printf("Adding status update to email queue for user '%s' failed: %v", u, err)
3340+
log.Printf("Adding status update to email queue for user '%v' failed: %v", u, err)
33343341
tx.Rollback()
33353342
continue
33363343
}
33373344
if numRows := commandTag.RowsAffected(); numRows != 1 {
33383345
log.Printf("Wrong number of rows affected (%v) when adding status update to email"+
3339-
"queue for user '%s'", numRows, u)
3346+
"queue for user '%v'", numRows, u)
33403347
tx.Rollback()
33413348
continue
33423349
}

common/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ type EnvInfo struct {
140140
// EventProcessingInfo hold configuration for the event processing loop
141141
type EventProcessingInfo struct {
142142
Delay time.Duration `toml:"delay"`
143-
EmailQueueDir string `toml:"email_queue_dir"`
144143
EmailQueueProcessingDelay time.Duration `toml:"email_queue_processing_delay"`
144+
Smtp2GoKey string `toml:"smtp2go_key"` // The SMTP2GO API key
145145
}
146146

147147
// LicenceDir holds the path to the licence files

cypress/e2e/1-webui/user-preferences.cy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('logged-in user preferences page', () => {
2222

2323
// Change user email address
2424
it('change user email address', () => {
25-
cy.get('[data-cy="email"]').should('have.attr', 'value', '[email protected]')
25+
cy.get('[data-cy="email"]').should('have.attr', 'value', 'default@docker-dev.dbhub.io')
2626
cy.get('[data-cy="email"]').type('{selectall}{backspace}').type('[email protected]').should('have.value', '[email protected]')
2727
cy.get('[data-cy="updatebtn"]').click()
2828
cy.visit('pref')

cypress/e2e/2-api/api.cy.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ describe('api tests', () => {
8585
let temp = JSON.parse(response.body)
8686
let jsonBody = temp[Object.keys(temp)[0]]
8787

88-
expect(jsonBody).to.have.property('author_email', '[email protected]')
88+
expect(jsonBody).to.have.property('author_email', 'default@docker-dev.dbhub.io')
8989
expect(jsonBody).to.have.property('author_name', 'Default system user')
9090
expect(jsonBody).to.have.property('committer_email', '')
9191
expect(jsonBody).to.have.property('committer_name', '')
@@ -344,7 +344,7 @@ describe('api tests', () => {
344344
// Test the "commits" structure
345345
let commitID = Object.keys(jsonBody.commits)
346346
let commitData = jsonBody.commits[commitID]
347-
expect(commitData).to.have.property('author_email', '[email protected]')
347+
expect(commitData).to.have.property('author_email', 'default@docker-dev.dbhub.io')
348348
expect(commitData).to.have.property('author_name', 'Default system user')
349349
expect(commitData).to.have.property('committer_email', '')
350350
expect(commitData).to.have.property('committer_name', '')
@@ -437,7 +437,7 @@ describe('api tests', () => {
437437
expect(jsonBody).to.have.property('Some release name')
438438
expect(jsonBody['Some release name']).to.include.keys(['commit', 'date'])
439439
expect(jsonBody['Some release name']).to.have.property('description', 'Some release description')
440-
expect(jsonBody['Some release name']).to.have.property('email', '[email protected]')
440+
expect(jsonBody['Some release name']).to.have.property('email', 'default@docker-dev.dbhub.io')
441441
expect(jsonBody['Some release name']).to.have.property('name', 'Default system user')
442442
expect(jsonBody['Some release name']).to.have.property('size', 73728)
443443
}
@@ -509,7 +509,7 @@ describe('api tests', () => {
509509
expect(jsonBody).to.have.property('Some tag name')
510510
expect(jsonBody['Some tag name']).to.include.keys(['commit', 'date'])
511511
expect(jsonBody['Some tag name']).to.have.property('description', 'Some tag description')
512-
expect(jsonBody['Some tag name']).to.have.property('email', '[email protected]')
512+
expect(jsonBody['Some tag name']).to.have.property('email', 'default@docker-dev.dbhub.io')
513513
expect(jsonBody['Some tag name']).to.have.property('name', 'Default system user')
514514
}
515515
)

docker/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ user_override = "default"
1515
[event]
1616
delay = 2
1717
email_queue_processing_delay = 5
18-
email_queue_dir = "/home/dbhub/.dbhub/email_queue"
18+
smtp2go_key = ""
1919

2020
[licence]
2121
licence_dir = "/dbhub.io/default_licences"

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ require (
3131

3232
require (
3333
github.com/Freeaqingme/dkim v0.0.0-20160606200812-57418d2fbdcd // indirect
34+
github.com/aquilax/truncate v1.0.0 // indirect
3435
github.com/aymerick/douceur v0.2.0 // indirect
3536
github.com/go-ini/ini v1.56.0 // indirect
3637
github.com/go-playground/locales v0.13.0 // indirect
@@ -64,6 +65,7 @@ require (
6465
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
6566
github.com/sirupsen/logrus v1.6.0 // indirect
6667
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
68+
github.com/smtp2go-oss/smtp2go-go v1.0.2 // indirect
6769
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect
6870
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect
6971
github.com/sqlitebrowser/blackfriday v9.0.0+incompatible // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ github.com/Freeaqingme/dkim v0.0.0-20160606200812-57418d2fbdcd h1:OXAdgeC8/sNLxo
55
github.com/Freeaqingme/dkim v0.0.0-20160606200812-57418d2fbdcd/go.mod h1:2jaO1ZDxGuwzrfRhIe1QKvws/FY3SJ9f1EMNLQLTa0g=
66
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
77
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
8+
github.com/aquilax/truncate v1.0.0 h1:UgIGS8U/aZ4JyOJ2h3xcF5cSQ06+gGBnjxH2RUHJe0U=
9+
github.com/aquilax/truncate v1.0.0/go.mod h1:BeMESIDMlvlS3bmg4BVvBbbZUNwWtS8uzYPAKXwwhLw=
810
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
911
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
1012
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
@@ -128,6 +130,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
128130
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
129131
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
130132
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
133+
github.com/smtp2go-oss/smtp2go-go v1.0.2 h1:vXkqx9kyoQIuetyV3nm40b+OZevihhgb78X4vA/u2fs=
134+
github.com/smtp2go-oss/smtp2go-go v1.0.2/go.mod h1:lkv36awQXRBWAvnd517FFESKvne8465KCu90lPThcEY=
131135
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E=
132136
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
133137
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8=

0 commit comments

Comments
 (0)