Commit Graph

306 Commits

Author SHA1 Message Date
KM Koushik 689cb8b366 feat: automate domain verification follow-ups (#375)
* feat: automate domain verification follow-ups

* fix: harden domain verification notifications

* fix: skip unchanged first-run domain status emails

* fix: make domain cleanup resilient and status labels readable

* fix: clarify domain verification notification messaging
2026-03-14 01:19:44 +11:00
KM Koushik 3c2d37906e feat: add multi-domain filters to webhooks (#361)
* feat(webhooks): add multi-domain endpoint filtering

* test(webhooks): add domain filter router coverage

* fix(webhooks): apply domain filters only to domain-scoped events

* stuff

* stuff
2026-03-08 09:01:39 +11:00
KM Koushik 33acd09d77 fix build 2026-03-08 00:15:34 +11:00
KM Koushik 62e0a1db88 feat: add contact-book variable registry for campaign personalization (#359)
* feat: add contact-book variable registry for campaign personalization

* test: include contact-book variables default in service expectation

* fix: address personalization review issues

* fix text

* fix: normalize contact variable access across contact flows

* stuff

* fix
2026-03-08 00:03:58 +11:00
Dave Stockley ce8b780155 feat: add dashboard analytics to sdk and public api (#353) 2026-03-04 22:06:21 +11:00
Dave Stockley 7428a1fbfa feat: add contactBooks to sdk, add delete campaign public endpoint (#352)
* feat: add contactBooks to sdk, add delete campaign public endpoint

* fix: pr review notes

* refactor: pr feedback

* feat: bulk delete/create contacts

* refactor: rename a few methods for consistency

* refactor: update openapi docs based on pr feedback

* refactor: update open api docs, based on pr feedback

* fix: delete campaign security issue

* refactor: delete campaign requires team id (from context)

* fix: enums
2026-03-04 07:10:43 +11:00
KM Koushik 73416dc481 fix(test): add redisKey to Redis mocks (#369)
* fix(test): mock redisKey in Redis test doubles

* fix(test): include BULL_PREFIX in redis mock
2026-03-02 08:43:29 +11:00
Michał Ordon 62d7c44efc feat: add REDIS_KEY_PREFIX env var for Redis ACL namespace isolation (#365)
* feat: add REDIS_KEY_PREFIX env var for Redis ACL namespace isolation

Adds optional REDIS_KEY_PREFIX env var that prefixes all Redis keys
(BullMQ queues via `prefix` option, cache/lock/rate-limit keys via
`redisKey()` helper). When unset, behavior is unchanged (BullMQ
defaults to "bull:", cache keys are unprefixed).

This enables self-hosters using Redis ACL multi-tenancy to restrict
useSend to its own key namespace (e.g. `~usesend:*`).

16 files changed across env schema, Redis module, 9 BullMQ queue/worker
files, and 5 direct Redis key operation sites.

* docs: add REDIS_KEY_PREFIX to self-host assets and fix docker run example

Add REDIS_KEY_PREFIX env var to docker/prod/compose.yml, .env.example,
.env.selfhost.example, and self-hosting docs. Fix missing trailing
backslashes in standalone docker run example.

* fix(redis): disable ioredis ready check and BullMQ version check

Redis ACL blocks INFO command (in @dangerous category). ioredis uses
INFO for ready check, BullMQ uses it for version detection. Without
these flags, BullMQ workers fail to initialize and silently stop
processing jobs.

- Add enableReadyCheck: false to ioredis connection
- Add skipVersionCheck: true to all 5 Queue + 5 Worker constructors

* fix(redis): add skipVersionCheck to remaining BullMQ job queues

Add skipVersionCheck: true to Queue and Worker constructors in all 4 job
files (campaign-scheduler, cleanup-email-bodies, usage-job,
webhook-cleanup) to match the pattern already used in service files.
This prevents BullMQ version mismatch errors when using REDIS_KEY_PREFIX
with Redis ACL namespace isolation.
2026-03-02 08:12:47 +11:00
KM Koushik 69eeb2d96e fix: reset contact list page when filters change (#368) 2026-03-01 20:38:42 +11:00
Anish de9bbcdc00 Fix typo in double-opt-in confirmation message (#367) 2026-03-01 10:45:48 +11:00
KM Koushik 9e588e2e6b feat: submit contact add form with Cmd/Ctrl+Enter (#366) 2026-03-01 00:58:02 +11:00
KM Koushik e3e9635a5f feat: add customizable contact double opt-in flow (#350)
* feat: add customizable contact double opt-in flow

* test: add double opt-in service coverage

* fix: address review comments for double opt-in PR

- Make pending status conditional on doubleOptInEnabled flag
- Backfill legacy unsubscribeReason for reliable pending detection
- Add doubleOptInContent to contact book listing select
- Fix duplicate toast on DOI editor subject save failure
- Harden searchParams parsing against string[] values
- Make default DOI template use link mark for clickable URL
- Make public API create+update atomic via transaction
- Prevent contact upsert failure when DOI email send fails
- Fix empty string template variable replacement

Co-authored-by: opencode <opencode@anthropic.com>

* fix: harden double opt-in confirmation safeguards

Preserve explicit unsubscribe intent in DOI flows and prevent confirmation links from re-subscribing opted-out contacts. Also sanitize subscribe-page error messaging and use timing-safe hash comparison for link verification.

* ui stuff

* fix: require doubleOptInUrl in double opt-in templates

* feat: add configurable from address for double opt-in emails

* feat: add resend confirmation flow for pending contacts

* fix: move subscribe confirmation to explicit POST flow

* test: add contact book public API endpoint coverage

* docs: add double opt-in documentation and update OpenAPI spec

Add a user guide for the double opt-in feature covering setup, contact
statuses, email customization, template variables, and best practices.
Update the OpenAPI spec to include doubleOptIn fields in all contactBook
request/response schemas.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: opencode <opencode@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 00:34:20 +11:00
KM Koushik edcd32a4ea fix: prevent premature webhook auto-disable and allow re-enable (#364)
* fix: prevent premature webhook auto-disable and allow re-enable

Use persisted failure counters when deciding auto-disable status and restore dashboard re-enable flow so webhooks are not deactivated unexpectedly after reset.

* fix: count webhook failures per failed call

Only increment consecutive failure counters after a call exhausts retries, while keeping the 30-call auto-disable threshold and stale-state protection.

* fix(docs): correct webhook SDK package name (#363)

* test: isolate webhook unit suite from mailer deps

Mock limit service in webhook unit tests so Vitest does not resolve team-service and mailer paths requiring usesend-js during CI.
2026-02-28 07:40:26 +11:00
Dan b2ed09e7a7 feat: add API key editing functionality to the dashboard (#358)
- new edit button in /dev-settings
- new updateApiKey mutation in api router
- new edit dialog-component
- new update-function in api-service
- changed sorting of api-key query to avoid list items jumping after updates
2026-02-25 23:11:11 +11:00
KM Koushik 0c9ebc86a3 fix: preserve reply-to metadata when duplicating campaigns (#357) 2026-02-23 12:06:33 +11:00
KM Koushik 61dfcee67d fix: enforce team scoping for campaign, contacts, and invites (#356)
* fix: enforce team-scoped lookups for campaign contacts and invites

* fix(test): mock domain service in campaign security test
2026-02-23 11:30:05 +11:00
Manoj Naik 585cd23ba2 fix: sync suppression list removal with AWS SES (closes #324) (#331)
* fix: sync suppression list removal with AWS SES (closes #324)

When removing an email from the suppression list, now also removes it from
AWS SES account-level suppression list across all regions where the team
has domains configured.

- Add deleteFromSesSuppressionList helper to ses.ts
- Update removeSuppression to query team domains for unique regions
- Use best-effort pattern: AWS failures don't block local DB deletion
- Handle NotFoundException gracefully (email not in SES list)

* fix: correct failure detection logic for SES suppression removal

deleteFromSesSuppressionList returns false on error (never throws),
so check for fulfilled promises with value === false instead of
rejected status.

* fix: account for rejected promises in SES suppression removal

Updated the filter logic for Promise.allSettled to include 'rejected'
status as well as 'fulfilled' with a 'false' value. This ensures that
any errors occurring before the try block in deleteFromSesSuppressionList
are correctly caught and logged.
2026-02-17 07:49:03 +11:00
KM Koushik 487902421b feat: add web testing foundation with infra-backed suites (#349)
* feat: add web test framework with infra-backed suites

* fix: honor DATABASE_URL env in integration prepare script

* fix: apply web test review feedback

* fix: streamline web test infra lifecycle and workflow scope
2026-02-16 09:13:29 +11:00
KM Koushik e246d32ef9 fix: prevent duplicate notification emails via atomic Redis SET NX (#346)
The warning and limit-reached notification emails were being sent
multiple times because of a race condition: concurrent workers could
both read the Redis cooldown key as empty (GET), both send emails,
then both set the key (SETEX). Replaced the non-atomic GET + SETEX
pattern with a single atomic SET ... NX EX that claims the cooldown
slot before any emails are sent. Also increased cooldown from 6 hours
to 24 hours so each notification is sent at most once per day.

https://claude.ai/code/session_01VBYXi5e64Vtq1cXWsfTYTw

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-01 07:24:02 +11:00
KM Koushik eed37d09c6 fix build 2026-01-18 21:44:25 +11:00
KM Koushik 8676965019 feat: add webhooks (#334) 2026-01-18 20:50:54 +11:00
KM Koushik f40a311cc9 fix: enforce contact book ownership (#341) 2026-01-17 18:08:05 +11:00
Dave Stockley 6786ff003e feat: contact books public api (#336)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-01-17 17:24:25 +11:00
Thiago Praxedes 83119f97c8 Refine suppression list to include only affected recipients (#339) 2026-01-17 17:00:43 +11:00
Dave Stockley 68d951c55a feat: v1/campaign public api endpoint (#335)
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-01-11 09:37:57 +11:00
KM Koushik 95dfa6b532 Upgrade Next.js to 15.5.9 (#326)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-18 06:48:24 +11:00
KM Koushik bef580ff92 use turbo in dev mode (#321) 2025-12-14 20:29:53 +11:00
KM Koushik 11b5ac1bf7 Fix bar chart hover on zero delivery days (#320)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 10:27:28 +11:00
KM Koushik 1e79f13bd4 add export contact book option (#318) 2025-12-14 10:08:54 +11:00
KM Koushik 461cd949e5 Fix Infinity% display bug in email chart (#319)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 09:35:05 +11:00
KM Koushik fd3c600b2e add upload contacts support (#314) 2025-12-13 08:15:42 +11:00
Vincent Vu 9c1c6f3538 fix(security): CVE-2025-55184, CVE-2025-55183 (#313) 2025-12-12 10:11:43 +11:00
KM Koushik 3e3f6d521a Display Docker image version in sidebar (#304)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-07 22:18:56 +11:00
KM Koushik 693ec9c0ce bump nextjx version (#301) 2025-12-07 07:41:15 +11:00
KM Koushik e1b64d0d7b Add feedback dialog for cloud dashboard (#293) 2025-11-29 10:22:12 +11:00
Deepak S 357d561a8e "[feat] Emails retention periods/cleanup #158" (#286) 2025-11-29 08:13:41 +11:00
KM Koushik cb489654b5 idempotency (#282) 2025-11-17 11:42:09 +11:00
KM Koushik f525381fb9 send free limit reached email for inactive plans 2025-10-26 11:05:51 +11:00
KM Koushik 7edff5b783 Add subscription ID search to admin team lookup (#284)
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-26 09:40:11 +11:00
KM Koushik 4fe354b85a Fix free limits and email notifications for inactive users (#283)
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-26 07:52:44 +11:00
KM Koushik 8e569f886e fix build 2025-10-25 05:43:00 +11:00
KM Koushik 1c9056ba75 Configure Email Usage Alert Logic (#278)
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-25 05:37:46 +11:00
KM Koushik 374f173a09 add delete resource modal (#280) 2025-10-25 05:37:16 +11:00
KM Koushik f1e63b6c46 add team ID column to email analytics page (#279)
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-25 04:49:45 +11:00
KM Koushik 77b0239b92 add contact to users on waitlist removal (#276) 2025-10-19 15:08:33 +11:00
KM Koushik 189b44bc1e fix campaign update not working 2025-10-19 07:14:03 +11:00
KM Koushik a5ca3b2f87 add campaign api (#274) 2025-10-18 10:31:43 +11:00
KM Koushik e631f16c85 feat: batch campaigns (#227) 2025-10-12 22:43:16 +11:00
KM Koushik 159b15e37e queue bulk contacts (#273) 2025-10-12 06:18:04 +11:00
KM Koushik 2fe2d5cdab update package version and response return tyupe for delete domain api (#272) 2025-10-11 06:37:24 +11:00