Skip to content

Releases: NodeByteLTD/ByteSend

[0.2.6] - Limits, Logs and Insights

10 May 09:02
f4a1c3d

Choose a tag to compare

Added

Marketing Components

  • Modular homepage architecture — split the landing page into reusable section components (Hero, TrustStrip, Features, Comparison, PricingSection, CallToAction, DevSection) for clearer ownership and easier iteration
  • Single pricing calculator flow — promoted the calculator-led pricing experience and removed older card-based pricing composition in favor of a unified pricing section

Dashboard — Broadcasts

  • Broadcasts section — introduced a dedicated /broadcasts route and page hierarchy (/broadcasts, /broadcasts/[broadcastId], /broadcasts/[broadcastId]/edit) for one-off email blasts, separating them from the Campaigns flow which is now focused on recurring/automated sequences

Dashboard — Logs

  • Unified logs dashboard — added a first-class /logs dashboard page that merges email events, webhook calls, and notification delivery logs into one searchable audit trail with source and status filtering

Dashboard Settings

  • Settings API Keys page — added first-class /settings/api-keys route
  • Settings SMTP page — added first-class /settings/smtp route

Billing — Custom Plan Contracts

  • Slider-based plan selectorBillingPlanSelector component replaces static plan cards at /settings/billing with interactive marketing and transactional email limit sliders (1,000 – 3,000,000 each); calculated contract total updates in real time as limits are adjusted
  • Custom Stripe checkout sessionscreateCustomCheckoutSession tRPC mutation creates a Stripe session with price_data (unit_amount = exact contract price in CAD) rather than a fixed price ID, locking teams into the exact limit/price combination they selected
  • Custom plan querygetCustomPlanContract tRPC query lets the billing page and PlanDetails component surface the active custom contract (limits + monthly price) for teams already on a custom plan
  • Custom plan DB fields — added four optional fields to the Team model (customPlanEnabled, customMarketingEmailLimit, customTransactionalEmailLimit, customMonthlyPriceCents); populated at checkout completion via the Stripe webhook

Campaigns

  • Campaign intent field — campaigns now store an intent column (migration 20260510120000_add_campaign_intent) to distinguish campaign purpose at creation time; create-campaign dialog captures intent upfront

SDKs

  • Go SDK initial package — introduced packages/go-sdk with typed client surfaces for emails, contacts, contact books, campaigns, domains, and analytics

Documentation

  • SMTP auth API reference page — added apps/docs/api-reference/smtp/auth.mdx
  • Self-hosting Docker doc relocation — promoted Docker setup docs to apps/docs/self-hosting/docker.mdx and aligned navigation

CI / Review Automation

  • CodeRabbit support — added .github/workflows/coderabbit.yml and root .coderabbit.yaml to enable automated PR review summaries and inline feedback on pull requests

Changed

Marketing Site

  • Homepage composition refresh — replaced older section files (FeatureCard*, PricingTiers, CodeExample) with the new component set and updated page assembly
  • Icon system modernization — replaced hand-authored inline SVG usage in key marketing and app screens with icon components for consistency and maintainability
  • Developer section improvements — expanded dev-focused section behavior and language-toggle handling in CodeLangToggle and DevSection

Settings UX & Navigation

  • Developer settings consolidation — aligned developer tooling pages with the canonical Settings area while preserving compatibility flows from legacy dev-settings routes

Plans, Billing & Limits

  • Pricing/plan constant updates — refreshed shared Stripe plan/product definitions and app-side plan/payment constants to keep UI, checkout, and limits in sync
  • Billing page rebuilt around custom plan selector/settings/billing now surfaces the slider-based BillingPlanSelector as the primary upgrade path; PlanDetails shows active custom contract details (limits and monthly price) when a custom plan is active
  • Custom plan limit enforcementLimitService now reads customMarketingEmailLimit and customTransactionalEmailLimit from the Team record and enforces per-type monthly caps for custom plan teams, bypassing the standard plan-tier daily limits; the daily usage job skips metered overage billing for custom plan teams to avoid double-charging
  • Admin plan assignment clears custom contractadminAssignPlan with method: "complimentary" now explicitly clears all custom plan fields (customPlanEnabled, all limit columns) so stale slider contracts do not persist after a manual admin override
  • Free-plan marketing access switched to limit-based enforcement — Contacts, Broadcasts, and Campaigns are no longer hard-locked in the dashboard for Free teams; access is now controlled by enforceable plan limits (including campaign count and monthly sending caps)
  • Free plan marketing baseline updated — FREE plan now includes limited marketing capability (marketingEmailsIncluded: true) with a capped campaign allowance (campaignsLimit: 3) instead of full marketing exclusion
  • Stripe seed updates — adjusted stripe-seed.ts for current product/price setup behavior

