Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Forward Email for smtp and bounce support #2016

Merged
merged 5 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions cmd/bounce.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,23 @@ func handleBounceWebhook(c echo.Context) error {
}
bounces = append(bounces, bs...)

// ForwardEmail.
case service == "forwardemail" && app.constants.BounceForwardemailEnabled:
var (
sig = c.Request().Header.Get("X-Webhook-Signature")
)

bs, err := app.bounce.Forwardemail.ProcessBounce([]byte(sig), rawReq)
if err != nil {
app.log.Printf("error processing forwardemail notification: %v", err)
if _, ok := err.(*echo.HTTPError); ok {
return err
}

return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData"))
}
bounces = append(bounces, bs...)

default:
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("bounces.unknownService"))
}
Expand Down
19 changes: 15 additions & 4 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,11 @@ type constants struct {
Extensions []string
}

BounceWebhooksEnabled bool
BounceSESEnabled bool
BounceSendgridEnabled bool
BouncePostmarkEnabled bool
BounceWebhooksEnabled bool
BounceSESEnabled bool
BounceSendgridEnabled bool
BouncePostmarkEnabled bool
BounceForwardemailEnabled bool

PermissionsRaw json.RawMessage
Permissions map[string]struct{}
Expand Down Expand Up @@ -432,6 +433,9 @@ func initConstants() *constants {
c.BounceSESEnabled = ko.Bool("bounce.ses_enabled")
c.BounceSendgridEnabled = ko.Bool("bounce.sendgrid_enabled")
c.BouncePostmarkEnabled = ko.Bool("bounce.postmark.enabled")
c.BounceForwardemailEnabled = ko.Bool("bounce.forwardemail.enabled")

fmt.Println(c.BounceForwardemailEnabled)

c.HasLegacyUser = ko.Exists("app.admin_username") || ko.Exists("app.admin_password")

Expand Down Expand Up @@ -709,6 +713,13 @@ func initBounceManager(app *App) *bounce.Manager {
ko.String("bounce.postmark.username"),
ko.String("bounce.postmark.password"),
},
ForwardEmail: struct {
Enabled bool
Key string
}{
ko.Bool("bounce.forwardemail.enabled"),
ko.String("bounce.forwardemail.key"),
},
RecordBounceCB: app.core.RecordBounce,
}

