Add support for variable bounce processing actions.

- Add support for `complaint` to the SES bounce processor.
- Add support for `hard/soft` to Sendgrid bounce processor.
- Add new bounce actions `None` and `Unsubscribe`.
- Add per type (`soft/hard/complaint`) bounce rule configuration to
  admin settings UI.
- Refactor Cypress bounce tests.
This commit is contained in:
Kailash Nadh 2023-04-11 11:33:13 +05:30
parent 13ad9adb8b
commit 5fc28a733c
39 changed files with 326 additions and 109 deletions

View file

@ -132,7 +132,7 @@ func handleBounceWebhook(c echo.Context) error {
case service == "":
var b models.Bounce
if err := json.Unmarshal(rawReq, &b); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidData"))
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidData")+":"+err.Error())
}
if bv, err := validateBounceFields(b, app); err != nil {
@ -207,11 +207,11 @@ func handleBounceWebhook(c echo.Context) error {
func validateBounceFields(b models.Bounce, app *App) (models.Bounce, error) {
if b.Email == "" && b.SubscriberUUID == "" {
return b, echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData"))
return b, echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "email / subscriber_uuid"))
}
if b.SubscriberUUID != "" && !reUUID.MatchString(b.SubscriberUUID) {
return b, echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidUUID"))
return b, echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "subscriber_uuid"))
}
if b.Email != "" {
@ -222,8 +222,8 @@ func validateBounceFields(b models.Bounce, app *App) (models.Bounce, error) {
b.Email = em
}
if b.Type != models.BounceTypeHard && b.Type != models.BounceTypeSoft {
return b, echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData"))
if b.Type != models.BounceTypeHard && b.Type != models.BounceTypeSoft && b.Type != models.BounceTypeComplaint {
return b, echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "type"))
}
return b, nil

View file

@ -184,18 +184,21 @@ func main() {
// Load i18n language map.
app.i18n = initI18n(app.constants.Lang, fs)
app.core = core.New(&core.Opt{
cOpt := &core.Opt{
Constants: core.Constants{
SendOptinConfirmation: app.constants.SendOptinConfirmation,
MaxBounceCount: ko.MustInt("bounce.count"),
BounceAction: ko.MustString("bounce.action"),
},
Queries: queries,
DB: db,
I18n: app.i18n,
Log: lo,
}, &core.Hooks{
}
if err := ko.Unmarshal("bounce.actions", &cOpt.Constants.BounceActions); err != nil {
lo.Fatalf("error unmarshalling bounce config: %v", err)
}
app.core = core.New(cOpt, &core.Hooks{
SendOptinConfirmation: sendOptinConfirmationHook(app),
})

View file

@ -10,68 +10,52 @@ describe('Bounces', () => {
cy.get('.b-tabs nav a').eq(6).click();
cy.get('[data-cy=btn-enable-bounce] .switch').click();
cy.get('[data-cy=btn-enable-bounce-webhook] .switch').click();
cy.get('[data-cy=btn-bounce-count] .plus').click();
cy.get('[data-cy=btn-save]').click();
cy.wait(2000);
});
it('Post bounces', () => {
// Get campaign.
let camp = {};
cy.request(`${apiUrl}/api/campaigns`).then((resp) => {
camp = resp.body.data.results[0];
})
cy.then(() => {
console.log("campaign is ", camp.uuid);
})
}).then(() => {
console.log('campaign is ', camp.uuid);
});
// Get subscribers.
let subs = [];
cy.request(`${apiUrl}/api/subscribers`).then((resp) => {
subs = resp.body.data.results;
console.log(subs)
}).then(() => {
// Register soft bounces do nothing.
let sub = {};
cy.request('POST', `${apiUrl}/webhooks/bounce`, { source: 'api', type: 'soft', email: subs[0].email });
cy.request('POST', `${apiUrl}/webhooks/bounce`, { source: 'api', type: 'soft', email: subs[0].email });
cy.request(`${apiUrl}/api/subscribers/${subs[0].id}`).then((resp) => {
sub = resp.body.data;
}).then(() => {
cy.expect(sub.status).to.equal('enabled');
});
// Hard bounces blocklist.
cy.request('POST', `${apiUrl}/webhooks/bounce`, { source: 'api', type: 'hard', email: subs[0].email });
cy.request('POST', `${apiUrl}/webhooks/bounce`, { source: 'api', type: 'hard', email: subs[0].email });
cy.request(`${apiUrl}/api/subscribers/${subs[0].id}`).then((resp) => {
sub = resp.body.data;
}).then(() => {
cy.expect(sub.status).to.equal('blocklisted');
});
// Complaint bounces delete.
cy.request('POST', `${apiUrl}/webhooks/bounce`, { source: 'api', type: 'complaint', email: subs[1].email });
cy.request('POST', `${apiUrl}/webhooks/bounce`, { source: 'api', type: 'complaint', email: subs[1].email });
cy.request({ url: `${apiUrl}/api/subscribers/${subs[1].id}`, failOnStatusCode: false }).then((resp) => {
expect(resp.status).to.eq(400);
});
cy.loginAndVisit('/subscribers/bounces');
});
cy.then(() => {
console.log(`got ${subs.length} subscribers`);
// Post bounces. Blocklist the 1st sub.
cy.request('POST', `${apiUrl}/webhooks/bounce`, { source: "api", type: "hard", email: subs[0].email });
cy.request('POST', `${apiUrl}/webhooks/bounce`, { source: "api", type: "hard", campaign_uuid: camp.uuid, email: subs[0].email });
cy.request('POST', `${apiUrl}/webhooks/bounce`, { source: "api", type: "hard", campaign_uuid: camp.uuid, subscriber_uuid: subs[0].uuid });
for (let i = 0; i < 2; i++) {
cy.request('POST', `${apiUrl}/webhooks/bounce`, { source: "api", type: "soft", campaign_uuid: camp.uuid, subscriber_uuid: subs[1].uuid });
}
});
cy.wait(250);
});
it('Opens bounces page', () => {
cy.loginAndVisit('/subscribers/bounces');
cy.wait(250);
cy.get('tbody tr').its('length').should('eq', 5);
});
it('Delete bounce', () => {
cy.get('tbody tr:last-child [data-cy="btn-delete"]').click();
cy.get('.modal button.is-primary').click();
cy.wait(250);
cy.get('tbody tr').its('length').should('eq', 4);
});
it('Check subscriber statuses', () => {
cy.loginAndVisit(`/subscribers/${subs[0].id}`);
cy.wait(250);
cy.get('.modal-card-head .tag').should('have.class', 'blocklisted');
cy.get('.modal-card-foot button[type="button"]').click();
cy.loginAndVisit(`/subscribers/${subs[1].id}`);
cy.wait(250);
cy.get('.modal-card-head .tag').should('have.class', 'enabled');
});
});

View file

@ -51,6 +51,12 @@
</router-link>
</b-table-column>
<b-table-column v-slot="props" field="type" :label="$t('globals.fields.type')" sortable>
<router-link :to="{ name: 'bounces', query: { source: props.row.type } }">
{{ $t(`bounces.${props.row.type}`) }}
</router-link>
</b-table-column>
<b-table-column v-slot="props" field="created_at"
:label="$t('globals.fields.createdAt')" sortable>
{{ $utils.niceDate(props.row.createdAt, true) }}

View file