Analytics

  • Analytics detail expansion — dashboard analytics now includes additional breakdown cards (delivery rate, bounce rate, complaint rate, and total volume) for clearer at-a-glance performance tracking
  • Paid-only advanced analytics insights — added a paid-tier insights section for open rate, click rate, click-to-open rate, and average daily volume with an upgrade CTA for free teams

Usage Breakdown

  • Full usage limit breakdown — the /settings/usage page now shows a complete limit audit for all tracked resources, including monthly and daily email usage, marketing vs transactional email usage, domains, contact books, contacts, campaigns, team members, and webhooks, so teams can see both current usage and allowed limits in one place

Documentation & API Spec

  • OpenAPI refresh — regenerated and updated API reference spec and intro content
  • Docs navigation/content refresh — updated docs navigation and onboarding pages (including Go and self-hosting paths) to match current product structure

Fixed

Dashboard & Routing

  • Settings route reliability — removed dead-end developer settings destinations by wiring pages into canonical Settings routes and maintaining redirect compatibility

Billing & Limits

  • Admin bypass email normalizationLimitService.isAdminOrFounderTeam now normalises the environment admin email with trim() + toLowerCase() and uses a case-insensitive Prisma query, preventing mismatches caused by casing differences in env config
  • Custom plan Stripe webhook syncsyncStripeData now reads customPlanEnabled, customMarketingEmailLimit, customTransactionalEmailLimit, and customMonthlyPriceCents from subscription metadata and persists them back to the Team record so contract limits survive server restarts and are always in sync with Stripe

Visual Consistency

  • Cross-page icon sizing/alignment — normalized icon rendering across footer, auth, error, not-found, and dashboard surfaces after component migration
  • UpgradeModal scroll — fixed scroll behavior in the upgrade and notification modals so long plan/feature lists are fully accessible without clipping

[0.2.5] - Smarter Billing, Better Alerts

09 May 19:52

Choose a tag to compare

Added

Authentication

  • GitHub OAuth support — added GitHub as a sign-in provider alongside Discord (GITHUB_ID / GITHUB_SECRET), enabling GitHub auth for cloud and self-hosted deployments

Notification Providers

  • Multi-provider notifications — teams can now configure external alerting providers for operational events (email, campaign, domain, and error notifications)
  • Supported providers — Discord, Slack, Microsoft Teams, Telegram, and Custom Webhook are now supported with provider-specific configuration
  • Notification provider schema — added NotificationProvider model to store provider type, config, status, and team linkage
  • Notification log schema — added NotificationLog model to store per-dispatch delivery outcomes and failure details
  • Notification provider API — added notificationProvider tRPC router with list, getById, create, update, delete, test, getLogs, and getStats
  • Notification dispatcher services — added provider dispatch and event emission services (notification-provider-service.ts and notification-emitter.ts)
  • Notifications settings page — added /settings/notifications UI for provider management, testing, logs, and usage guidance
  • Notification integration reference — added .references/notification-integration.md

Admin / Billing Operations

  • Admin plan assignment flow — added adminAssignPlan to let cloud admins assign plans to teams either as complimentary grants or Stripe checkout-link driven assignments
  • Admin team billing controls — admin team settings now supports dailyEmailLimit = -1 for unlimited daily sending

Billing / Plan Source-of-Truth

  • Perks derived from shared plan constantsapps/web/src/lib/constants/payments.ts now generates plan perks from @bytesend/lib PLANS rather than static duplicated data
  • Billing plan cards from shared plans/settings/billing plan options now derive from the shared PLANS map to avoid UI/config drift