Expand Down
7 changes: 6 additions & 1 deletion cmd/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ func handleGetSettings(c echo.Context) error {
for i := 0; i < len(s.Messengers); i++ {
s.Messengers[i].Password = strings.Repeat(pwdMask, utf8.RuneCountInString(s.Messengers[i].Password))
}

s.UploadS3AwsSecretAccessKey = strings.Repeat(pwdMask, utf8.RuneCountInString(s.UploadS3AwsSecretAccessKey))
s.SendgridKey = strings.Repeat(pwdMask, utf8.RuneCountInString(s.SendgridKey))
s.BouncePostmark.Password = strings.Repeat(pwdMask, utf8.RuneCountInString(s.BouncePostmark.Password))
s.BounceForwardEmail.Key = strings.Repeat(pwdMask, utf8.RuneCountInString(s.BounceForwardEmail.Key))
s.SecurityCaptchaSecret = strings.Repeat(pwdMask, utf8.RuneCountInString(s.SecurityCaptchaSecret))
s.OIDC.ClientSecret = strings.Repeat(pwdMask, utf8.RuneCountInString(s.OIDC.ClientSecret))
s.BouncePostmark.Password = strings.Repeat(pwdMask, utf8.RuneCountInString(s.BouncePostmark.Password))

return c.JSON(http.StatusOK, okResp{s})
}
Expand Down Expand Up @@ -199,6 +201,9 @@ func handleUpdateSettings(c echo.Context) error {
if set.BouncePostmark.Password == "" {
set.BouncePostmark.Password = cur.BouncePostmark.Password
}
if set.BounceForwardEmail.Key == "" {
set.BounceForwardEmail.Key = cur.BounceForwardEmail.Key
}
if set.SecurityCaptchaSecret == "" {
set.SecurityCaptchaSecret = cur.SecurityCaptchaSecret
}
Expand Down
1 change: 1 addition & 0 deletions cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var migList = []migFunc{
{"v2.5.0", migrations.V2_5_0},
{"v3.0.0", migrations.V3_0_0},
{"v4.0.0", migrations.V4_0_0},
{"v4.1.0", migrations.V4_1_0},
}

// upgrade upgrades the database to the current version by running SQL migration files
Expand Down
11 changes: 6 additions & 5 deletions docs/docs/content/bounces.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ curl -u curl -u 'api_username:access_token' -X POST 'http://localhost:9000/webho
## External webhooks
listmonk supports receiving bounce webhook events from the following SMTP providers.

| Endpoint | Description | More info |
|:----------------------------------------------------------|:---------------------------------------|:----------------------------------------------------------------------------------------------------------------------|
| `https://listmonk.yoursite.com/webhooks/service/ses` | Amazon (AWS) SES | See below |
| `https://listmonk.yoursite.com/webhooks/service/sendgrid` | Sendgrid / Twilio Signed event webhook | [More info](https://docs.sendgrid.com/for-developers/tracking-events/getting-started-event-webhook-security-features) |
| `https://listmonk.yoursite.com/webhooks/service/postmark` | Postmark webhook | [More info](https://postmarkapp.com/developer/webhooks/webhooks-overview) |
| Endpoint | Description | More info |
|:--------------------------------------------------------------|:---------------------------------------|:----------------------------------------------------------------------------------------------------------------------|
| `https://listmonk.yoursite.com/webhooks/service/ses` | Amazon (AWS) SES | See below |
| `https://listmonk.yoursite.com/webhooks/service/sendgrid` | Sendgrid / Twilio Signed event webhook | [More info](https://docs.sendgrid.com/for-developers/tracking-events/getting-started-event-webhook-security-features) |
| `https://listmonk.yoursite.com/webhooks/service/postmark` | Postmark webhook | [More info](https://postmarkapp.com/developer/webhooks/webhooks-overview) |
| `https://listmonk.yoursite.com/webhooks/service/forwardemail` | Forward Email webhook | [More info](https://forwardemail.net/en/faq#do-you-support-bounce-webhooks) |

## Amazon Simple Email Service (SES)

Expand Down
8 changes: 8 additions & 0 deletions docs/swagger/collections.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2705,6 +2705,8 @@ components:
type: string
settings.bounces.enableSendgrid:
type: string
settings.bounces.enableForwardemail:
type: string
settings.bounces.enablePostmark:
type: string
settings.bounces.enableWebhooks:
Expand All @@ -2725,6 +2727,8 @@ components:
type: string
settings.bounces.sendgridKey:
type: string
settings.bounces.forwardemailKey:
type: string
settings.bounces.postmarkUsername:
type: string
settings.bounces.postmarkUsernameHelp:
Expand Down Expand Up @@ -3408,6 +3412,10 @@ components:
type: boolean
bounce.sendgrid_key:
type: string
bounce.forwardemail_enabled:
type: boolean
bounce.forwardemail_key:
type: string
bounce.postmark_enabled:
type: boolean
bounce.postmark_username:
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/views/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ export default Vue.extend({
hasDummy = 'postmark';
}

if (this.isDummy(form['bounce.forwardemail'].key)) {
form['bounce.forwardemail'].key = '';
} else if (this.hasDummy(form['bounce.forwardemail'].key)) {
hasDummy = 'forwardemail';
}

for (let i = 0; i < form.messengers.length; i += 1) {
// If it's the dummy UI password placeholder, ignore it.
if (this.isDummy(form.messengers[i].password)) {
Expand Down
28 changes: 23 additions & 5 deletions frontend/src/views/settings/bounces.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@
</div>
<div class="column">
<b-field :label="$t('settings.bounces.sendgridKey')" :message="$t('globals.messages.passwordChange')">
<b-input v-model="data['bounce.sendgrid_key']" type="password" :disabled="!data['bounce.sendgrid_enabled']"
name="sendgrid_enabled" :native-value="true" data-cy="btn-enable-bounce-sendgrid" />
<b-input v-model="data['bounce.sendgrid_key']" type="password"
:disabled="!data['bounce.sendgrid_enabled']" name="sendgrid_enabled" :native-value="true"
data-cy="btn-enable-bounce-sendgrid" />
</b-field>
</div>
</div>
Expand All @@ -83,8 +84,9 @@
<div class="column">
<b-field :label="$t('settings.bounces.postmarkUsername')"
:message="$t('settings.bounces.postmarkUsernameHelp')">
<b-input v-model="data['bounce.postmark'].username" type="text" :disabled="!data['bounce.postmark'].enabled"
name="postmark_username" data-cy="btn-enable-bounce-postmark" />
<b-input v-model="data['bounce.postmark'].username" type="text"
:disabled="!data['bounce.postmark'].enabled" name="postmark_username"
data-cy="btn-enable-bounce-postmark" />
</b-field>
</div>
<div class="column">
Expand All @@ -95,6 +97,21 @@
</b-field>
</div>
</div>
<div class="columns">
<div class="column is-3">
<b-field :label="$t('settings.bounces.enableForwardemail')">
<b-switch v-model="data['bounce.forwardemail'].enabled" name="forwardemail_enabled" :native-value="true"
data-cy="btn-enable-bounce-forwardemail" />
</b-field>
</div>
<div class="column">
<b-field :label="$t('settings.bounces.forwardemailKey')" :message="$t('globals.messages.passwordChange')">
<b-input v-model="data['bounce.forwardemail'].key" type="password"
:disabled="!data['bounce.forwardemail'].enabled" name="forwardemail_enabled" :native-value="true"
data-cy="btn-enable-bounce-forwardemail" />
</b-field>
</div>
</div>
</div>
</div>

Expand Down Expand Up @@ -180,7 +197,8 @@
</b-field>
<b-field :label="$t('settings.mailserver.skipTLS')" expanded
:message="$t('settings.mailserver.skipTLSHelp')">
<b-switch v-model="item.tls_skip_verify" :disabled="!item.tls_enabled" name="item.tls_skip_verify" />
<b-switch v-model="item.tls_skip_verify" :disabled="!item.tls_enabled"
name="item.tls_skip_verify" />
</b-field>
</b-field>
</div>
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/views/settings/smtp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<a href="#" @click.prevent="() => fillSettings(n, 'mailjet')">Mailjet</a>
<a href="#" @click.prevent="() => fillSettings(n, 'sendgrid')">Sendgrid</a>
<a href="#" @click.prevent="() => fillSettings(n, 'postmark')">Postmark</a>
<a href="#" @click.prevent="() => fillSettings(n, 'forwardemail')">Forward Email</a>
</div>
<hr />

Expand Down Expand Up @@ -165,8 +166,8 @@
</div>
<div class="column is-4">
<b-field :label="$t('settings.smtp.toEmail')" label-position="on-border">
<b-input type="email" required v-model="testEmail" :ref="'testEmailTo'" placeholder="[email protected]"
:custom-class="`test-email-${n}`" />
<b-input type="email" required v-model="testEmail" :ref="'testEmailTo'"
placeholder="[email protected]" :custom-class="`test-email-${n}`" />
</b-field>
</div>
</template>
Expand Down Expand Up @@ -220,6 +221,9 @@ const smtpTemplates = {
sendgrid: {
host: 'smtp.sendgrid.net', port: 465, auth_protocol: 'login', tls_type: 'TLS',
},
forwardemail: {
host: 'smtp.forwardemail.net', port: 465, auth_protocol: 'login', tls_type: 'TLS',
},
postmark: {
host: 'smtp.postmarkapp.com', port: 587, auth_protocol: 'cram', tls_type: 'STARTTLS',
},
Expand Down
2 changes: 2 additions & 0 deletions i18n/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@
"settings.bounces.countHelp": "Nombre de rebots per subscriptor",
"settings.bounces.delete": "Esborra",
"settings.bounces.enable": "Activa el processament de rebots",
"settings.bounces.enableForwardemail": "Enable Forward Email",
"settings.bounces.enableMailbox": "Activa la bústia de rebots",
"settings.bounces.enablePostmark": "Activa Postmark",
"settings.bounces.enableSES": "Activa SES",
Expand All @@ -383,6 +384,7 @@
"settings.bounces.enabled": "Activat",
"settings.bounces.folder": "Carpeta",
"settings.bounces.folderHelp": "Nom de la carpeta IMAP a escanejar. Ex: Safata d'entrada.",
"settings.bounces.forwardemailKey": "Forward Email Key",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "L'interval d'escaneig ha de ser com a mínim d'1 minut.",
"settings.bounces.name": "Rebots",
Expand Down
2 changes: 2 additions & 0 deletions i18n/cs-cz.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@
"settings.bounces.countHelp": "Počet případů nedoručitelnosti na odběratele",
"settings.bounces.delete": "Odstranit",
"settings.bounces.enable": "Povolit zpracování nedoručitelnosti",
"settings.bounces.enableForwardemail": "Enable Forward Email",
"settings.bounces.enableMailbox": "Povolit poštovní schránku v případě nedoručitelnosti",
"settings.bounces.enablePostmark": "Povolit Postmark",
"settings.bounces.enableSES": "Povolit SES",
Expand All @@ -383,6 +384,7 @@
"settings.bounces.enabled": "Povoleno",
"settings.bounces.folder": "Složka",
"settings.bounces.folderHelp": "Název složky IMAP ke skenování. Např.: Došlá pošta.",
"settings.bounces.forwardemailKey": "Forward Email Key",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Interval skenování v případě nedoručitelnosti by měl být minimálně 1 minuta.",
"settings.bounces.name": "Případy nedoručitelnosti",
Expand Down
2 changes: 2 additions & 0 deletions i18n/cy.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@
"settings.bounces.countHelp": "Nifer y pethau sydd wedi sboncio'n ôl fesul tanysgrifiwr",
"settings.bounces.delete": "Dileu",
"settings.bounces.enable": "Galluogi proses sboncio'n ôl",
"settings.bounces.enableForwardemail": "Enable Forward Email",
"settings.bounces.enableMailbox": "Galluogi blwch post negeseuon sydd wedi sboncio'n ôl",
"settings.bounces.enablePostmark": "Galluogi Postmark",
"settings.bounces.enableSES": "Galluogi SES",
Expand All @@ -383,6 +384,7 @@
"settings.bounces.enabled": "Wedi galluogi",
"settings.bounces.folder": "Ffolder",
"settings.bounces.folderHelp": "Enw'r ffolder IMAP i'w sganio. ee: blwch derbyn.",
"settings.bounces.forwardemailKey": "Forward Email Key",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Dylai'r cyfnod sganio ar gyfer negeseuon sydd wedi sboncio'n ôl bara o leiaf 1 munud",
"settings.bounces.name": "Wedi sboncio'n ôl",
Expand Down
2 changes: 2 additions & 0 deletions i18n/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@
"settings.bounces.count": "Antal afvisninger",
"settings.bounces.countHelp": "Antal afvisninger pr. abonnent",
"settings.bounces.enable": "Aktivér bounce behandling",
"settings.bounces.enableForwardemail": "Enable Forward Email",
"settings.bounces.enableMailbox": "Aktivér bounce-postkasse",
"settings.bounces.enablePostmark": "Aktivér poststempel",
"settings.bounces.enableSES": "Aktiver SES",
Expand All @@ -381,6 +382,7 @@
"settings.bounces.enabled": "Aktiveret",
"settings.bounces.folder": "Mappe",
"settings.bounces.folderHelp": "Navnet på den IMAP-mappe, der skal scannes. F.eks.: Indbakke.",
"settings.bounces.forwardemailKey": "Forward Email Key",
"settings.bounces.invalidScanInterval": "Bounce skanningsinterval skal være mindst 1 minut.",
"settings.bounces.name": "Fejlsendt",
"settings.bounces.none": "Ingen",
Expand Down
2 changes: 2 additions & 0 deletions i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@
"settings.bounces.count": "Bounce Anzahl",
"settings.bounces.countHelp": "Anzahl von Bounces pro Abonnent",
"settings.bounces.enable": "Verarbeiten von Bounces aktivieren",
"settings.bounces.enableForwardemail": "Enable Forward Email",
"settings.bounces.enableMailbox": "Bounce-Postfach aktivieren",
"settings.bounces.enablePostmark": "Postmark aktivieren",
"settings.bounces.enableSES": "SES aktivieren",
Expand All @@ -381,6 +382,7 @@
"settings.bounces.enabled": "Aktiviert",
"settings.bounces.folder": "Ordner",
"settings.bounces.folderHelp": "Name des zu scannenden IMAP-Ordners. z.B.: Inbox.",
"settings.bounces.forwardemailKey": "Forward Email Key",
"settings.bounces.invalidScanInterval": "Der Bounce Scan-Interval sollte mindestens 1 Minute betragen.",
"settings.bounces.name": "Bounces",
"settings.bounces.none": "Keine",
Expand Down
2 changes: 2 additions & 0 deletions i18n/el.json
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@
"settings.bounces.count": "Πλήθος bounce",
"settings.bounces.countHelp": "Αριθμός bounce ανά συνδρομητή",
"settings.bounces.enable": "Ενεργοποίηση επεξεργασίας bounce",
"settings.bounces.enableForwardemail": "Enable Forward Email",
"settings.bounces.enableMailbox": "Ενεργοποίηση γραμματοκιβωτίου για τα bounce",
"settings.bounces.enablePostmark": "Ενεργοποίηση Postmark",
"settings.bounces.enableSES": "Ενεργοποίηση SES",
Expand All @@ -381,6 +382,7 @@
"settings.bounces.enabled": "Ενεργοποιημένο",
"settings.bounces.folder": "Φάκελος",
"settings.bounces.folderHelp": "Όνομα του φακέλου IMAP προς περιοδική σάρωση. Π.χ.: Εισερχόμενα.",
"settings.bounces.forwardemailKey": "Forward Email Key",
"settings.bounces.invalidScanInterval": "Το διάστημα σάρωσης για αναγνώριση των bounce πρέπει να είναι τουλάχιστον 1 λεπτό.",
"settings.bounces.name": "Bounce",
"settings.bounces.none": "Κανένα",
Expand Down
2 changes: 2 additions & 0 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@
"settings.bounces.count": "Bounce count",
"settings.bounces.countHelp": "Number of bounces per subscriber",
"settings.bounces.enable": "Enable bounce processing",
"settings.bounces.enableForwardemail": "Enable Forward Email",
"settings.bounces.enableMailbox": "Enable bounce mailbox",
"settings.bounces.enablePostmark": "Enable Postmark",
"settings.bounces.enableSES": "Enable SES",
Expand All @@ -381,6 +382,7 @@
"settings.bounces.enabled": "Enabled",
"settings.bounces.folder": "Folder",
"settings.bounces.folderHelp": "Name of the IMAP folder to scan. Eg: Inbox.",
"settings.bounces.forwardemailKey": "Forward Email Key",
"settings.bounces.invalidScanInterval": "Bounce scan interval should be minimum 1 minute.",
"settings.bounces.name": "Bounces",
"settings.bounces.none": "None",
Expand Down
2 changes: 2 additions & 0 deletions i18n/eo.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@
"settings.bounces.countHelp": "Nombre de rebots per subscriptor",
"settings.bounces.delete": "Esborra",
"settings.bounces.enable": "Activa el processament de rebots",
"settings.bounces.enableForwardemail": "Enable Forward Email",
"settings.bounces.enableMailbox": "Activa la bústia de rebots",
"settings.bounces.enablePostmark": "Activa Postmark",
"settings.bounces.enableSES": "Activa SES",
Expand All @@ -383,6 +384,7 @@
"settings.bounces.enabled": "Activat",
"settings.bounces.folder": "Carpeta",
"settings.bounces.folderHelp": "Nom de la carpeta IMAP a escanejar. Ex: Safata d'entrada.",
"settings.bounces.forwardemailKey": "Forward Email Key",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "L'interval d'escaneig ha de ser com a mínim d'1 minut.",
"settings.bounces.name": "Rebots",
Expand Down
Loading