128 Commits

Author SHA1 Message Date
Kailash Nadh
570bb46d75 Add cron-based VACUUM ANALYZE support for DB maintenance.
- Add a new vacuum setting option on the UI in Admin -> Settings -> Maintenance.
- Also refactor frontend (lock-and-wait-for-restart) login on settings into
  the global vue instance so that it can be reused across contexts.
  Settings.vue and Maintenance.vue both now use it to wait for the backend
  to restart.
2025-11-30 11:54:15 +05:30
Kailash Nadh
67ad4d54ce Add external recipient support to /api/tx endpoint.
This patch introduces the long pending requirement of being able to send messages
to arbitrary recipients via the `/api/tx` endpoint without them having to be
subscribers in the database.

It maintains backwards compatibility and introduces just one new field,
`subscriber_mode` to the `/api/tx` endpoint.

- `default`  - Recipients must exist as subscribers in the database.
               Pass either `subscriber_emails` or `subscriber_ids`.                                                                                                                                                                     |

- `fallback` - Only accepts `subscriber_emails` and looks up subscribers in the
               database. If not found, sends the message to the e-mail anyway.
               In the template, apart from `{{ .Subscriber.Email }}`, other
               subscriber fields such as `.Name`. will be empty.
               Use `{{ Tx.Data.* }}` instead. |

- `external` - Sends to the given `subscriber_emails` without subscriber lookup
               in the database. In the template, apart from
               `{{ .Subscriber.Email }}`, other subscriber fields such as
               `.Name`. will be empty. Use `{{ Tx.Data.* }}` instead.                                                |
2025-11-28 16:37:20 +05:30
Kailash Nadh
583f92a6fc Add bulk deletion (by id or query) to lists and campaigns.
- Like subscribers, select one-or more or 'all' items and delete them
  on the lists and campaigns UIs.
- New `DELETE /api/lists` and `DELETE /api/campaigns` endpoints that
  take one or more `id` params or a single `query` param.
2025-11-28 16:37:20 +05:30
Kailash Nadh
2074604512 Add archival support to lists.
This patch adds a new `status` field (active, archived) to the lists table and
an 'Archived?' toggle on the UI that allows a list to be marked as archived.
This hides the lists from the lists page, campaigns list selection, list roles,
and public forms. A new "View archived lists" link on the lists UI allows
viewing the list of archived lists.

This is useful to hide/declutter lists by archiving historical, temporary lists
etc. This is largely a UX value addition.