CI / Automation

  • Issue summary workflow — added .github/workflows/issue-summary.yml to automatically summarize newly opened issues
  • Stale cleanup workflow — added .github/workflows/stale-cleanup.yml to clean up inactive issues and pull requests
  • CodeQL workflow — introduced .github/workflows/codeql.yml and enabled develop branch triggers
  • JavaScript SDK release workflow — added .github/workflows/npm-release.yml to build and publish the bytesend-js package from packages/sdk changes on main (plus manual dispatch)
  • Python SDK release workflow — added .github/workflows/pypi-release.yml to build and publish the bytesend-python package from packages/python-sdk on pushes to main and manual dispatch

Community / Governance

  • Repository security policy — added .github/SECURITY.md with supported versions, private reporting process, and response expectations
  • Code of Conduct — added .github/CODE_OF_CONDUCT.md (Contributor Covenant v2.1)
  • Contributing guide — added .github/CONTRIBUTING.md with development workflow, PR expectations, and testing checklist
  • Support guide — added .github/SUPPORT.md with support channels and security-report routing

GitHub Templates

  • PR template — added .github/PULL_REQUEST_TEMPLATE.md to standardize change summaries, testing notes, and release-impact checks
  • Issue template config — added .github/ISSUE_TEMPLATE/config.yml with contact links and blank-issue controls
  • New issue forms — added .github/ISSUE_TEMPLATE/feature.yml and .github/ISSUE_TEMPLATE/docs.yml

Changed

Plans & Pricing

  • BASIC plan updated — aligned plan limits/pricing model by setting BASIC to CA$20/mo with 100,000 monthly emails, 30 members, and 12 domains
  • LIFETIME plan updated — aligned lifetime limits to current plan progression at CA$199 one-time with 500,000 monthly emails, 100 members, and 30 domains

Settings UX

  • Settings navigation restructured — removed Team inner General/Members subtabs and promoted them to top-level Settings navigation
  • General tab behavior/settings now serves as the General overview (team profile and core team settings)
  • Members tab split-out — members management moved to dedicated /settings/members tab alongside billing/usage-related settings
  • Usage resource breakdowns — usage view now includes explicit domain, webhook, and member usage breakdowns with limit context

SMTP Server

  • SMTP server vendored into monorepoapps/smtp-server is now tracked directly in this repository (no gitlink/submodule-style entry), simplifying versioning and release consistency
  • Authentication compatibility fallback — SMTP auth now supports the API-driven custom team username flow while retaining a legacy fallback username candidate for older client configurations