@ -20,7 +20,7 @@
</header>
<hr />
<section class="wrap">
<section class="wrap" v-if="form">
<b-tabs type="is-boxed" :animated="false" v-model="tab">
<b-tab-item :label="$t('settings.general.name')" label-position="on-border">
<general-settings :form="form" :key="key" />
@ -103,7 +103,7 @@ export default Vue.extend({
// formCopy is a stringified copy of the original settings against which
// form is compared to detect changes.
formCopy: '',
form: {},
form: null,
tab: 0,
};
},

View file

@ -1,26 +1,37 @@
<template>
<div>
<div class="columns mb-6">
<div class="column">
<div class="column is-3">
<b-field :label="$t('settings.bounces.enable')" data-cy="btn-enable-bounce">
<b-switch v-model="data['bounce.enabled']" name="bounce.enabled" />
</b-field>
</div>
<div class="column" :class="{'disabled': !data['bounce.enabled']}">
<b-field :label="$t('settings.bounces.count')" label-position="on-border"
:message="$t('settings.bounces.countHelp')" data-cy="btn-bounce-count">
<b-numberinput v-model="data['bounce.count']"
name="bounce.count" type="is-light"
controls-position="compact" placeholder="3" min="1" max="1000" />
</b-field>
</div>
<div class="column" :class="{'disabled': !data['bounce.enabled']}">
<b-field :label="$t('settings.bounces.action')" label-position="on-border">
<b-select name="bounce.action" v-model="data['bounce.action']">
<option value="blocklist">{{ $t('settings.bounces.blocklist') }}</option>
<option value="delete">{{ $t('settings.bounces.delete') }}</option>
</b-select>
</b-field>
<div class="column">
<div v-for="typ in bounceTypes" :key="typ" class="columns">
<div class="column is-2" :class="{'disabled': !data['bounce.enabled']}"
:label="$t('settings.bounces.count')" label-position="on-border">
{{ $t(`bounces.${typ}`) }}
</div>
<div class="column is-4" :class="{'disabled': !data['bounce.enabled']}">
<b-field :label="$t('settings.bounces.count')" label-position="on-border"
:message="$t('settings.bounces.countHelp')" data-cy="btn-bounce-count">
<b-numberinput v-model="data['bounce.actions'][typ]['count']"
name="bounce.count" type="is-light"
controls-position="compact" placeholder="3" min="1" max="1000" />
</b-field>
</div>
<div class="column is-4" :class="{'disabled': !data['bounce.enabled']}">
<b-field :label="$t('settings.bounces.action')" label-position="on-border">
<b-select name="bounce.action" v-model="data['bounce.actions'][typ]['action']"
expanded>
<option value="none">{{ $t('globals.terms.none') }}</option>
<option value="unsubscribe">{{ $t('email.unsub') }}</option>
<option value="blocklist">{{ $t('settings.bounces.blocklist') }}</option>
<option value="delete">{{ $t('globals.buttons.delete') }}</option>
</b-select>
</b-field>
</div>
</div>
</div>
</div><!-- columns -->
@ -182,6 +193,7 @@ export default Vue.extend({
data() {
return {
bounceTypes: ['soft', 'hard', 'complaint'],
data: this.form,
regDuration,
};

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Els recomptes no són únics, ja que el seguiment dels subscriptors individuals està desactivat.",
"analytics.title": "Indicadors",
"analytics.toDate": "Fins a",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Font",
"bounces.unknownService": "Servei desconegut",
"bounces.view": "Veure rebots",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Canals",
"globals.terms.minute": "Minut | Minuts",
"globals.terms.month": "Mes | Mesos",
"globals.terms.none": "None",
"globals.terms.second": "Segon | Segons",
"globals.terms.settings": "Configuració",
"globals.terms.subscriber": "Subscriptor | Subscriptors",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "Públic",
"settings.bounces.action": "Acció",
"settings.bounces.blocklist": "Llista de bloqueig",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Recompte de rebots",
"settings.bounces.countHelp": "Nombre de rebots per subscriptor",
"settings.bounces.delete": "Esborra",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "Activat",
"settings.bounces.folder": "Carpeta",
"settings.bounces.folderHelp": "Nom de la carpeta IMAP a escanejar. Ex: Safata d'entrada.",
"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",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Interval d'escaneig",
"settings.bounces.scanIntervalHelp": "Interval en què s'hauria d'escanejar la bústia de rebot (s per segon, m per minut).",
"settings.bounces.sendgridKey": "Clau SendGrid ",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Tipus",
"settings.bounces.username": "Usuari",
"settings.confirmRestart": "Assegura't que les campanyes en curs estiguin en pausa. Reinicia?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Protože je sledování odběratelů vypnuté, neexistuje počet na odběratele.",
"analytics.title": "Analytika",
"analytics.toDate": "Do",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Zdroj",
"bounces.unknownService": "Neznámá služba.",
"bounces.view": "Zobrazit převzetí",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Kurýři",
"globals.terms.minute": "Minuta | Minuty",
"globals.terms.month": "Měsíc | Měsíce",
"globals.terms.none": "None",
"globals.terms.second": "Vteřina | Vteřiny",
"globals.terms.settings": "Nastavení",
"globals.terms.subscriber": "Odběratel | Odběratelé",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "Veřejné",
"settings.bounces.action": "Akce",
"settings.bounces.blocklist": "Seznam blokovaných",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Počet případů nedoručitelnosti",
"settings.bounces.countHelp": "Počet případů nedoručitelnosti na odběratele",
"settings.bounces.delete": "Odstranit",
@ -360,11 +365,14 @@
"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.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",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Interval skenování",
"settings.bounces.scanIntervalHelp": "Interval, ve kterém by se poštovní schránka v případě nedoručitelnosti měla skenovat na nedoručitelnost (s - sekundy, m - minuty).",
"settings.bounces.sendgridKey": "Klíč SendGrid",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Typ",
"settings.bounces.username": "Jméno uživatele",
"settings.confirmRestart": "Ujistěte se, že jsou běžící kampaně pozastavené. Restartovat?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Nid yw'r niferoedd yn unigryw gan fod y system olrhain tanysgrifiwr unigol wedi'i diffodd",
"analytics.title": "Dadansoddeg",
"analytics.toDate": "At",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Ffynhonnell",
"bounces.unknownService": "Gwasanaeth anhysbys.",
"bounces.view": "Gweld beth sydd wedi sboncio",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Negeseuwyr",
"globals.terms.minute": "Munud | Munudau",
"globals.terms.month": "Mis | Misoedd",
"globals.terms.none": "None",
"globals.terms.second": "Eiliad | Eiliadau",
"globals.terms.settings": "Gosodiadau",
"globals.terms.subscriber": "Tanysgrifiwr | Tanysgrifwyr",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "Cyhoeddus",
"settings.bounces.action": "Gweithred",
"settings.bounces.blocklist": "Rhestr rwystro",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Nifer y pethau sydd wedi sboncio'n ôl",
"settings.bounces.countHelp": "Nifer y pethau sydd wedi sboncio'n ôl fesul tanysgrifiwr",
"settings.bounces.delete": "Dileu",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "Wedi galluogi",
"settings.bounces.folder": "Ffolder",
"settings.bounces.folderHelp": "Enw'r ffolder IMAP i'w sganio. ee: blwch derbyn.",
"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",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Cyfnod sganio",
"settings.bounces.scanIntervalHelp": "Y cyfnod ar gyfer sganio'r blwch post ar gyfer negeseuon sydd wedi sboncio'n ôl (e ar gyfer eiliad",
"settings.bounces.sendgridKey": "Allwedd SendGrid",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Math",
"settings.bounces.username": "Enw defnyddiwr",
"settings.confirmRestart": "Sicrhewch bod yr ymgyrchoedd byw wedi'u rhewi. Ailddechrau?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Statistiken sind anonym, da Einzelabonnenten Tracking abgeschaltet ist.",
"analytics.title": "Statistiken",
"analytics.toDate": "Bis",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Quelle",
"bounces.unknownService": "Unbekannter Dienst.",
"bounces.view": "Bounces anzeigen",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Messenger",
"globals.terms.minute": "Minute | Minuten",
"globals.terms.month": "Monat | Monate",
"globals.terms.none": "None",
"globals.terms.second": "Sekunde | Sekunden",
"globals.terms.settings": "Einstellungen",
"globals.terms.subscriber": "Abonnent | Abonnenten",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "Öffentlich",
"settings.bounces.action": "Aktion",
"settings.bounces.blocklist": "Sperrliste",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Bounce Anzahl",
"settings.bounces.countHelp": "Anzahl von Bounces pro Abonnent",
"settings.bounces.delete": "Löschen",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "Aktiviert",
"settings.bounces.folder": "Ordner",
"settings.bounces.folderHelp": "Name des zu scannenden IMAP-Ordners. z.B.: Inbox.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Der Bounce Scan-Interval sollte mindestens 1 Minute betragen.",
"settings.bounces.name": "Bounces",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Scan-Interval",
"settings.bounces.scanIntervalHelp": "Interval mit dem das Bounce-Postfach gescannt werden soll (s for Sekunden, m für Minuten).",
"settings.bounces.sendgridKey": "SendGrid Schlüssel",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Typ",
"settings.bounces.username": "Benutzername",
"settings.confirmRestart": "Stelle sicher, dass laufende Kampagnen pausiert sind. Neustarten?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "The counts are non-unique as individual subscriber tracking is turned off.",
"analytics.title": "Analytics",
"analytics.toDate": "To",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Source",
"bounces.unknownService": "Unknown service.",
"bounces.view": "View bounces",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Messengers",
"globals.terms.minute": "Minute | Minutes",
"globals.terms.month": "Month | Months",
"globals.terms.none": "None",
"globals.terms.second": "Second | Seconds",
"globals.terms.settings": "Settings",
"globals.terms.subscriber": "Subscriber | Subscribers",
@ -351,7 +355,6 @@
"settings.bounces.blocklist": "Blocklist",
"settings.bounces.count": "Bounce count",
"settings.bounces.countHelp": "Number of bounces per subscriber",
"settings.bounces.delete": "Delete",
"settings.bounces.enable": "Enable bounce processing",
"settings.bounces.enableMailbox": "Enable bounce mailbox",
"settings.bounces.enableSES": "Enable SES",
@ -362,6 +365,7 @@
"settings.bounces.folderHelp": "Name of the IMAP folder to scan. Eg: Inbox.",
"settings.bounces.invalidScanInterval": "Bounce scan interval should be minimum 1 minute.",
"settings.bounces.name": "Bounces",
"settings.bounces.none": "None",
"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",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Los totales no son por suscriptores únicos ya que el rastreo individual de suscriptores está desactivado.",
"analytics.title": "Analíticas",
"analytics.toDate": "Hasta",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Fuente",
"bounces.unknownService": "Servicio desconocido.",
"bounces.view": "Ver rebotes",
@ -209,6 +212,7 @@
"globals.terms.messengers": "Mensajeros",
"globals.terms.minute": "Minuto | Minutos",
"globals.terms.month": "Mes | Meses",
"globals.terms.none": "None",
"globals.terms.second": "Segundo | Segundos",
"globals.terms.settings": "Configuraciones",
"globals.terms.subscriber": "Suscriptor | Suscriptores",
@ -350,6 +354,7 @@
"settings.appearance.publicName": "Público",
"settings.bounces.action": "Acción",
"settings.bounces.blocklist": "Lista de bloqueo",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Conteo de rebotes",
"settings.bounces.countHelp": "Número de rebotes por suscripción",
"settings.bounces.delete": "Eliminar",
@ -361,11 +366,14 @@
"settings.bounces.enabled": "Activado",
"settings.bounces.folder": "Carpeta",
"settings.bounces.folderHelp": "Nombre de la carpeta IMAP a escanear, por ejemplo: Entrada.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "El intervalo mínimo de escanéo de los rebotes debería de ser 1 minuto.",
"settings.bounces.name": "Rebotes",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Intervalo de escaneo",
"settings.bounces.scanIntervalHelp": "Intervalo en el que el buzón de rebotes debería ser escaneado para encontrar nuevos rebotes (s para segundos, m para minutos).",
"settings.bounces.sendgridKey": "Clave para SendGrid",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Tipo",
"settings.bounces.username": "Nombre de usuario",
"settings.confirmRestart": "Asegúrese de que las campañas ejecutándose están pausadas. ¿Reiniciar?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Avausmäärät eivät ole yksilöllisiä, koska yksittäisten tilaajien seuranta on poistettu käytöstä.",
"analytics.title": "Analytiikka",
"analytics.toDate": "Asti",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Lähde",
"bounces.unknownService": "Tuntematon palvelu.",
"bounces.view": "Näytä epäonnistuneet toimitukset",
@ -209,6 +212,7 @@
"globals.terms.messengers": "Messengers",
"globals.terms.minute": "Minute | Minutes",
"globals.terms.month": "Month | Months",
"globals.terms.none": "None",
"globals.terms.second": "Second | Seconds",
"globals.terms.settings": "Settings",
"globals.terms.subscriber": "Subscriber | Subscribers",
@ -350,6 +354,7 @@
"settings.appearance.publicName": "Public",
"settings.bounces.action": "Action",
"settings.bounces.blocklist": "Blocklist",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Bounce count",
"settings.bounces.countHelp": "Number of bounces per subscriber",
"settings.bounces.delete": "Delete",
@ -361,11 +366,14 @@
"settings.bounces.enabled": "Enabled",
"settings.bounces.folder": "Folder",
"settings.bounces.folderHelp": "Name of the IMAP folder to scan. Eg: Inbox.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Bounce scan interval should be minimum 1 minute.",
"settings.bounces.name": "Bounces",
"settings.bounces.none": "None",
"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.soft": "Soft",
"settings.bounces.type": "Type",
"settings.bounces.username": "Username",
"settings.confirmRestart": "Ensure running campaigns are paused. Restart?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Les comptes ne sont pas uniques car le suivi individuel des abonnés est désactivé.",
"analytics.title": "Analyses",
"analytics.toDate": "Au",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Source",
"bounces.unknownService": "Service inconnu.",
"bounces.view": "Voir les rebonds",
@ -209,6 +212,7 @@
"globals.terms.messengers": "Services de messagerie",
"globals.terms.minute": "Minute | Minutes",
"globals.terms.month": "Mois | Mois",
"globals.terms.none": "None",
"globals.terms.second": "Seconde | Secondes",
"globals.terms.settings": "Paramètres",
"globals.terms.subscriber": "Abonné·e | Abonné·es",
@ -350,6 +354,7 @@
"settings.appearance.publicName": "Public",
"settings.bounces.action": "Action",
"settings.bounces.blocklist": "Liste de bloquage",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Comptage des rebonds",
"settings.bounces.countHelp": "Nombre de rebonds par abonné",
"settings.bounces.delete": "Effacer",
@ -361,11 +366,14 @@
"settings.bounces.enabled": "Activer",
"settings.bounces.folder": "Dossier",
"settings.bounces.folderHelp": "Nom du dossier IMAP à scanner. Exple : InBox.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "L'intervalle de 'scan' des rebonds doit être d'au moins 1 minute.",
"settings.bounces.name": "Rebonds",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Interval de 'scan'",
"settings.bounces.scanIntervalHelp": "Intervalle auquel la boîte aux lettres de rebond doit être analysée pour les rebonds (s pour seconde, m pour minute).",
"settings.bounces.sendgridKey": "Clés de SendGrid",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Type",
"settings.bounces.username": "Identifiant",
"settings.confirmRestart": "Assurez-vous que les campagnes actives soient en pause. Redémarrer ?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Darabszámok összesítve. A megtekintések és kattintások tagokhoz kötése ki van kapcsolva.",
"analytics.title": "Kimutatás",
"analytics.toDate": "Eddig",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Forrás",
"bounces.unknownService": "Ismeretlen szolgáltatás.",
"bounces.view": "Visszapattanások megtekintése",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Kézbesítők",
"globals.terms.minute": "Perc",
"globals.terms.month": "Hónap",
"globals.terms.none": "None",
"globals.terms.second": "Másodperc",
"globals.terms.settings": "Beállítások",
"globals.terms.subscriber": "Tag",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "Nyilvános",
"settings.bounces.action": "Művelet",
"settings.bounces.blocklist": "Tiltás",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Visszapattanások száma",
"settings.bounces.countHelp": "Visszapattanások száma tagokra lebontva",
"settings.bounces.delete": "Törlés",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "Engedélyezve",
"settings.bounces.folder": "Mappa",
"settings.bounces.folderHelp": "A vizsgálandó IMAP mappa neve. Például: 'Inbox'",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Az ellenőrzés gyakorisága 1 percnél nagyobb kell legyen.",
"settings.bounces.name": "Visszapattanók",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Ellenőrzés gyakorisága",
"settings.bounces.scanIntervalHelp": "A visszapattanó e-mailek ellenőrzésének gyakorisága. (s: másodperc, m: perc)",
"settings.bounces.sendgridKey": "Kulcs",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Típus",
"settings.bounces.username": "Név",
"settings.confirmRestart": "Újraindítás előtt győződjön meg róla, hogy a futó kampányok szünetelnek!",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "I totali non sono per ogni singola suscrizione perchè le opzioni d'inseguimento individuali non sono statte attivate.",
"analytics.title": "Analitiche",
"analytics.toDate": "a",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Sorgente",
"bounces.unknownService": "Servizio sconosciuto.",
"bounces.view": "Visualizza i rimbalzi",
@ -209,6 +212,7 @@
"globals.terms.messengers": "Strumento di messaggeria",
"globals.terms.minute": "Minuto | Minuti",
"globals.terms.month": "Mese | Mesi",
"globals.terms.none": "None",
"globals.terms.second": "Secondo | Secondi",
"globals.terms.settings": "Impostazioni",
"globals.terms.subscriber": "Iscritto | Iscritti",
@ -350,6 +354,7 @@
"settings.appearance.publicName": "Publico",
"settings.bounces.action": "Azzione",
"settings.bounces.blocklist": "Elenco bloccato",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Numero di rimblazi",
"settings.bounces.countHelp": "Numero di rimbalzi per iscritto",
"settings.bounces.delete": "Cancella",
@ -361,11 +366,14 @@
"settings.bounces.enabled": "Attivato",
"settings.bounces.folder": "Cartella",
"settings.bounces.folderHelp": "Nome della cartella IMAP da analizzare. Ad esempio: Posta in arrivo.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "L'intervallo di scansione dei rimbalzi deve essere di almeno 1 minuto.",
"settings.bounces.name": "Rimbalzi",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Intervallo di scansione",
"settings.bounces.scanIntervalHelp": "Intervallo con cui la mailbox di rimbalzo deve essere scansionata per i rimbalzi (s per secondo, m per minuto).",
"settings.bounces.sendgridKey": "Chiave SendGrid",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Tipo",
"settings.bounces.username": "Nome utente",
"settings.confirmRestart": "Asicurati che le campagne sono in pausa. Riavviare?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "個々の加入者の追跡がオフとなっているため、カウントは特有のものではありません。",
"analytics.title": "分析",
"analytics.toDate": "まで",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "ソース",
"bounces.unknownService": "不明のサービス。",
"bounces.view": "バウンスビュー",
@ -209,6 +212,7 @@
"globals.terms.messengers": "メッセンジャー",
"globals.terms.minute": "分 | 分",
"globals.terms.month": "月 | 月",
"globals.terms.none": "None",
"globals.terms.second": "秒 | 秒",
"globals.terms.settings": "設定",
"globals.terms.subscriber": "加入者 | 加入者",
@ -350,6 +354,7 @@
"settings.appearance.publicName": "公開",
"settings.bounces.action": "作用",
"settings.bounces.blocklist": "ブロックリスト",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "バウンス数",
"settings.bounces.countHelp": "加入者ごとのバウンス数",
"settings.bounces.delete": "削除",
@ -361,11 +366,14 @@
"settings.bounces.enabled": "有効",
"settings.bounces.folder": "フォルダ",
"settings.bounces.folderHelp": "スキャンするIMAPフォルダの名前。 例: Inbox.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "バウンススキャン間隔は最低1分。",
"settings.bounces.name": "バウンス",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "スキャン間隔",
"settings.bounces.scanIntervalHelp": "バウンスメールボックスのバウンスをスキャンする間隔 (秒はs,分はm).",
"settings.bounces.sendgridKey": "SendGridキー",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "タイプ",
"settings.bounces.username": "ユーザーネーム",
"settings.confirmRestart": "実行中のキャンペーンの停止を確認。再スタートしますか?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "വ്യക്തിഗത സബ്‌സ്‌ക്രൈബർ ട്രാക്കിംഗ് ഓഫാക്കിയതിനാൽ എണ്ണത്തിൽ വ്യത്യാസം കണ്ടേക്കാം.",
"analytics.title": "അനലിറ്റിക്സ്",
"analytics.toDate": "വരെ",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "ഉറവിടം",
"bounces.unknownService": "അറിയാത്ത സേവനം",
"bounces.view": "ബൗൺസായവ കാണുക",
@ -208,6 +211,7 @@
"globals.terms.messengers": "സന്ദേശ വാഹകർ",
"globals.terms.minute": "മിനുട്ട് | മിനുട്ടുകൾ",
"globals.terms.month": "മാസം | മാസങ്ങൾ",
"globals.terms.none": "None",
"globals.terms.second": "സെക്കന്റു് | സെക്കന്റുകൾ",
"globals.terms.settings": "ക്രമീകരണങ്ങൾ",
"globals.terms.subscriber": "വരിക്കാരൻ | വരിക്കാർ",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "പൊതു",
"settings.bounces.action": "നടപടി",
"settings.bounces.blocklist": "ബ്ലോക്ക് ലിസ്റ്റ്",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "ബൗൺസായവയുടെ എണ്ണം",
"settings.bounces.countHelp": "വരിക്കാർക്കു ആനുപാതികയി ബൗൺസുകളുടെ എണ്ണം",
"settings.bounces.delete": "നീക്കം ചെയ്യുക",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "പ്രവർത്തനക്ഷമമാക്കി",
"settings.bounces.folder": "ഫോൾഡർ",
"settings.bounces.folderHelp": "സ്കാൻ ചെയ്യാനുള്ള IMAP ഫോൾഡറിന്റെ പേര്. ഉദാ: ഇൻബോക്സ്.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "ബൗൺസ് സ്കാൻ ചെയ്യാനുള്ള ഏറ്റവും കുറഞ്ഞ ഇടവേള 1 മിനിറ്റായിരിക്കണം.",
"settings.bounces.name": "ബൗൺസുകൾ",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "സ്കാൻ ചെയ്യാനുള്ള ഇടവേള",
"settings.bounces.scanIntervalHelp": "ബൗൺസ് മെയിൽബോക്‌സ് സ്‌കാൻ ചെയ്യേണ്ട ഇടവേള (സെക്കൻഡിന് s, മിനിറ്റിന് m).",
"settings.bounces.sendgridKey": "SendGrid കീ",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "തരം",
"settings.bounces.username": "ഉപഭോക്തൃനാമം",
"settings.confirmRestart": "റണ്ണിംഗ് കാമ്പെയ്‌നുകൾ താൽക്കാലികമായി നിർത്തിയെന്ന് ഉറപ്പാക്കുക. പുനരാരംഭിക്കുട്ടേ?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "De tellingen zijn niet uniek omdat het volgen van individuele abonnees is uitgeschakeld.",
"analytics.title": "Analyse",
"analytics.toDate": "Tot",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Bron",
"bounces.unknownService": "Onbekende service.",
"bounces.view": "Zie bounces",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Messengers",
"globals.terms.minute": "Minuut | Minuten",
"globals.terms.month": "Maand | Maanden",
"globals.terms.none": "None",
"globals.terms.second": "Seconde | Seconden",
"globals.terms.settings": "Instellingen",
"globals.terms.subscriber": "Abonnee | Abonnees",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "Publiek",
"settings.bounces.action": "Actie",
"settings.bounces.blocklist": "Geblokkeerd",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Aantal bounces",
"settings.bounces.countHelp": "Aantal bounces per abonnee",
"settings.bounces.delete": "Verwijder",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "Ingeschakeld",
"settings.bounces.folder": "Map",
"settings.bounces.folderHelp": "Naam van de IMAP map om te scannen. Bv.: Inbox.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Bounce scan interval moet minstens 1 minuut zijn.",
"settings.bounces.name": "Bounces",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Scaninterval",
"settings.bounces.scanIntervalHelp": "Interval waarin de bounce mailbox gescanned moet worden voor bounces (s voor seconden, m voor minuten).",
"settings.bounces.sendgridKey": "SendGrid sleutel",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Type",
"settings.bounces.username": "Gebruikersnaam",
"settings.confirmRestart": "Zorg dat lopende campagnes gepauzeerd zijn. Herstarten?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Zliczenia nie są unikalne, ponieważ indywidualne śledzenie subskrybentów jest wyłączone.",
"analytics.title": "Analityka",
"analytics.toDate": "Do",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Źródła",
"bounces.unknownService": "Nieznane usługi.",
"bounces.view": "Zobacz odbicia",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Komunikatory",
"globals.terms.minute": "Minuta | Minut",
"globals.terms.month": "Miesiąc | Miesięcy",
"globals.terms.none": "None",
"globals.terms.second": "Sekunda | Sekundy",
"globals.terms.settings": "Ustawienia",
"globals.terms.subscriber": "Subskrypcja | Subskrypcje",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "Publiczne",
"settings.bounces.action": "Akcja",
"settings.bounces.blocklist": "Lista zablokowanych",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Liczba odbić",
"settings.bounces.countHelp": "Liczba odbić na subskrybenta",
"settings.bounces.delete": "Usuń",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "Włączone",
"settings.bounces.folder": "Folder",
"settings.bounces.folderHelp": "Nazwa folderu IMAP do skanowania. Np: Inbox.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Interwał czasu powinien być minimum 1 minuta.",
"settings.bounces.name": "Odbicia",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Interwał skanowania",
"settings.bounces.scanIntervalHelp": "Interwał czasu przeszukiwania skrzynki w poszkukiwaniu odbić (s dla sekund, m dla minut).",
"settings.bounces.sendgridKey": "Klucz SendGrid",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Typ",
"settings.bounces.username": "Nazwa użytkownika",
"settings.confirmRestart": "Upewnij się, że uruchomione kampanie są zapauzowane. Zrestartować?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "As contagens não são únicas pois o rastreamento de assinantes está desligado.",
"analytics.title": "Analytics",
"analytics.toDate": "Para",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Fonte",
"bounces.unknownService": "Serviço desconhecido.",
"bounces.view": "Ver bounces",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Mensageiros",
"globals.terms.minute": "Minuto | Minutos",
"globals.terms.month": "Mês | Meses",
"globals.terms.none": "None",
"globals.terms.second": "Segundo | Segundos",
"globals.terms.settings": "Configurações",
"globals.terms.subscriber": "Assinante | Assinantes",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "Publico",
"settings.bounces.action": "Ação",
"settings.bounces.blocklist": "Blocklist",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Contagem Bounce",
"settings.bounces.countHelp": "Número de bounces por assinante",
"settings.bounces.delete": "Deletar",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "Ativado",
"settings.bounces.folder": "Pasta",
"settings.bounces.folderHelp": "Noma da pasta IMAP para escanear. Ex: Inbox.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Intervalo de escaneamento de Bounce deve ser no mínimo 1 minuto.",
"settings.bounces.name": "Bounces",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Intervalo de Escaneamento",
"settings.bounces.scanIntervalHelp": "Intervalo no qual a caixa de emails de bounce deve ser escaneada por bounces (s para segundo, m para minuto).",
"settings.bounces.sendgridKey": "Key SendGrid",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Tipo",
"settings.bounces.username": "Nome de usuário",
"settings.confirmRestart": "Certifique-se de que as campanhas em execução estão pausadas. Reiniciar?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "As quantidades não são únicas dado que o rastreamento individual de cada subscritor está desligado.",
"analytics.title": "Analítica",
"analytics.toDate": "Até",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Fonte",
"bounces.unknownService": "Serviço desconhecido.",
"bounces.view": "Ver bounces",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Mensageiros",
"globals.terms.minute": "Minuto | Minutos",
"globals.terms.month": "Mês | Meses",
"globals.terms.none": "None",
"globals.terms.second": "Segundo | Segundos",
"globals.terms.settings": "Definições",
"globals.terms.subscriber": "Subscritor | Subcritores",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "Público",
"settings.bounces.action": "Ação",
"settings.bounces.blocklist": "Lista de Bloqueico",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Número de bounces",
"settings.bounces.countHelp": "Número de bounces por subscritor",
"settings.bounces.delete": "Eliminar",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "Ligado",
"settings.bounces.folder": "Pasta",
"settings.bounces.folderHelp": "Nome da pasta IMAP para procurar. E.g.: Inbox.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Intervalo de procura de bounces deve ser, no mínimo, 1 minuto.",
"settings.bounces.name": "Bounces",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Intervalo de procura",
"settings.bounces.scanIntervalHelp": "Intervalo de procura de bounces na caixa de correio de bounces (s para segundos, m para minutos).",
"settings.bounces.sendgridKey": "Chave do SendGrid",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Tipo",
"settings.bounces.username": "Nome de utilizador",
"settings.confirmRestart": "Tenha a certeza que as campanhas em curso estão em pausa. Reiniciar?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "The counts are non-unique as individual subscriber tracking is turned off.",
"analytics.title": "Analitiza",
"analytics.toDate": "La",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Sursa",
"bounces.unknownService": "Serviciu necunoscut.",
"bounces.view": "Vizualizeaz[ respingeri",
@ -209,6 +212,7 @@
"globals.terms.messengers": "Messengers",
"globals.terms.minute": "Minute | Minutes",
"globals.terms.month": "Month | Months",
"globals.terms.none": "None",
"globals.terms.second": "Second | Seconds",
"globals.terms.settings": "Setări",
"globals.terms.subscriber": "Abonat | Abonați",
@ -350,6 +354,7 @@
"settings.appearance.publicName": "Public",
"settings.bounces.action": "Acțiune",
"settings.bounces.blocklist": "Lista de blocare",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Numarul de respingeri",
"settings.bounces.countHelp": "Numarul de respingeri per abonat",
"settings.bounces.delete": "Șterge",
@ -361,11 +366,14 @@
"settings.bounces.enabled": "Activat",
"settings.bounces.folder": "Dosar",
"settings.bounces.folderHelp": "Numele folderului IMAP de scanat. De exemplu: Mesaje primite.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Intervalul de scanare al respingerilor treubie sa fie de minim 1 minut.",
"settings.bounces.name": "Respingeri",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Interval de scanare",
"settings.bounces.scanIntervalHelp": "Interval la care căsuța poștală de respingeri trebuie scanată pentru respingeri (s pentru secunde, m pentru minut).",
"settings.bounces.sendgridKey": "Cheie SendGrid ",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Tip",
"settings.bounces.username": "Utilizator",
"settings.confirmRestart": "Asigura-te ca difuzarea campaniilor este întreruptă. Repornești?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Счётчики не уникальны, т.к. индивидуальное отслеживание подписчиков выключено.",
"analytics.title": "Аналитика",
"analytics.toDate": "По",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Источник",
"bounces.unknownService": "Неизвестная услуга.",
"bounces.view": "Просмотр отскоков",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Мессенджеры",
"globals.terms.minute": "Минута | Минуты",
"globals.terms.month": "Месяц | Месяцы",
"globals.terms.none": "None",
"globals.terms.second": "Секунда | Секунды",
"globals.terms.settings": "Параметры",
"globals.terms.subscriber": "Подписчик | Подписчики",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "Общественность",
"settings.bounces.action": "Действие",
"settings.bounces.blocklist": "Блок-лист",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Количество отскоков",
"settings.bounces.countHelp": "Количество отказов на одного абонента",
"settings.bounces.delete": "Удалить",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "Включено",
"settings.bounces.folder": "Папка",
"settings.bounces.folderHelp": "Имя папки IMAP для сканирования. Например: Входящие.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Интервал сканирования скачков должен составлять минимум 1 минуту.",
"settings.bounces.name": "Отскоки",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Интервал сканирования",
"settings.bounces.scanIntervalHelp": "Интервал, с которым почтовый ящик должен сканироваться на наличие отказов (с - секунда, м - минута).",
"settings.bounces.sendgridKey": "Ключ SendGrid",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Тип",
"settings.bounces.username": "Имя пользователя",
"settings.confirmRestart": "Убедитесь, что запущенные кампании приостановлены. Запустить снова?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "The counts are non-unique as individual subscriber tracking is turned off.",
"analytics.title": "Analytics",
"analytics.toDate": "To",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Source",
"bounces.unknownService": "Unknown service.",
"bounces.view": "View bounces",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Messengers",
"globals.terms.minute": "Minute | Minutes",
"globals.terms.month": "Month | Months",
"globals.terms.none": "None",
"globals.terms.second": "Second | Seconds",
"globals.terms.settings": "Settings",
"globals.terms.subscriber": "Subscriber | Subscribers",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "Public",
"settings.bounces.action": "Action",
"settings.bounces.blocklist": "Blocklist",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Bounce count",
"settings.bounces.countHelp": "Number of bounces per subscriber",
"settings.bounces.delete": "Delete",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "Enabled",
"settings.bounces.folder": "Folder",
"settings.bounces.folderHelp": "Name of the IMAP folder to scan. Eg: Inbox.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Bounce scan interval should be minimum 1 minute.",
"settings.bounces.name": "Bounces",
"settings.bounces.none": "None",
"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.soft": "Soft",
"settings.bounces.type": "Type",
"settings.bounces.username": "Username",
"settings.confirmRestart": "Ensure running campaigns are paused. Restart?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Pretože je sledovanie odberateľov vypnuté, neexistuje počet na odberateľa.",
"analytics.title": "Analytika",
"analytics.toDate": "Do",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Zdroj",
"bounces.unknownService": "Neznáma služba.",
"bounces.view": "Zobraziť prevzetie",
@ -208,6 +211,7 @@
"globals.terms.messengers": "Doručovatelia",
"globals.terms.minute": "Minúta | Minúty",
"globals.terms.month": "Mesiac | Mesiace",
"globals.terms.none": "None",
"globals.terms.second": "Sekunda | Sekundy",
"globals.terms.settings": "Nastavenia",
"globals.terms.subscriber": "Odberateľ | Odberatelia",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "Verejné",
"settings.bounces.action": "Akcie",
"settings.bounces.blocklist": "Zoznam blokovaných",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Počet nedoručiteľných",
"settings.bounces.countHelp": "Počet nedoručiteľných na odberateľa",
"settings.bounces.delete": "Odstrániť",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "Zapnuté",
"settings.bounces.folder": "Priečinok",
"settings.bounces.folderHelp": "Názov kontrolovaného priečinku IMAP. Napríklad INBOX.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Interval kontroly nedoručiteľných by mal byť minimálne 1 minúta.",
"settings.bounces.name": "Nedoručiteľné",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Interval kontroly",
"settings.bounces.scanIntervalHelp": "Interval, v ktorom by se poštová schránka nedoručiteľných mala kontrolovať na nové správy (s - sekundy, m - minúty).",
"settings.bounces.sendgridKey": "Kľúč SendGrid",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Typ",
"settings.bounces.username": "Meno používateľa",
"settings.confirmRestart": "Uistite sa, že sú bežiace kampane pozastavené. Reštartovať?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Bireysel abone takibi kapalı olduğu için sayılar benzersiz değildir.",
"analytics.title": "Analitik",
"analytics.toDate": "To",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Kaynak",
"bounces.unknownService": "Bilinmeyen servis.",
"bounces.view": "Sıçramaları görüntüleyin",
@ -209,6 +212,7 @@
"globals.terms.messengers": "Kuryeler",
"globals.terms.minute": "Dakika | Dakikalar",
"globals.terms.month": "Ay | Aylar",
"globals.terms.none": "None",
"globals.terms.second": "Saniye | Saniyeler",
"globals.terms.settings": "Ayarlar",
"globals.terms.subscriber": "Üye | Üyeler",
@ -350,6 +354,7 @@
"settings.appearance.publicName": "Halka açık",
"settings.bounces.action": "Eylem",
"settings.bounces.blocklist": "Engelleme listesi",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Sıçrama sayısı",
"settings.bounces.countHelp": "Abone başına geri dönüş sayısı",
"settings.bounces.delete": "Sil",
@ -361,11 +366,14 @@
"settings.bounces.enabled": "Etkinleştir",
"settings.bounces.folder": "Dizin",
"settings.bounces.folderHelp": "Taranacak IMAP klasörünün adı. Örn: Gelen Kutusu.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Sıçrama tarama aralığı en az 1 dakika olmalıdır.",
"settings.bounces.name": "Sıçramalar",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Tarama aralığı",
"settings.bounces.scanIntervalHelp": "Sıçrama posta kutusunun sıçramalar için taranması gereken aralık (saniye için s, dakika için m).",
"settings.bounces.sendgridKey": "SendGrid Anahtarı",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Tip",
"settings.bounces.username": "Kullanıcı adı",
"settings.confirmRestart": "Çalışan kampanyaların duraklatıldığından emin ol. Yeniden başlat?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "Số lượng không phải là duy nhất vì theo dõi người đăng ký cá nhân bị tắt.",
"analytics.title": "Phân tích",
"analytics.toDate": "Đến",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "Nguồn",
"bounces.unknownService": "Dịch vụ không xác định.",
"bounces.view": "Xem thư bị trả lại",
@ -209,6 +212,7 @@
"globals.terms.messengers": "Tin nhắn",
"globals.terms.minute": "Minute | Minutes",
"globals.terms.month": "Month | Months",
"globals.terms.none": "None",
"globals.terms.second": "Second | Seconds",
"globals.terms.settings": "Cài đặt",
"globals.terms.subscriber": "Subscriber | Subscribers",
@ -350,6 +354,7 @@
"settings.appearance.publicName": "Công khai",
"settings.bounces.action": "Hành động",
"settings.bounces.blocklist": "Danh sách chặn",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "Số trang không truy cập",
"settings.bounces.countHelp": "Số trang không truy cập cho mỗi người đăng ký",
"settings.bounces.delete": "Xóa",
@ -361,11 +366,14 @@
"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.",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "Khoảng thời gian quét bị trả lại phải tối thiểu là 1 phút.",
"settings.bounces.name": "Bị trả lại",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "Khoảng thời gian quét",
"settings.bounces.scanIntervalHelp": "Khoảng thời gian mà hộp thư trả lại sẽ được quét để tìm thư trả lại (s cho giây, m cho phút).",
"settings.bounces.sendgridKey": "Khóa SendGrid",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "Loại",
"settings.bounces.username": "Tài khoản",
"settings.confirmRestart": "Đảm bảo các chiến dịch đang chạy bị tạm dừng. Khởi động lại?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "由于个人订户跟踪已关闭,因此计数不唯一。",
"analytics.title": "统计信息",
"analytics.toDate": "至",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "资源",
"bounces.unknownService": "未知的服务。",
"bounces.view": "查看退回邮",
@ -208,6 +211,7 @@
"globals.terms.messengers": "信使",
"globals.terms.minute": "分钟 | 几分钟",
"globals.terms.month": "月 | 几个月",
"globals.terms.none": "None",
"globals.terms.second": "秒 | 几秒",
"globals.terms.settings": "设置",
"globals.terms.subscriber": "订阅者 | 多个订阅者",
@ -349,6 +353,7 @@
"settings.appearance.publicName": "公开",
"settings.bounces.action": "行动",
"settings.bounces.blocklist": "黑名单",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "反弹计数",
"settings.bounces.countHelp": "每个订阅者的反弹次数",
"settings.bounces.delete": "删除",
@ -360,11 +365,14 @@
"settings.bounces.enabled": "已启用",
"settings.bounces.folder": "文件夹",
"settings.bounces.folderHelp": "要扫描的 IMAP 文件夹的名称。例如:收件箱。",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "反弹扫描间隔应至少为 1 分钟。",
"settings.bounces.name": "反弹",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "扫描间隔",
"settings.bounces.scanIntervalHelp": "应扫描退回邮箱以查找退回邮件的时间间隔s 表示秒m 表示分钟)。",
"settings.bounces.sendgridKey": "SendGrid键",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "类型",
"settings.bounces.username": "用户名",
"settings.confirmRestart": "确保暂停正在运行的广告系列。重新开始?",

View file

@ -10,6 +10,9 @@
"analytics.nonUnique": "由於個人訂戶跟踪已關閉,因此計數不唯一。",
"analytics.title": "統計信息",
"analytics.toDate": "至",
"bounces.complaint": "Complaint",
"bounces.hard": "Hard",
"bounces.soft": "Soft",
"bounces.source": "資源",
"bounces.unknownService": "未知的服務。",
"bounces.view": "查看退回郵",
@ -209,6 +212,7 @@
"globals.terms.messengers": "信使",
"globals.terms.minute": "分鐘| 幾分鐘",
"globals.terms.month": "月| 幾個月",
"globals.terms.none": "None",
"globals.terms.second": "秒| 幾秒",
"globals.terms.settings": "設置",
"globals.terms.subscriber": "訂閱者| 多個訂閱者",
@ -350,6 +354,7 @@
"settings.appearance.publicName": "公開",
"settings.bounces.action": "行動",
"settings.bounces.blocklist": "黑名單",
"settings.bounces.complaint": "Complaint",
"settings.bounces.count": "反彈計數",
"settings.bounces.countHelp": "每個訂閱者的反彈次數",
"settings.bounces.delete": "刪除",
@ -361,11 +366,14 @@
"settings.bounces.enabled": "已啟用",
"settings.bounces.folder": "文件夾",
"settings.bounces.folderHelp": "要掃描的IMAP 文件夾的名稱。例如:收件箱。",
"settings.bounces.hard": "Hard",
"settings.bounces.invalidScanInterval": "反彈掃描間隔應至少為1 分鐘。",
"settings.bounces.name": "反彈",
"settings.bounces.none": "None",
"settings.bounces.scanInterval": "掃描間隔",
"settings.bounces.scanIntervalHelp": "應掃描退回郵箱以查找退回郵件的時間間隔s 表示秒m 表示分鐘)。",
"settings.bounces.sendgridKey": "SendGrid鍵",
"settings.bounces.soft": "Soft",
"settings.bounces.type": "類型",
"settings.bounces.username": "用戶名",
"settings.confirmRestart": "確保暫停正在運行的廣告系列。重新開始?",

View file

@ -17,9 +17,10 @@ import (
)
type sendgridNotif struct {
Email string `json:"email"`
Timestamp int64 `json:"timestamp"`
Event string `json:"event"`
Email string `json:"email"`
Timestamp int64 `json:"timestamp"`
Event string `json:"event"`
BounceClassification string `json:"bounce_classification"`
// SendGrid flattens all X-headers and adds them to the bounce
// event notification.
@ -65,11 +66,16 @@ func (s *Sendgrid) ProcessBounce(sig, timestamp string, b []byte) ([]models.Boun
continue
}
typ := models.BounceTypeHard
if n.BounceClassification == "technical" || n.BounceClassification == "content" {
typ = models.BounceTypeSoft
}
tstamp := time.Unix(n.Timestamp, 0)
bn := models.Bounce{
CampaignUUID: n.CampaignUUID,
Email: strings.ToLower(n.Email),
Type: models.BounceTypeHard,
Type: typ,
Meta: json.RawMessage(b),
Source: "sendgrid",
CreatedAt: tstamp,

View file

@ -119,7 +119,8 @@ func (s *SES) ProcessBounce(b []byte) (models.Bounce, error) {
return bounce, fmt.Errorf("error unmarshalling SES notification: %v", err)
}
if (m.EventType != "" && m.EventType != "Bounce") || (m.NotifType != "" && m.NotifType != "Bounce") {
if (m.EventType != "" && m.EventType != "Bounce") ||
(m.NotifType != "" && (m.NotifType != "Bounce" && m.NotifType != "Complaint")) {
return bounce, errors.New("notification type is not bounce")
}
@ -127,9 +128,12 @@ func (s *SES) ProcessBounce(b []byte) (models.Bounce, error) {
return bounce, errors.New("no destination e-mails found in SES notification")
}
typ := "soft"
typ := models.BounceTypeSoft
if m.Bounce.BounceType == "Permanent" {
typ = "hard"
typ = models.BounceTypeHard
}
if m.NotifType == "Complaint" {
typ = models.BounceTypeComplaint
}
// Look for the campaign ID in headers.

View file

@ -59,6 +59,11 @@ func (c *Core) GetBounce(id int) (models.Bounce, error) {
// RecordBounce records a new bounce.
func (c *Core) RecordBounce(b models.Bounce) error {
action, ok := c.constants.BounceActions[b.Type]
if !ok {
return echo.NewHTTPError(http.StatusBadRequest, c.i18n.Ts("globals.messages.invalidData")+": "+b.Type)
}
_, err := c.q.RecordBounce.Exec(b.SubscriberUUID,
b.Email,
b.CampaignUUID,
@ -66,8 +71,8 @@ func (c *Core) RecordBounce(b models.Bounce) error {
b.Source,
b.Meta,
b.CreatedAt,
c.constants.MaxBounceCount,
c.constants.BounceAction)
action.Count,
action.Action)
if err != nil {
// Ignore the error if it complained of no subscriber.

View file

@ -36,8 +36,10 @@ type Core struct {
// Constants represents constant config.
type Constants struct {
SendOptinConfirmation bool
MaxBounceCount int
BounceAction string
BounceActions map[string]struct {
Count int
Action string
}
}
// Hooks contains external function hooks that are required by the core package.

View file

@ -11,11 +11,16 @@ func V2_5_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
('app.enable_public_archive_rss_content', 'false')
('app.enable_public_archive_rss_content', 'false'),
('bounce.actions', '{"soft": {"count": 2, "action": "none"}, "hard": {"count": 2, "action": "blocklist"}, "complaint" : {"count": 2, "action": "blocklist"}}')
ON CONFLICT DO NOTHING;
`); err != nil {
return err
}
if _, err := db.Exec(`DELETE FROM settings WHERE key IN ('bounce.count', 'bounce.action');`); err != nil {
return err
}
return nil
}

View file

@ -79,8 +79,9 @@ const (
EmailHeaderDeliveredTo = "Delivered-To"
EmailHeaderReceived = "Received"
BounceTypeHard = "hard"
BounceTypeSoft = "soft"
BounceTypeHard = "hard"
BounceTypeSoft = "soft"
BounceTypeComplaint = "complaint"
// Templates.
TemplateTypeCampaign = "campaign"

View file

@ -81,14 +81,16 @@ type Settings struct {
MaxMsgRetries int `json:"max_msg_retries"`
} `json:"messengers"`
BounceEnabled bool `json:"bounce.enabled"`
BounceEnableWebhooks bool `json:"bounce.webhooks_enabled"`
BounceCount int `json:"bounce.count"`
BounceAction string `json:"bounce.action"`
SESEnabled bool `json:"bounce.ses_enabled"`
SendgridEnabled bool `json:"bounce.sendgrid_enabled"`
SendgridKey string `json:"bounce.sendgrid_key"`
BounceBoxes []struct {
BounceEnabled bool `json:"bounce.enabled"`
BounceEnableWebhooks bool `json:"bounce.webhooks_enabled"`
BounceActions map[string]struct {
Count int `json:"count"`
Action string `json:"action"`
} `json:"bounce.actions"`
SESEnabled bool `json:"bounce.ses_enabled"`
SendgridEnabled bool `json:"bounce.sendgrid_enabled"`
SendgridKey string `json:"bounce.sendgrid_key"`
BounceBoxes []struct {
UUID string `json:"uuid"`
Enabled bool `json:"enabled"`
Type string `json:"type"`

View file

@ -979,15 +979,9 @@ WITH sub AS (
camp AS (
SELECT id FROM campaigns WHERE $3 != '' AND uuid = $3::UUID
),
bounce AS (
-- Record the bounce if the subscriber is not already blocklisted;
INSERT INTO bounces (subscriber_id, campaign_id, type, source, meta, created_at)
SELECT (SELECT id FROM sub), (SELECT id FROM camp), $4, $5, $6, $7
WHERE NOT EXISTS (SELECT 1 WHERE (SELECT status FROM sub) = 'blocklisted')
),
num AS (
-- Add a +1 to include the current insertion that is happening.
SELECT COUNT(*) + 1 AS num FROM bounces WHERE subscriber_id = (SELECT id FROM sub)
SELECT COUNT(*) + 1 AS num FROM bounces WHERE subscriber_id = (SELECT id FROM sub) AND type = $4
),
-- block1 and block2 will run when $8 = 'blocklist' and the number of bounces exceed $8.
block1 AS (
@ -996,7 +990,13 @@ block1 AS (
),
block2 AS (
UPDATE subscriber_lists SET status='unsubscribed'
WHERE $9 = 'blocklist' AND (SELECT num FROM num) >= $8 AND subscriber_id = (SELECT id FROM sub) AND (SELECT status FROM sub) != 'blocklisted'
WHERE $9 = 'unsubscribe' AND (SELECT num FROM num) >= $8 AND subscriber_id = (SELECT id FROM sub) AND (SELECT status FROM sub) != 'blocklisted'
),
bounce AS (
-- Record the bounce if the subscriber is not already blocklisted;
INSERT INTO bounces (subscriber_id, campaign_id, type, source, meta, created_at)
SELECT (SELECT id FROM sub), (SELECT id FROM camp), $4, $5, $6, $7
WHERE NOT EXISTS (SELECT 1 WHERE (SELECT status FROM sub) = 'blocklisted' OR (SELECT num FROM num) > $8)
)
-- This delete will only run when $9 = 'delete' and the number of bounces exceed $8.
DELETE FROM subscribers

View file

@ -231,8 +231,7 @@ INSERT INTO settings (key, value) VALUES
('messengers', '[]'),
('bounce.enabled', 'false'),
('bounce.webhooks_enabled', 'false'),
('bounce.count', '2'),
('bounce.action', '"blocklist"'),
('bounce.actions', '{"soft": {"count": 2, "action": "none"}, "hard": {"count": 2, "action": "blocklist"}, "complaint" : {"count": 2, "action": "delete"}}'),
('bounce.ses_enabled', 'false'),
('bounce.sendgrid_enabled', 'false'),
('bounce.sendgrid_key', '""'),