Closes #2613.
2025-11-23 15:39:17 +05:30
Kailash Nadh
817048985c Split models file to domain specific files (#2775)
Co-authored-by: Mithilesh Gupta <guptamithilesh@protonmail.com>
2025-11-22 12:27:08 +05:30
Kailash Nadh
296245a1b3 Add 2FA TOTP support for authentication.
- Allow users to enable TOTP 2FA from the profile page by scanning a QR code.
- Create new `internal/tmptokens` in-memory token store for temp tokens for
  temporary login -> 2FA flow.
- Refactor reset methods to use this package instead of inline locked map.
2025-11-20 23:23:54 +05:30
Meysam
a2bfc0b77e feat: add subscriber activity tracking UI in admin panel (#2756)
* feat: add subscriber activity tracking UI in admin panel
* Apply minor cosmetic fixes to the subscriber activity forum.

- Remove dead icon references
- Remove new i18n language strings and reuse existing ones
- Refresh i18n languages with new strings
- Tweak styles

---------

Co-authored-by: Kailash Nadh <kailash@nadh.in>
2025-11-18 22:22:16 +05:30
Kailash Nadh
cdf0a5c153 Add CORS configuration to security settings.
This patch adds a new CORS config textbox in Settings -> Security that allows
configuring CORS origin domains per line.

Closes #2724
2025-10-25 14:33:25 +05:30
Kailash Nadh
301c13a60d Add optional subject param to tx API. Closes #2333. 2025-09-08 18:21:12 +05:30
Kailash Nadh
09d291e119 Add support for built-in ALTCHA CAPTCHA implementation.
The existing hCaptcha implementation as the only CAPTCHA option isn't ideal
as hCaptcha is a proprietary SaaS provider. This commit adds supports for
ALTCHA (altcha.org) a self-contained "proof-of-work" based CAPTCHA option.

Closes #2243.
2025-08-16 16:48:20 +05:30
Bowrna
ba24c64fae Add subsriber blocklisting on the bounces UI (#2409)
Co-authored-by: Kailash Nadh <kailash@nadh.in>
2025-08-01 23:21:25 +05:30
Vivek R
c9c678c04f Add support for OIDC user auto-creation (#2578)
This patch adds 3 new options to OIDC settings.

Toggle user auto-creation, and select default user/list roles
for auto-created users.

Co-authored-by: Kailash Nadh <kailash@nadh.in>
2025-07-20 16:11:45 +05:30
Kailash Nadh
dc466fc76b Fix quotes issue in TrackLink regexp. 2025-05-25 15:13:51 +05:30
Kailash Nadh
4da91a0e67 Fix broken TrackLink regexp. Closes #2448. 2025-05-24 22:47:49 +05:30
Kailash Nadh
12bc79866f Make OIDC provider name display on login button configurable. Closes #2211 2025-04-23 00:09:24 +05:30
Kailash Nadh
d3da0be629 Fix `test campaign' sending wrong template for visual campaigns. 2025-04-20 20:48:51 +05:30
Kailash Nadh
c1f81cfadd Fix compatibility issues with master.
- Fix merge conflicts.
- Simply logic in campaign preview handler.
- Remove redundant `GetCampaignForPreviewWithTemplate()` and switch to the old
  query that optionally takes a template.
- Fix Vue linting issues.
2025-04-20 20:48:51 +05:30
Vivek R
ae98280858 feat: Integrate email-builder on campaign/template editor UI and backend. 2025-04-20 20:48:51 +05:30
Fabian Schneebauer
a85e467cd9 Add missing charset for numbers in TrackLink regex. (#2404)
Co-authored-by: Fabian Schneebauer <fabian@fs-it.org>
2025-04-19 11:15:44 +05:30
Kailash Nadh
75aad8ec88 Add new search param to paginated API response structure. 2025-04-18 15:33:10 +05:30
Kailash Nadh
4b805f885b Fix broken subscribers:sql_query permission.
This permission was never checked for and had an unintended consequence of
allowing a non-superadmin user to execute arbitrary queries (expected), but
getting a superadmin session by joining the `sessions` table.

This patch:
- Introduces a table allowlist that uses the Postgres query plan (JSON)
  and validate the referenced tables against the allowed ones on arbitrary
  queries issued to the various `/subscribers` APIs.
- Explicitly adds the missing `subscribers:sql_query` permission check to all
  handlers that accept `query`.
- Introduces a new `search` parameter on all handlers that accept `query`.
  This parameter is an interface over the default name/email substring search
  instead of relying on `query`.
2025-04-18 14:15:47 +05:30
Kailash Nadh
e2f24a140e Turn notifs into a special stateful global singleton package, removing clunky deps. 2025-04-05 22:45:19 +05:30
Kailash Nadh
b3d46a8c85 Refactor system notification callbacks into a new notifs package. 2025-04-05 19:07:06 +05:30
Kailash Nadh
007f4de850 Fix a number of cosmetic inconsistenies across handlers and functions.
- Make the beginning of handlers consistent with uniform variable declaration
  and grouping.
- Add missing comments.
- Fix staticcheck/vet warnings and idiom issues.
2025-04-05 13:41:31 +05:30
Kailash Nadh
17998fbff5 Refactor user auth models and permission checks.
- Move user models from `/models` to `internal/auth`.
- Move and refactor various permission check functions into `User.()`
- Refactor awkward `get, manage bool` function args into `Get|Manage` bitflags.
2025-04-05 00:19:27 +05:30
Kailash Nadh
a271bf54d5 Introduce per-campaign filter permissions. Closes #2325.
This patch introduces new `campaigns:get_all` and `campaigns:manage_all`
permissions which alter the behaviour of the the old `campaigns:get` and
`campaigns:manage` permissions. This is a subtle breaking behavioural change.

Old:

- `campaigns:get` -> View all campaigns irrespective of a user's list
  permissions.
- `campaigns:manage` -> Manage all campaigns irrespective of a user's list
  permissions.

New:

- `campaigns:get_all` -> View all campaigns irrespective of a user's list
  permissions.
- `campaigns:manage_all` -> Manage all campaigns irrespective of a user's list
  permissions.
- `campaigns:get` -> View only the campaigns that have at least one list to
  which which a user has get or manage access.
- `campaigns:manage` -> Manage only the campaigns that have at list one list
  to which a user has get or manage access.

In addition, this patch refactors and cleans up certain permission related
logic and functions.
2025-03-31 16:39:42 +05:30
Kailash Nadh
b8f50eafa3 Add support for domain allowlists in addition to blocklists. Closes #2230.
This patch introduces a new `Domain allowlist` input in Settings -> Privacy UI
as a new tab alongside domain `Domain blocklist`. If any domains are entered
here, then only subscriptions/imports/additions of e-mails from those particular
domains are accepted. blocklist is mutually exclusive with allowlist when there
are values in the allowlist.
2025-03-29 23:31:34 +05:30
Fabian Schneebauer
7d3e6e615a Fix too greedy TrackLink regex replacement. (#2355)
Co-authored-by: Fabian Schneebauer <fabian@fs-it.org>
2025-03-25 15:39:35 +05:30
Kailash Nadh
a9869612dc Disallow private list UUIDs on public sub endpoints. Closes #2296. 2025-03-12 22:00:35 +05:30
lcd1232
d055cc5311 Add support for selecting SMTP per campaign (#2290)
This patch adds a new optional `name` field to SMTP server config on the UI.
When a name is given to an SMTP server, it's initialized as a standalone messenger
which shows up as a sub-group item under the main "email" messenger
on the campaign page.

Co-authored-by: Kailash Nadh <kailash@nadh.in>
2025-02-11 22:41:45 +05:30
Kailash Nadh
5c0de6ef0b Fix broken sorting lists by subscriber_count. Closes #2151. 2024-12-04 22:40:13 +05:30
Shaun Warman
cb8b54fd00 Add ForwardEmail (provider) bounce integration (#2016)
* Add ForwardEmail one-click SMTP form option.
* Add bounce webhook integration.

---------

Co-authored-by: Kailash Nadh <kailash@nadh.in>
2024-11-10 21:45:07 +05:30
Kailash Nadh
1e4b3a26f2 Separate get individual user and get all users queries. 2024-10-26 17:03:02 +05:30
Kailash Nadh
f226acaa23 Add missing auth permissions file. 2024-10-13 17:03:59 +05:30
Kailash Nadh
cea65c009d Fix and refactor subscriber batch fetching in campaign processing.
This has been a hair-pulling rabbit hole of an issue. #1931 and others.
When the `next-campaign-subscribers` query that fetches $n subscribers
per batch for a campaign returns no results, the manager assumes
that the campaign is done and marks as finished.

Marathon debugging revealed fundamental flaws in qyery's logic that
would incorrectly return 0 rows under certain conditions.
- Based on the "layout" of subscribers for eg: a series of blocklisted
  subscribers between confirmed subscribers.
  A series of unconfirmed subscribers in a batch belonging to a double
  opt-in list.
- Bulk import blocklisting users, but not marking their subscriptions
  as 'unsubscribed'.
- Conditions spread across multiple CTEs resulted in returning an
  arbitrary number of rows and $N per batch as the selected $N rows
  would get filtered out elsewhere, possibly even becoming 0.

After fixing this and testing it on our prod instance that has
15 million subscribers and ~70 million subscriptions in the
`subscriber_lists` table, ended up discovered significant inefficiences
in Postgres query planning. When `subscriber_lists` and campaign list IDs
are joined dynamically (CTE or ANY() or any kind of JOIN that involves)
a query, the Postgres query planner is unable to use the right indexes.

After testing dozens of approaches, discovered that statically passing
the values to join on (hardcoding or passing via parametrized $1 vars),
the query uses the right indexes. The difference is staggering.
For the particular scenario on our large prod DB to pull a batch,
~15 seconds vs. ~50ms, a whopping 300x improvement!

This patch splits `next-campaign-subscribers` into two separate queries,
one which fetches campaign metadata and list_ids, whose values are then
passed statically to the next query to fetch subscribers by batch.

In addition, it fixes and refactors broken filtering and counting logic
in `create-campaign` and `next-campaign` queries.

Closes #1931, #1993, #1986.
2024-10-13 17:03:59 +05:30
Kailash Nadh
a26834196e Refactor subscriber APIs list permission filtering. 2024-10-13 17:03:59 +05:30
Kailash Nadh
ae2a386193 Add support for "list roles".
This commit splits roles into two, user roles and list roles, both of which
are attached separately to a user.

List roles are collection of lists each with read|write permissions, while
user roles now have all permissions except for per-list ones.

This allows for easier management of roles, eliminating the need to clone and
create new roles just to adjust specific list permissions.
2024-10-13 17:03:58 +05:30
Kailash Nadh
12a6451ed0 Add list permission check to subscriber calls. 2024-10-13 17:03:55 +05:30
Kailash Nadh
d74e067961 Add per-list permission to list management.
- Filter lists by permitted list IDs in DB get calls.
- Split getLists() handlers into two (one, all) for clarity.
- Introduce new `subscribers:get_by_list` permission.
- Tweak UI rendering to work with new per-list permssions.
2024-10-13 16:59:52 +05:30
Kailash Nadh
1e875afa67 Add OIDC auth hooks (init, callback, session) and finish OIDC support. 2024-10-13 16:59:52 +05:30
Kailash Nadh
d52eac0948 Update user APIs and queries to embed role + list permissions. 2024-10-13 16:59:52 +05:30
Kailash Nadh
612c1d6eac Add per-list permission management to roles. 2024-10-13 16:59:52 +05:30
Kailash Nadh
dd9612b1ed Add user profile based permission check in auth middleware. 2024-10-13 16:59:52 +05:30
Kailash Nadh
32d5823dfe Refactor 'super' user type to a pre-defined super admin role. 2024-10-13 16:59:52 +05:30
Kailash Nadh
d4e4c5fa99 Add granular permissions and role management to backend and admin UI. 2024-10-13 16:59:51 +05:30
Kailash Nadh
313b2af6cf Make user avatar field nullable. 2024-10-13 16:59:51 +05:30
Kailash Nadh
4997c10b97 Add user profile APIs and update UI. 2024-10-13 16:59:51 +05:30
Kailash Nadh
57ac9dca4b Add public login page and auth middleware and handlers. 2024-10-13 16:59:51 +05:30
Kailash Nadh
1516bf216f Add api type user. 2024-10-13 16:59:51 +05:30
Kailash Nadh
0968e58766 Add user/password login handler. 2024-10-13 16:59:51 +05:30