Documentation

  • SMTP docs refreshed for monorepo paths — clone/build/deployment documentation now references NodeByteLTD/ByteSend and apps/smtp-server paths throughout
  • SMTP quickstart clarified — get-started docs now direct users to use their configured SMTP username (default bytesend) rather than implying only a fixed username
  • Core docs/readme refresh — updated main README and docs navigation/content pages for current monorepo structure and self-hosting guidance (apps/docs/README.md, apps/docs/docs.json, local/docker/self-hosting guide pages)
  • Feature docs expansion — added new guides for GitHub OAuth, API authentication, plans/pricing, plan management, admin operations, and notification providers (apps/docs/guides/*)
  • Mintlify branding refresh — updated apps/docs/docs.json theme colors/navigation and expanded apps/docs/introduction.mdx to surface new billing, alerting, and auth capabilities

References

  • Internal references expanded — added .references/README.md, smtp-auth-and-operations.md, release-playbook.md, and repository-governance.md
  • Webhook reference improvements — expanded .references/webhook-architecture.md with operations checklist, common failure modes, and change-safety notes

GitHub Templates

  • Issue form upgrades — revamped bug/marketing/SMTP templates with clearer triage metadata, reproducibility fields, and validation checkboxes

Workflows

  • PR labeling workflow rename — renamed the workflow file to label-prs.yml
  • Label action token update — updated token reference in .github/workflows/label.yml
  • Website test workflow tuning — adjusted website test workflow behavior
  • Docker publish workflow update — updated .github/workflows/docker-publish.yml
  • Docker manifest recreation safety — docker publish now removes existing manifests before create, preventing rerun failures on previously published tags
  • Docker remote tag cleanup — docker publish now removes pre-existing remote tags/manifests with docker buildx imagetools rm before publishing platform images/manifests, avoiding is a manifest list rerun failures
  • Website tests pnpm version alignment — removed hardcoded pnpm version from .github/workflows/website-test.yml so CI uses the repository packageManager version (pnpm@9.0.0)
  • Docker publish tag strategy hardening.github/workflows/docker-publish.yml now publishes ref-aware tags (latest, develop, version tag, and commit SHA) with matching multi-arch manifests
  • Manual Docker publish branch support — wired workflow_dispatch branch input into checkout and tag resolution so manual runs build/publish the selected branch
  • Labeler rules refresh — updated .github/labeler.yml to align automated PR labeling with the current repository structure
  • Actions runtime forward-compatibility — added FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true across workflows to avoid Node 20 JavaScript action deprecation breakage

Fixed

Suppressions

  • Suppression removal reliability — improved suppression deletion flow to handle non-canonical casing/inputs more robustly in dashboard and backend paths

Limits / Usage

  • Usage limit consistency — fixed plan usage limit handling to align dashboard/service behavior with shared plan constants

Marketing Site

  • Contact CTA destination — changed the marketing contact link from email to Discord

CI / Tests

  • Domain-service unit test import stabilityapps/web/src/server/service/domain-service.ts now initializes DNS resolvers with runtime-safe fallbacks (promises API or callback API), preventing ERR_INVALID_ARG_TYPE when DNS methods are partially mocked in tests
  • Usage unit test expectation alignmentapps/web/src/lib/usage.unit.test.ts now derives expected costs from exported usage constants instead of stale hardcoded values
  • Workspace SDK resolution in Vitestapps/web/vitest.config.ts now aliases bytesend-js to packages/sdk/index.ts during tests so unit suites do not depend on prebuilt SDK dist artifacts
  • Contact-service unit test isolationapps/web/src/server/service/contact-service.unit.test.ts now mocks LimitService.checkContactsLimit to avoid transitive TeamService cache dependencies and prevent brittle failures
  • Campaign security test alignmentapps/web/src/server/api/routers/campaign-security.trpc.test.ts updated for current plan access expectations

SMTP Server

  • SMTP Dockerfile context compatibilityapps/smtp-server/Dockerfile no longer expects pnpm-lock.yaml in app-only build contexts and now uses an app-local install path that works with the apps/smtp-server Docker build context
  • SMTP container entrypoint correction — fixed runtime command in apps/smtp-server/Dockerfile to execute dist/server.js from the container working directory
  • SMTP Docker Corepack compatibility — pinned Do...
Read more

[0.2.4] - Compliance & Commerce

08 May 22:30

Choose a tag to compare

Added

Billing

  • Stripe webhook automationpnpm stripe:seed now registers the Stripe webhook endpoint automatically. For non-development environments it lists existing webhook endpoints, creates the endpoint if not found (or updates its enabled events if it already exists), and saves the endpoint ID and whsec_* secret to the AppSetting table. Development environments skip registration and print a stripe listen hint instead
  • getWebhookSecret() — new export in stripe-config.ts that resolves the webhook secret from the AppSetting table; used as a fallback in the Stripe webhook route when STRIPE_WEBHOOK_SECRET env var is not set
  • stripe.webhook config keysDB_CONFIG_KEYS in packages/lib now includes stripe.webhook.endpoint_id and stripe.webhook.secret so the seed script and webhook route share a single source of truth for key names

Schema

  • extraDomainSlots column — added extraDomainSlots Int @default(0) to the Team model for tracking purchased additional domain slots
  • extraMemberSlots column — added extraMemberSlots Int @default(0) to the Team model for tracking purchased additional team member slots

Legal Pages (Cloud)

  • Cookie Policy — new /cookie-policy page covering UK PECR-compliant cookie usage; contact: legal@nodebyte.co.uk
  • DMCA Policy — new /dmca page with designated DMCA agent and takedown procedure; primary contact: dmca@nodebyte.co.uk
  • Acceptable Use Policy — new /acceptable-use page defining prohibited use for the email platform; contact: legal@nodebyte.co.uk
  • Data Processing Agreement — new /dpa page (UK GDPR Art. 28) documenting sub-processors (NodeByte Hosting UK, Amazon SES EU/US, Upstash Redis EU); contact: legal@nodebyte.co.uk
  • /legal hub — new central legal index page listing all six policy documents (Terms, Privacy, Cookie Policy, AUP, DMCA, DPA) as navigable cards with contact email references

Plan Enforcement — Marketing Features

  • LimitReason.MARKETING_NOT_AVAILABLE — new enum value added to LimitReason in lib/constants/plans.ts for use across server guards and the upgrade modal
  • LimitService.checkMarketingAccess(teamId) — new static method returning false for Free plan teams in cloud mode; bypassed for self-hosted and admin/founder teams
  • Campaign API guardcreateCampaign and duplicateCampaign tRPC mutations now call checkMarketingAccess upfront and throw FORBIDDEN for Free plan teams
  • Campaign service guardcreateCampaignFromApi and scheduleCampaign in campaign-service.ts now call checkMarketingAccess and throw ByteSendApiError(FORBIDDEN) for Free plan teams

Plan Enforcement — Resource Limits

  • Limit methods return usage counters — All LimitService.check*Limit() methods now return { isLimitReached, limit, currentCount, reason? } to enable UI display of usage ratios
  • Domain limit enforcementdomain.createDomain now enforces the domain limit (base + extraDomainSlots) at the API level and returns descriptive error with currentCount
  • Team member limit enforcementteam.createTeamInvite enforces the team member limit (base + extraMemberSlots) and includes currentCount in error message
  • Contact limit enforcementContactQueueService.addBulkContactJobs checks the contact limit before queueing imports; includes currentCount in error
  • Webhook limit enforcementWebhookService.createWebhook already enforced limits (reconfirmed)
  • Contact book limit enforcementcontactBookService.createContactBook already enforced limits (reconfirmed)

Plan Enforcement — Purchase Add-ons

  • purchaseAddonMemberSlots mutation — new tRPC mutation (alongside existing purchaseAddonDomainSlots) allows teams to buy additional team member slots at CA$5/slot/month
  • Separate addon price IDsPRICE_KEYS.addon now includes both domainMonthly and memberMonthly for distinct pricing; createAddonCheckoutSession accepts addonType parameter to select the correct price
  • Webhook addon tracking — Stripe webhook processing now distinguishes between EXTRA_DOMAIN and EXTRA_MEMBER addon prices and updates team.extraDomainSlots and team.extraMemberSlots independently

Email Metering & Overage Billing

  • Email meter event reportingEmailQueueService now reports successful emails to Stripe billing.meterEventAdjustments after each send, tracked separately by type (marketing vs transactional). Failures are logged but non-fatal so email delivery is never blocked by metering issues
  • Overage usage tracking — Marketing and transactional emails beyond the plan's included monthly limit are now reported to Stripe for metered billing (overage charges)

Dashboard — Usage Indicators

  • Domain usage counter + purchase flow — "Add domain" now shows "X / Y" usage and includes an in-dialog add-on checkout CTA. When below limit, the add form is shown; when at limit, the add form is hidden and the purchase CTA + limit message are shown
  • Team member usage counter + purchase flow — "Invite Member" now shows "X / Y" usage and includes an in-dialog add-on checkout CTA. When below limit, invite form is shown; when at limit, the invite form is hidden and the purchase CTA + limit message are shown
  • Upgrade/purchase flow wiring — Domain/member add-on CTAs open Stripe checkout sessions with proper success/cancel URLs and metadata for webhook handling

Changed

Development Setup

  • Database migration required — run pnpm prisma migrate dev to add the extraMemberSlots column to the Team table and create the new member addon price in Stripe via pnpm stripe:seed

Marketing Site

  • Pricing plans updated — landing page pricing section (page.tsx and PricingTiers.tsx) now shows the correct three plans: Free (CA$0, 12,500 emails/mo), Hobby (CA$5/mo, 25,000 emails/mo), and Lite (CA$10/mo, 50,000 emails/mo, recommended). Removed the old Professional and Lifetime cards
  • Comparison table corrected — free tier row updated from 5,000/mo to 12,500/mo; "Lifetime plan" row replaced with "Custom plans" (✓ for ByteSend, — for all competitors) to reflect the current offering
  • CTA copy corrected — "5,000 emails per month" updated to "12,500 emails per month" in the bottom CTA section
  • Footer simplified — site footer reverted to minimal single-row layout (© ByteSend · Docs · Changelog · Legal · Status) with "Legal" linking to the new /legal hub instead of individual policy links
  • Legal contact emails — all hey@nodebyte.co.uk references in privacy/page.tsx and terms/page.tsx replaced with legal@nodebyte.co.uk

Dashboard — Marketing Feature Gating (UI)

  • Sidebar Marketing section locked on Free planAppSidebar now checks currentTeam.plan and, for Free plan teams in cloud mode, renders the Contacts and Campaigns items as non-navigable buttons with a lock icon and dimmed opacity. Clicking either item opens the upgrade modal. The "Marketing" section label gains a "Paid" badge
  • Campaigns page upgrade gate/campaigns renders a centred lock screen (icon + copy + "Upgrade plan" button) instead of the campaign list when the current team is on the Free plan
  • Contacts page upgrade gate/contacts renders the same lock screen pattern for Free plan teams
  • Upgrade modal messageUpgradeModal messages Record now includes MARKETING_NOT_AVAILABLE: "Marketing features (Contacts & Campaigns) are not available on the Free plan."

Dashboard — Editor Surfaces

  • Template/Campaign/Double opt-in editor layouts unified — all three editor pages now share the same sticky top bar, metadata strips, and canvas structure for consistent behavior
  • Top-bar clipping resolved across all editor pages — removed negative vertical wrapper offsets that were causing the back/status row to clip under the dashboard header
  • Editor canvas attached to metadata strip — removed the visual gap so the editor appears connected to the variables/required strip rather than floating as a separate block
  • Editor width expanded to full available content area — editor canvas now spans the dashboard content width instead of constrained widths/max-width wrappers
  • Theme-aware editor shell — editor wrapper, toolbar, popovers, slash command menu, and inline controls now use design tokens (bg-background, bg-popover, border-border, text-foreground) for light/dark consistency
  • Toolbar feature expansion — added paragraph/heading controls, task list, text alignment (left/center/right), blockquote, code block, horizontal rule, unlink, and undo/redo actions in addition to existing inline formatting and list controls

Fixed

Database

  • Migration history drift resolved_prisma_migrations table contained ~50 stale records from old migrations that no longer existed locally. Truncated the table and replaced the entire history with a single baseline migration (20260507000000_init) generated from the current schema. Local prisma/migrations/ folder now has exactly one migration; DB records match

[0.2.3] - The Operator Update

07 May 17:58

Choose a tag to compare

Added

SMTP

  • Per-team custom SMTP username — teams can now set a custom SMTP username instead of the shared bytesend default. The username is stored on the Team model (smtpUsername String?; null means use the global default). Validation enforces alphanumeric characters, underscores, dots, and hyphens (max 64 chars)
  • POST /api/v1/smtp/auth endpoint — new public API endpoint the SMTP server calls during onAuth to validate credentials. Looks up the API key, resolves the team's effective username (smtpUsername ?? SMTP_USER), and returns {valid: true} or 401. Exempt from the standard bearer-token middleware and rate limiter
  • SMTP server remote authonAuth in apps/smtp-server now calls the ByteSend API for credential validation instead of doing a static string comparison against SMTP_AUTH_USERNAME. The server is fully stateless about usernames; all logic lives in the API

Dashboard

  • Editable SMTP settings page — the SMTP credentials page (/developer-settings/smtp) is now a client component with an editable username field. Shows the effective username (custom or default), a "Reset to default" button when a custom value is set, and an inline save button that appears when the field is dirty
  • team.getTeamDetails tRPC query — new procedure returning id, name, and smtpUsername for the current team
  • team.updateSmtpUsername tRPC mutation — team admins can update or reset their SMTP username. Passing null reverts to the global default

Notifications

  • Discord webhook notificationsDISCORD_WEBHOOK_URL is now fully wired to real app events. When set, ByteSend posts a message to Discord on: new user signup (email + name), new team created (name + team ID), hard bounce detected (email ID, subject, affected recipients, team ID), and spam complaint received (same fields). All notifications fire-and-forget and never block the main request flow

Changed

Design System — Dashboard-wide consistency pass

All dashboard pages and components now consistently follow the design system. The following violations were corrected across 13+ files:

Status badges

  • email-status-badge.tsx — replaced all hardcoded Tailwind palette colors (bg-emerald-500/15 text-emerald-500, bg-purple-500/15 text-purple-500, bg-gray-400/30, etc.) with CSS variable design tokens (bg-green/15 text-green, bg-primary/10 text-primary, bg-muted/30 text-muted-foreground, etc.)
  • domain-badge.tsx — replaced text-emerald-600 dark:text-emerald-400 bg-emerald-500/10, text-amber-600 dark:text-amber-400 bg-amber-500/10 etc. with text-green bg-green/15, text-yellow bg-yellow/15
  • webhook-status-badge.tsx and webhook-call-status-badge.tsx — replaced bg-gray-700/10 text-gray-400 default state and removed fixed w-[130px]/w-[110px] in favour of min-w-24 px-2
  • contact-list.tsx — replaced fixed w-[130px] status pills with min-w-24 px-2

Card and table borders

  • email-list.tsx, contact-list.tsx, webhook-list.tsx, suppression-list.tsx, dashboard/reputation-metrics.tsx — replaced border shadow / border rounded-xl shadow with border border-border/60 rounded-xl
  • contact-list.tsx — fixed typo border-broderborder-border/60

Email preview

  • email-details.tsx — replaced dark:bg-slate-200 h-[350px] email preview container with bg-white h-88; card borders changed from rounded-lg shadow to border-border/60 rounded-xl; border-gray-300 dark:border-gray-700 timeline connector → border-border

Editor pages — dark-mode canvas

  • double-opt-in/page.tsx — settings card border rounded-lg shadowborder border-border/60 rounded-xl; editor wrapper rounded-lg bg-gray-50 → same dark-aware panel (border + header + white canvas) applied to template and campaign editors in the previous release

Arbitrary Tailwind values replaced with shorthands across all modified files:

  • w-[700px]w-175 / max-w-175; w-[600px]w-150; w-[300px]w-75; w-[240px]w-60; w-[200px]w-50; w-[180px]w-45; w-[150px]w-38; w-[130px]min-w-24 px-2; w-[110px]min-w-24 px-2; w-[100px]w-25; w-[80px]w-20; w-[70px]w-17.5; h-[350px]h-88; h-[18px] w-[18px]h-4.5 w-4.5; w-[300px] tooltip → w-75; -mt-[0.125rem]-mt-0.5

[0.2.2] - Reliable Delivery

06 May 01:30

Choose a tag to compare

Fixed

Email Delivery

  • Emails stuck as QUEUED forever — when getConfigurationSetName() returned null the job silently returned success to BullMQ without marking the email as failed, leaving it in QUEUED state indefinitely. Now explicitly marks the email FAILED with a descriptive error event so the problem is visible in the email log
  • No retry on transient SES errors — any SES error (throttle, 5xx, network hiccup) was immediately written as a permanent FAILED with no retry. Transient errors (TooManyRequestsException, ServiceUnavailableException, server-fault responses) are now re-thrown so BullMQ retries the job automatically
  • Zero retry budget on email jobsDEFAULT_QUEUE_OPTIONS had no attempts key so BullMQ defaulted to 1 attempt. Email jobs now receive 3 attempts with 10 s exponential backoff (10 s → 20 s → 40 s)
  • Silent worker crashes — email queue workers had no error or stalled event listeners; a crashed worker left jobs orphaned with no log trace. Both events are now logged with region and queue context

Domain / DKIM

  • DKIM auto-reregister infinite loop — the hourly background verification job triggered auto-reregistration on wrong_key DNS status and also when lastCheckedAt was null, causing an endless re-registration cycle for newly added domains. Auto-reregister now only fires when the DKIM record is confirmed found in DNS (propagation confirmed) but SES has not acknowledged it for over 1 hour, and requires a non-null lastCheckedAt
  • Verify button inaccessible after DKIM re-generation — reregistration set isVerifying: true, which hid the Verify button and locked users out of the verification flow. Now sets isVerifying: false and uses a Redis-backed dkimReregistered flag (24 h TTL) to show an amber guidance banner with instructions; the banner clears automatically when the user clicks Verify

Version Display

  • Version always showing "unknown" in production — Docker Compose defaulted APP_VERSION to the string literal "unknown" when not provided by CI; the /api/version route immediately returned this sentinel without attempting the GitHub API fallback. The route now ignores "unknown" and "canary" as sentinel values, and the Docker Compose APP_VERSION arg now defaults to empty so the GitHub release lookup runs when CI does not inject a version