diff --git a/cmd/bounce.go b/cmd/bounce.go
index 0c2231a5..6f418a41 100644
--- a/cmd/bounce.go
+++ b/cmd/bounce.go
@@ -191,6 +191,19 @@ func handleBounceWebhook(c echo.Context) error {
}
bounces = append(bounces, bs...)
+ // Postmark.
+ case service == "postmark" && app.constants.BouncePostmarkEnabled:
+ bs, err := app.bounce.Postmark.ProcessBounce(rawReq, c)
+ if err != nil {
+ app.log.Printf("error processing postmark 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"))
}
diff --git a/cmd/init.go b/cmd/init.go
index f5c481a0..25f05bc3 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -103,6 +103,7 @@ type constants struct {
BounceWebhooksEnabled bool
BounceSESEnabled bool
BounceSendgridEnabled bool
+ BouncePostmarkEnabled bool
}
type notifTpls struct {
@@ -400,6 +401,8 @@ func initConstants() *constants {
c.BounceWebhooksEnabled = ko.Bool("bounce.webhooks_enabled")
c.BounceSESEnabled = ko.Bool("bounce.ses_enabled")
c.BounceSendgridEnabled = ko.Bool("bounce.sendgrid_enabled")
+ c.BouncePostmarkEnabled = ko.Bool("bounce.postmark.enabled")
+
return &c
}
@@ -668,7 +671,15 @@ func initBounceManager(app *App) *bounce.Manager {
SESEnabled: ko.Bool("bounce.ses_enabled"),
SendgridEnabled: ko.Bool("bounce.sendgrid_enabled"),
SendgridKey: ko.String("bounce.sendgrid_key"),
-
+ Postmark: struct {
+ Enabled bool
+ Username string
+ Password string
+ }{
+ ko.Bool("bounce.postmark.enabled"),
+ ko.String("bounce.postmark.username"),
+ ko.String("bounce.postmark.password"),
+ },
RecordBounceCB: app.core.RecordBounce,
}
diff --git a/cmd/settings.go b/cmd/settings.go
index cbef5325..f8411b33 100644
--- a/cmd/settings.go
+++ b/cmd/settings.go
@@ -69,6 +69,7 @@ func handleGetSettings(c echo.Context) error {
s.UploadS3AwsSecretAccessKey = strings.Repeat(pwdMask, utf8.RuneCountInString(s.UploadS3AwsSecretAccessKey))
s.SendgridKey = strings.Repeat(pwdMask, utf8.RuneCountInString(s.SendgridKey))
s.SecurityCaptchaSecret = strings.Repeat(pwdMask, utf8.RuneCountInString(s.SecurityCaptchaSecret))
+ s.BouncePostmark.Password = strings.Repeat(pwdMask, utf8.RuneCountInString(s.BouncePostmark.Password))
return c.JSON(http.StatusOK, okResp{s})
}
@@ -183,6 +184,9 @@ func handleUpdateSettings(c echo.Context) error {
if set.SendgridKey == "" {
set.SendgridKey = cur.SendgridKey
}
+ if set.BouncePostmark.Password == "" {
+ set.BouncePostmark.Password = cur.BouncePostmark.Password
+ }
if set.SecurityCaptchaSecret == "" {
set.SecurityCaptchaSecret = cur.SecurityCaptchaSecret
}
diff --git a/cmd/upgrade.go b/cmd/upgrade.go
index bd0c1759..3eedb1a3 100644
--- a/cmd/upgrade.go
+++ b/cmd/upgrade.go
@@ -36,6 +36,7 @@ var migList = []migFunc{
{"v2.3.0", migrations.V2_3_0},
{"v2.4.0", migrations.V2_4_0},
{"v2.5.0", migrations.V2_5_0},
+ {"v2.6.0", migrations.V2_6_0},
}
// upgrade upgrades the database to the current version by running SQL migration files
diff --git a/docs/docs/content/bounces.md b/docs/docs/content/bounces.md
index 15332df7..c13c83aa 100644
--- a/docs/docs/content/bounces.md
+++ b/docs/docs/content/bounces.md
@@ -18,12 +18,12 @@ Some mail servers may also return the bounce to the `Reply-To` address, which ca
The bounce webhook API can be used to record bounce events with custom scripting. This could be by reading a mailbox, a database, or mail server logs.
| Method | Endpoint | Description |
-|--------|------------------|------------------------|
+| ------ | ---------------- | ---------------------- |
| `POST` | /webhooks/bounce | Record a bounce event. |
| Name | Data type | Required/Optional | Description |
-|-------------------|-----------|-------------------|--------------------------------------------------------------------------------------|
+| ----------------- | --------- | ----------------- | ------------------------------------------------------------------------------------ |
| `subscriber_uuid` | String | Optional | The UUID of the subscriber. Either this or `email` is required. |
| `email` | String | Optional | The e-mail of the subscriber. Either this or `subscriber_uuid` is required. |
| `campaign_uuid` | String | Optional | UUID of the campaign for which the bounce happened. |
@@ -46,7 +46,7 @@ listmonk supports receiving bounce webhook events from the following SMTP provid
|-----------------------------|------------------|-----------|
| `https://listmonk.yoursite.com/webhooks/service/ses` | Amazon (AWS) SES | You can use these [Mautic steps](https://docs.mautic.org/en/channels/emails/bounce-management#amazon-webhook) as a general guide, but use your listmonk's endpoint instead.
- When creating the *topic* select "standard" instead of the preselected "FIFO". You can put a name and leave everything else at default.
- When creating a *subscription* choose HTTPS for "Protocol", and leave *"Enable raw message delivery"* UNCHECKED.
- On the _"SES -> verified identities"_ page, make sure to check **"[include original headers](https://github.com/knadh/listmonk/issues/720#issuecomment-1046877192)"**.
- The Mautic screenshot suggests you should turn off _email feedback forwarding_, but that's completely optional depending on whether you want want email notifications.
|
| `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)
## Verification
diff --git a/docs/swagger/collections.yaml b/docs/swagger/collections.yaml
index 7cb37c86..d68df248 100644
--- a/docs/swagger/collections.yaml
+++ b/docs/swagger/collections.yaml
@@ -2703,6 +2703,8 @@ components:
type: string
settings.bounces.enableSendgrid:
type: string
+ settings.bounces.enablePostmark:
+ type: string
settings.bounces.enableWebhooks:
type: string
settings.bounces.enabled:
@@ -2721,6 +2723,12 @@ components:
type: string
settings.bounces.sendgridKey:
type: string
+ settings.bounces.postmarkUsername:
+ type: string
+ settings.bounces.postmarkUsernameHelp:
+ type: string
+ settings.bounces.postmarkPassword:
+ type: string
settings.bounces.type:
type: string
settings.bounces.username:
@@ -3398,6 +3406,12 @@ components:
type: boolean
bounce.sendgrid_key:
type: string
+ bounce.postmark_enabled:
+ type: boolean
+ bounce.postmark_username:
+ type: string
+ bounce.postmark_password:
+ type: string
bounce.mailboxes:
type: array
items:
diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue
index e04e4bb9..6fd4e06d 100644
--- a/frontend/src/views/Settings.vue
+++ b/frontend/src/views/Settings.vue
@@ -155,6 +155,12 @@ export default Vue.extend({
hasDummy = 'captcha';
}
+ if (this.isDummy(form['bounce.postmark'].password)) {
+ form['bounce.postmark'].password = '';
+ } else if (this.hasDummy(form['bounce.postmark'].password)) {
+ hasDummy = 'postmark';
+ }
+
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)) {
diff --git a/frontend/src/views/settings/bounces.vue b/frontend/src/views/settings/bounces.vue
index c1714d4c..8ffbc8c3 100644
--- a/frontend/src/views/settings/bounces.vue
+++ b/frontend/src/views/settings/bounces.vue
@@ -73,6 +73,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/i18n/ca.json b/i18n/ca.json
index 15685177..557fed5f 100644
--- a/i18n/ca.json
+++ b/i18n/ca.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "Activa SES",
"settings.bounces.enableSendgrid": "Activa SendGrid",
"settings.bounces.enableWebhooks": "Activa els webhooks pels rebots",
+ "settings.bounces.enablePostmark": "Activa Postmark",
"settings.bounces.enabled": "Activat",
"settings.bounces.folder": "Carpeta",
"settings.bounces.folderHelp": "Nom de la carpeta IMAP a escanejar. Ex: Safata d'entrada.",
@@ -577,4 +578,4 @@
"templates.subject": "Assumpte",
"users.login": "Inicia sessió",
"users.logout": "Tanca sessió"
-}
+}
\ No newline at end of file
diff --git a/i18n/cs-cz.json b/i18n/cs-cz.json
index 0ca61523..a3649ff4 100644
--- a/i18n/cs-cz.json
+++ b/i18n/cs-cz.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "Povolit SES",
"settings.bounces.enableSendgrid": "Povolit SendGrid",
"settings.bounces.enableWebhooks": "Povolit webhooky v případě nedoručitelnosti",
+ "settings.bounces.enablePostmark": "Povolit Postmark",
"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.",
@@ -577,4 +578,4 @@
"templates.subject": "Předmět",
"users.login": "Přihlásit",
"users.logout": "Odhlásit"
-}
+}
\ No newline at end of file
diff --git a/i18n/cy.json b/i18n/cy.json
index abeb3856..900449f2 100644
--- a/i18n/cy.json
+++ b/i18n/cy.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "Galluogi SES",
"settings.bounces.enableSendgrid": "Galluogi SendGrid",
"settings.bounces.enableWebhooks": "Galluogi bachau gwe sydd wedi sboncio'n ôl",
+ "settings.bounces.enablePostmark": "Galluogi Postmark",
"settings.bounces.enabled": "Wedi galluogi",
"settings.bounces.folder": "Ffolder",
"settings.bounces.folderHelp": "Enw'r ffolder IMAP i'w sganio. ee: blwch derbyn.",
@@ -577,4 +578,4 @@
"templates.subject": "Pwnc",
"users.login": "Mewngofnodi",
"users.logout": "Allgofnodi"
-}
+}
\ No newline at end of file
diff --git a/i18n/de.json b/i18n/de.json
index 1206ec05..f04055cf 100644
--- a/i18n/de.json
+++ b/i18n/de.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "SES aktivieren",
"settings.bounces.enableSendgrid": "SendGrid aktivieren",
"settings.bounces.enableWebhooks": "Bounce-Webhooks aktivieren",
+ "settings.bounces.enablePostmark": "Postmark aktivieren",
"settings.bounces.enabled": "Aktiviert",
"settings.bounces.folder": "Ordner",
"settings.bounces.folderHelp": "Name des zu scannenden IMAP-Ordners. z.B.: Inbox.",
@@ -577,4 +578,4 @@
"templates.subject": "Betreff",
"users.login": "Anmelden",
"users.logout": "Abmelden"
-}
+}
\ No newline at end of file
diff --git a/i18n/en.json b/i18n/en.json
index 339f1ba2..be947549 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -363,6 +363,7 @@
"settings.bounces.enableSES": "Enable SES",
"settings.bounces.enableSendgrid": "Enable SendGrid",
"settings.bounces.enableWebhooks": "Enable bounce webhooks",
+ "settings.bounces.enablePostmark": "Enable Postmark",
"settings.bounces.enabled": "Enabled",
"settings.bounces.folder": "Folder",
"settings.bounces.folderHelp": "Name of the IMAP folder to scan. Eg: Inbox.",
@@ -372,6 +373,9 @@
"settings.bounces.scanInterval": "Scan interval",
"settings.bounces.scanIntervalHelp": "Interval at which the bounce mailbox should be scanned for bounces (s for second, m for minute).",
"settings.bounces.sendgridKey": "SendGrid Key",
+ "settings.bounces.postmarkUsername": "Postmark Username",
+ "settings.bounces.postmarkUsernameHelp": "Postmark allows you to enable basic authorization for webhooks. Make sure to enter the same credentials here and in your Postmark webhook settings.",
+ "settings.bounces.postmarkPassword": "Postmark Password",
"settings.bounces.type": "Type",
"settings.bounces.username": "Username",
"settings.confirmRestart": "Ensure running campaigns are paused. Restart?",
@@ -573,4 +577,4 @@
"templates.subject": "Subject",
"users.login": "Login",
"users.logout": "Logout"
-}
+}
\ No newline at end of file
diff --git a/i18n/es.json b/i18n/es.json
index 7b2f45c1..40eacf40 100644
--- a/i18n/es.json
+++ b/i18n/es.json
@@ -366,6 +366,7 @@
"settings.bounces.enableSES": "Activar SES",
"settings.bounces.enableSendgrid": "Activar SendGrid",
"settings.bounces.enableWebhooks": "Activar webhooks de rebotes",
+ "settings.bounces.enablePostmark": "Activar Postmark",
"settings.bounces.enabled": "Activado",
"settings.bounces.folder": "Carpeta",
"settings.bounces.folderHelp": "Nombre de la carpeta IMAP a escanear, por ejemplo: Entrada.",
@@ -578,4 +579,4 @@
"templates.subject": "Asunto",
"users.login": "Ingresar",
"users.logout": "Salir"
-}
+}
\ No newline at end of file
diff --git a/i18n/fi.json b/i18n/fi.json
index 4b53d0ae..2693c438 100644
--- a/i18n/fi.json
+++ b/i18n/fi.json
@@ -365,6 +365,7 @@
"settings.bounces.enableMailbox": "Ota käyttöön bounce-postilaatikko",
"settings.bounces.enableSES": "Ota käyttöön SES",
"settings.bounces.enableSendgrid": "Ota käyttöön SendGrid",
+ "settings.bounces.enablePostmark": "Ota käyttöön Postmark",
"settings.bounces.enableWebhooks": "Ota käyttöön palautusten webhookit",
"settings.bounces.enabled": "Käytössä",
"settings.bounces.folder": "Kansio",
@@ -578,4 +579,4 @@
"templates.subject": "Aihe",
"users.login": "Kirjaudu sisään",
"users.logout": "Kirjaudu ulos"
-}
+}
\ No newline at end of file
diff --git a/i18n/fr.json b/i18n/fr.json
index 05c9eb75..a8db607e 100644
--- a/i18n/fr.json
+++ b/i18n/fr.json
@@ -366,6 +366,7 @@
"settings.bounces.enableSES": "Activer SES",
"settings.bounces.enableSendgrid": "Activer SendGrid",
"settings.bounces.enableWebhooks": "Activez les 'webhooks' de rebond",
+ "settings.bounces.enablePostmark": "Activer Postmark",
"settings.bounces.enabled": "Activer",
"settings.bounces.folder": "Dossier",
"settings.bounces.folderHelp": "Nom du dossier IMAP à scanner. Exple : InBox.",
@@ -578,4 +579,4 @@
"templates.subject": "Objet",
"users.login": "Connecter",
"users.logout": "Déconnecter"
-}
+}
\ No newline at end of file
diff --git a/i18n/hu.json b/i18n/hu.json
index 0ce03b37..ea27b068 100644
--- a/i18n/hu.json
+++ b/i18n/hu.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "SES",
"settings.bounces.enableSendgrid": "SendGrid",
"settings.bounces.enableWebhooks": "Visszapattanó webhook",
+ "settings.bounces.enablePostmark": "Postmark",
"settings.bounces.enabled": "Engedélyezve",
"settings.bounces.folder": "Mappa",
"settings.bounces.folderHelp": "A vizsgálandó IMAP mappa neve. Például: 'Inbox'",
@@ -577,4 +578,4 @@
"templates.subject": "Tárgy",
"users.login": "Belépés",
"users.logout": "Kijelentkezés"
-}
+}
\ No newline at end of file
diff --git a/i18n/it.json b/i18n/it.json
index 42f90d02..0997e6cf 100644
--- a/i18n/it.json
+++ b/i18n/it.json
@@ -366,6 +366,7 @@
"settings.bounces.enableSES": "Attiva SES",
"settings.bounces.enableSendgrid": "Attiva SendGrid",
"settings.bounces.enableWebhooks": "Attiva bounce webhooks",
+ "settings.bounces.enablePostmark": "Attiva Postmark",
"settings.bounces.enabled": "Attivato",
"settings.bounces.folder": "Cartella",
"settings.bounces.folderHelp": "Nome della cartella IMAP da analizzare. Ad esempio: Posta in arrivo.",
@@ -578,4 +579,4 @@
"templates.subject": "Oggeto",
"users.login": "Accesso",
"users.logout": "Esci"
-}
+}
\ No newline at end of file
diff --git a/i18n/jp.json b/i18n/jp.json
index 8765350a..ddba9539 100644
--- a/i18n/jp.json
+++ b/i18n/jp.json
@@ -366,6 +366,7 @@
"settings.bounces.enableSES": "SESを有効にする",
"settings.bounces.enableSendgrid": "SendGridを有効にする",
"settings.bounces.enableWebhooks": "バウンスウェブフックを有効にする",
+ "settings.bounces.enablePostmark": "Postmarkを有効にする",
"settings.bounces.enabled": "有効",
"settings.bounces.folder": "フォルダ",
"settings.bounces.folderHelp": "スキャンするIMAPフォルダの名前。 例: Inbox.",
@@ -578,4 +579,4 @@
"templates.subject": "件名",
"users.login": "ログイン",
"users.logout": "ログアウト"
-}
+}
\ No newline at end of file
diff --git a/i18n/ml.json b/i18n/ml.json
index 39e24ff6..91b26554 100644
--- a/i18n/ml.json
+++ b/i18n/ml.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "SES പ്രവർത്തനക്ഷമമാക്കുക",
"settings.bounces.enableSendgrid": "SendGrid പ്രവർത്തനക്ഷമമാക്കുക",
"settings.bounces.enableWebhooks": "ബൗൺസ് വെബ്ഹുക്കുകൾ പ്രവർത്തനക്ഷമമാക്കുക",
+ "settings.bounces.enablePostmark": "Postmark പ്രവർത്തനക്ഷമമാക്കുക",
"settings.bounces.enabled": "പ്രവർത്തനക്ഷമമാക്കി",
"settings.bounces.folder": "ഫോൾഡർ",
"settings.bounces.folderHelp": "സ്കാൻ ചെയ്യാനുള്ള IMAP ഫോൾഡറിന്റെ പേര്. ഉദാ: ഇൻബോക്സ്.",
@@ -577,4 +578,4 @@
"templates.subject": "വിഷയം",
"users.login": "പ്രവേശിക്കുക",
"users.logout": "പുറത്തുകടക്കുക"
-}
+}
\ No newline at end of file
diff --git a/i18n/nl.json b/i18n/nl.json
index 903959cb..188d08b5 100644
--- a/i18n/nl.json
+++ b/i18n/nl.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "SES inschakelen",
"settings.bounces.enableSendgrid": "SendGrid inschakelen",
"settings.bounces.enableWebhooks": "Bounce webhooks inschakelen",
+ "settings.bounces.enablePostmark": "Postmark inschakelen",
"settings.bounces.enabled": "Ingeschakeld",
"settings.bounces.folder": "Map",
"settings.bounces.folderHelp": "Naam van de IMAP map om te scannen. Bv.: Inbox.",
@@ -577,4 +578,4 @@
"templates.subject": "Onderwerp",
"users.login": "Inloggen",
"users.logout": "Uitloggen"
-}
+}
\ No newline at end of file
diff --git a/i18n/pl.json b/i18n/pl.json
index f78d4667..2ad5d91e 100644
--- a/i18n/pl.json
+++ b/i18n/pl.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "Włącz SES",
"settings.bounces.enableSendgrid": "Włącz SendGrid",
"settings.bounces.enableWebhooks": "Włącz webhooki odbić",
+ "settings.bounces.enablePostmark": "Włącz Postmark",
"settings.bounces.enabled": "Włączone",
"settings.bounces.folder": "Folder",
"settings.bounces.folderHelp": "Nazwa folderu IMAP do skanowania. Np: Inbox.",
@@ -577,4 +578,4 @@
"templates.subject": "Temat",
"users.login": "Zaloguj",
"users.logout": "Wyloguj"
-}
+}
\ No newline at end of file
diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json
index 566c536e..aea98a42 100644
--- a/i18n/pt-BR.json
+++ b/i18n/pt-BR.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "Ativar SES",
"settings.bounces.enableSendgrid": "Ativar SendGrid",
"settings.bounces.enableWebhooks": "Ativar webhooks bounce",
+ "settings.bounces.enablePostmark": "Ativar Postmark",
"settings.bounces.enabled": "Ativado",
"settings.bounces.folder": "Pasta",
"settings.bounces.folderHelp": "Noma da pasta IMAP para escanear. Ex: Inbox.",
@@ -577,4 +578,4 @@
"templates.subject": "Assunto",
"users.login": "Entrar",
"users.logout": "Sair"
-}
+}
\ No newline at end of file
diff --git a/i18n/pt.json b/i18n/pt.json
index 89985889..a519ffd8 100644
--- a/i18n/pt.json
+++ b/i18n/pt.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "Ligar SES",
"settings.bounces.enableSendgrid": "Ligar SendGrid",
"settings.bounces.enableWebhooks": "Ligar webhooks de bounces",
+ "settings.bounces.enablePostmark": "Ligar Postmark",
"settings.bounces.enabled": "Ligado",
"settings.bounces.folder": "Pasta",
"settings.bounces.folderHelp": "Nome da pasta IMAP para procurar. E.g.: Inbox.",
@@ -577,4 +578,4 @@
"templates.subject": "Assunto",
"users.login": "Entrar",
"users.logout": "Sair"
-}
+}
\ No newline at end of file
diff --git a/i18n/ro.json b/i18n/ro.json
index 5048e8df..9cec93c2 100644
--- a/i18n/ro.json
+++ b/i18n/ro.json
@@ -366,6 +366,7 @@
"settings.bounces.enableSES": "Activați SES",
"settings.bounces.enableSendgrid": "Activați SendGrid",
"settings.bounces.enableWebhooks": "Activați webhooks bounce",
+ "settings.bounces.enablePostmark": "Activați Postmark",
"settings.bounces.enabled": "Activat",
"settings.bounces.folder": "Director",
"settings.bounces.folderHelp": "Numele folderului IMAP pentru a scana. De exemplu: Inbox.",
diff --git a/i18n/ru.json b/i18n/ru.json
index 1a997626..3a91b25d 100644
--- a/i18n/ru.json
+++ b/i18n/ru.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "Включить SES",
"settings.bounces.enableSendgrid": "Включить SendGrid",
"settings.bounces.enableWebhooks": "Включить веб-крючки отскока",
+ "settings.bounces.enablePostmark": "Включить Postmark",
"settings.bounces.enabled": "Включено",
"settings.bounces.folder": "Папка",
"settings.bounces.folderHelp": "Имя папки IMAP для сканирования. Например: Входящие.",
@@ -577,4 +578,4 @@
"templates.subject": "Тема",
"users.login": "Вход в систему",
"users.logout": "Выход из системы"
-}
+}
\ No newline at end of file
diff --git a/i18n/se.json b/i18n/se.json
index ef9d07b0..54950cd2 100644
--- a/i18n/se.json
+++ b/i18n/se.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "Enable SES",
"settings.bounces.enableSendgrid": "Enable SendGrid",
"settings.bounces.enableWebhooks": "Enable bounce webhooks",
+ "settings.bounces.enablePostmark": "Enable Postmark",
"settings.bounces.enabled": "Enabled",
"settings.bounces.folder": "Folder",
"settings.bounces.folderHelp": "Name of the IMAP folder to scan. Eg: Inbox.",
@@ -577,4 +578,4 @@
"templates.subject": "Subject",
"users.login": "Login",
"users.logout": "Logout"
-}
+}
\ No newline at end of file
diff --git a/i18n/sk.json b/i18n/sk.json
index 316510ce..a4ddb768 100644
--- a/i18n/sk.json
+++ b/i18n/sk.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "Zapnúť SES",
"settings.bounces.enableSendgrid": "Zapnúť SendGrid",
"settings.bounces.enableWebhooks": "Zapnúť webhooky pre nedoručiteľné",
+ "settings.bounces.enablePostmark": "Zapnúť Postmark",
"settings.bounces.enabled": "Zapnuté",
"settings.bounces.folder": "Priečinok",
"settings.bounces.folderHelp": "Názov kontrolovaného priečinku IMAP. Napríklad INBOX.",
@@ -577,4 +578,4 @@
"templates.subject": "Predmet",
"users.login": "Prihlásiť",
"users.logout": "Odhlásiť"
-}
+}
\ No newline at end of file
diff --git a/i18n/tr.json b/i18n/tr.json
index 7cfdadae..53f9c06a 100644
--- a/i18n/tr.json
+++ b/i18n/tr.json
@@ -366,6 +366,7 @@
"settings.bounces.enableSES": "SES'i etkinleştirin",
"settings.bounces.enableSendgrid": "SendGrid'i etkinleştirin",
"settings.bounces.enableWebhooks": "Sıçrama web kancalarını etkinleştirin",
+ "settings.bounces.enablePostmark": "Postmark'i etkinleştirin",
"settings.bounces.enabled": "Etkinleştir",
"settings.bounces.folder": "Dizin",
"settings.bounces.folderHelp": "Taranacak IMAP klasörünün adı. Örn: Gelen Kutusu.",
@@ -578,4 +579,4 @@
"templates.subject": "Konu",
"users.login": "Giriş",
"users.logout": "Çıkış"
-}
+}
\ No newline at end of file
diff --git a/i18n/vi.json b/i18n/vi.json
index 0117947d..81178468 100644
--- a/i18n/vi.json
+++ b/i18n/vi.json
@@ -366,6 +366,7 @@
"settings.bounces.enableSES": "Bật SES",
"settings.bounces.enableSendgrid": "Bật SendGrid",
"settings.bounces.enableWebhooks": "Bật webhook bị trả lại",
+ "settings.bounces.enablePostmark": "Bật Postmark",
"settings.bounces.enabled": "Đã bật",
"settings.bounces.folder": "Thư mục",
"settings.bounces.folderHelp": "Tên của thư mục IMAP để quét. Vd: Hộp thư đến.",
@@ -578,4 +579,4 @@
"templates.subject": "Chủ đề",
"users.login": "Đăng nhập",
"users.logout": "Đăng xuất"
-}
+}
\ No newline at end of file
diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json
index d4979434..9ebac187 100644
--- a/i18n/zh-CN.json
+++ b/i18n/zh-CN.json
@@ -365,6 +365,7 @@
"settings.bounces.enableSES": "启用SES",
"settings.bounces.enableSendgrid": "启用SendGrid",
"settings.bounces.enableWebhooks": "启用反弹webhooks",
+ "settings.bounces.enablePostmark": "启用Postmark",
"settings.bounces.enabled": "已启用",
"settings.bounces.folder": "文件夹",
"settings.bounces.folderHelp": "要扫描的 IMAP 文件夹的名称。例如:收件箱。",
@@ -577,4 +578,4 @@
"templates.subject": "主题",
"users.login": "登录",
"users.logout": "登出"
-}
+}
\ No newline at end of file
diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json
index f5911ecd..9aca067f 100644
--- a/i18n/zh-TW.json
+++ b/i18n/zh-TW.json
@@ -366,6 +366,7 @@
"settings.bounces.enableSES": "啟用SES",
"settings.bounces.enableSendgrid": "啟用SendGrid",
"settings.bounces.enableWebhooks": "啟用反彈webhooks",
+ "settings.bounces.enablePostmark": "啟用Postmark",
"settings.bounces.enabled": "已啟用",
"settings.bounces.folder": "文件夾",
"settings.bounces.folderHelp": "要掃描的IMAP 文件夾的名稱。例如:收件箱。",
@@ -578,4 +579,4 @@
"templates.subject": "主題",
"users.login": "登錄",
"users.logout": "登出"
-}
+}
\ No newline at end of file
diff --git a/internal/bounce/bounce.go b/internal/bounce/bounce.go
index a646730e..857a50a7 100644
--- a/internal/bounce/bounce.go
+++ b/internal/bounce/bounce.go
@@ -33,6 +33,11 @@ type Opt struct {
SESEnabled bool `json:"ses_enabled"`
SendgridEnabled bool `json:"sendgrid_enabled"`
SendgridKey string `json:"sendgrid_key"`
+ Postmark struct {
+ Enabled bool
+ Username string
+ Password string
+ }
RecordBounceCB func(models.Bounce) error
}
@@ -43,6 +48,7 @@ type Manager struct {
mailbox Mailbox
SES *webhooks.SES
Sendgrid *webhooks.Sendgrid
+ Postmark *webhooks.Postmark
queries *Queries
opt Opt
log *log.Logger
@@ -77,6 +83,7 @@ func New(opt Opt, q *Queries, lo *log.Logger) (*Manager, error) {
if opt.SESEnabled {
m.SES = webhooks.NewSES()
}
+
if opt.SendgridEnabled {
sg, err := webhooks.NewSendgrid(opt.SendgridKey)
if err != nil {
@@ -85,6 +92,10 @@ func New(opt Opt, q *Queries, lo *log.Logger) (*Manager, error) {
m.Sendgrid = sg
}
}
+
+ if opt.Postmark.Enabled {
+ m.Postmark = webhooks.NewPostmark(opt.Postmark.Username, opt.Postmark.Password)
+ }
}
return m, nil
diff --git a/internal/bounce/webhooks/postmark.go b/internal/bounce/webhooks/postmark.go
new file mode 100644
index 00000000..3bed6961
--- /dev/null
+++ b/internal/bounce/webhooks/postmark.go
@@ -0,0 +1,118 @@
+package webhooks
+
+import (
+ "crypto/subtle"
+ "encoding/json"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/knadh/listmonk/models"
+ "github.com/labstack/echo/v4"
+ "github.com/labstack/echo/v4/middleware"
+)
+
+type postmarkNotif struct {
+ RecordType string `json:"RecordType"`
+ MessageStream string `json:"MessageStream"`
+ ID int `json:"ID"`
+ Type string `json:"Type"`
+ TypeCode int `json:"TypeCode"`
+ Name string `json:"Name"`
+ Tag string `json:"Tag"`
+ MessageID string `json:"MessageID"`
+ Metadata map[string]string `json:"Metadata"`
+ ServerID int `json:"ServerID"`
+ Description string `json:"Description"`
+ Details string `json:"Details"`
+ Email string `json:"Email"`
+ From string `json:"From"`
+ BouncedAt time.Time `json:"BouncedAt"` // "2019-11-05T16:33:54.9070259Z"
+ DumpAvailable bool `json:"DumpAvailable"`
+ Inactive bool `json:"Inactive"`
+ CanActivate bool `json:"CanActivate"`
+ Subject string `json:"Subject"`
+ Content string `json:"Content"`
+}
+
+// Postmark handles webhook notifications (mainly bounce notifications).
+type Postmark struct {
+ authHandler echo.HandlerFunc
+}
+
+func NewPostmark(username, password string) *Postmark {
+ return &Postmark{
+ authHandler: middleware.BasicAuth(makePostmarkAuthHandler(username, password))(func(c echo.Context) error {
+ return nil
+ }),
+ }
+}
+
+// ProcessBounce processes Postmark bounce notifications and returns one object.
+func (p *Postmark) ProcessBounce(b []byte, c echo.Context) ([]models.Bounce, error) {
+ // Do basicauth.
+ if err := p.authHandler(c); err != nil {
+ return nil, err
+ }
+
+ var n postmarkNotif
+ if err := json.Unmarshal(b, &n); err != nil {
+ return nil, fmt.Errorf("error unmarshalling postmark notification: %v", err)
+ }
+
+ // Ignore non-bounce messages.
+ if n.RecordType != "Bounce" {
+ return nil, nil
+ }
+
+ supportedBounceType := true
+ typ := models.BounceTypeHard
+ switch n.Type {
+ case "HardBounce", "BadEmailAddress", "ManuallyDeactivated":
+ typ = models.BounceTypeHard
+ case "SoftBounce", "Transient", "DnsError", "SpamNotification", "VirusNotification", "DMARCPolicy":
+ typ = models.BounceTypeSoft
+ case "SpamComplaint":
+ typ = models.BounceTypeComplaint
+ default:
+ supportedBounceType = false
+ }
+
+ if !supportedBounceType {
+ return nil, fmt.Errorf("unsupported bounce type: %v", n.Type)
+ }
+
+ // Look for the campaign ID in headers.
+ campUUID := ""
+ if v, ok := n.Metadata["X-Listmonk-Campaign"]; ok {
+ campUUID = v
+ }
+
+ return []models.Bounce{{
+ Email: strings.ToLower(n.Email),
+ CampaignUUID: campUUID,
+ Type: typ,
+ Source: "postmark",
+ Meta: json.RawMessage(b),
+ CreatedAt: n.BouncedAt,
+ }}, nil
+}
+
+func makePostmarkAuthHandler(cfgUser, cfgPassword string) func(username, password string, c echo.Context) (bool, error) {
+ var (
+ u = []byte(cfgUser)
+ p = []byte(cfgPassword)
+ )
+
+ return func(username, password string, c echo.Context) (bool, error) {
+ if len(u) == 0 || len(p) == 0 {
+ return true, nil
+ }
+
+ if subtle.ConstantTimeCompare([]byte(username), u) == 1 && subtle.ConstantTimeCompare([]byte(password), p) == 1 {
+ return true, nil
+ }
+
+ return false, nil
+ }
+}
diff --git a/internal/migrations/v2.6.0.go b/internal/migrations/v2.6.0.go
new file mode 100644
index 00000000..f7a11f08
--- /dev/null
+++ b/internal/migrations/v2.6.0.go
@@ -0,0 +1,17 @@
+package migrations
+
+import (
+ "github.com/jmoiron/sqlx"
+ "github.com/knadh/koanf/v2"
+ "github.com/knadh/stuffbin"
+)
+
+// V2_6_0 performs the DB migrations.
+func V2_6_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error {
+ // Insert new preference settings.
+ if _, err := db.Exec(`INSERT INTO settings (key, value) VALUES ('bounce.postmark', '{"enabled": false, "username": "", "password": ""}') ON CONFLICT DO NOTHING;`); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/models/settings.go b/models/settings.go
index 7638844d..2350f82f 100644
--- a/models/settings.go
+++ b/models/settings.go
@@ -92,7 +92,12 @@ type Settings struct {
SESEnabled bool `json:"bounce.ses_enabled"`
SendgridEnabled bool `json:"bounce.sendgrid_enabled"`
SendgridKey string `json:"bounce.sendgrid_key"`
- BounceBoxes []struct {
+ BouncePostmark struct {
+ Enabled bool `json:"enabled"`
+ Username string `json:"username"`
+ Password string `json:"password"`
+ } `json:"bounce.postmark"`
+ BounceBoxes []struct {
UUID string `json:"uuid"`
Enabled bool `json:"enabled"`
Type string `json:"type"`
diff --git a/schema.sql b/schema.sql
index f8e5b451..3f89f6dc 100644
--- a/schema.sql
+++ b/schema.sql
@@ -255,6 +255,7 @@ INSERT INTO settings (key, value) VALUES
('bounce.ses_enabled', 'false'),
('bounce.sendgrid_enabled', 'false'),
('bounce.sendgrid_key', '""'),
+ ('bounce.postmark', '{"enabled": false, "username": "", "password": ""}'),
('bounce.mailboxes',
'[{"enabled":false, "type": "pop", "host":"pop.yoursite.com","port":995,"auth_protocol":"userpass","username":"username","password":"password","return_path": "bounce@listmonk.yoursite.com","scan_interval":"15m","tls_enabled":true,"tls_skip_verify":false}]'),
('appearance.admin.custom_css', '""'),