Releases: NodeByteLTD/ByteSend
Releases · NodeByteLTD/ByteSend
[0.2.6] - Limits, Logs and Insights
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
/broadcastsroute 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
/logsdashboard 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-keysroute - Settings SMTP page — added first-class
/settings/smtproute
Billing — Custom Plan Contracts
- Slider-based plan selector —
BillingPlanSelectorcomponent replaces static plan cards at/settings/billingwith 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 sessions —
createCustomCheckoutSessiontRPC mutation creates a Stripe session withprice_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 query —
getCustomPlanContracttRPC query lets the billing page andPlanDetailscomponent 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
Teammodel (customPlanEnabled,customMarketingEmailLimit,customTransactionalEmailLimit,customMonthlyPriceCents); populated at checkout completion via the Stripe webhook
Campaigns
- Campaign intent field — campaigns now store an
intentcolumn (migration20260510120000_add_campaign_intent) to distinguish campaign purpose at creation time;create-campaigndialog captures intent upfront
SDKs
- Go SDK initial package — introduced
packages/go-sdkwith 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.mdxand aligned navigation
CI / Review Automation
- CodeRabbit support — added
.github/workflows/coderabbit.ymland root.coderabbit.yamlto 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
CodeLangToggleandDevSection
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/billingnow surfaces the slider-basedBillingPlanSelectoras the primary upgrade path;PlanDetailsshows active custom contract details (limits and monthly price) when a custom plan is active - Custom plan limit enforcement —
LimitServicenow readscustomMarketingEmailLimitandcustomTransactionalEmailLimitfrom theTeamrecord 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 contract —
adminAssignPlanwithmethod: "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.tsfor 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/usagepage 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 normalization —
LimitService.isAdminOrFounderTeamnow normalises the environment admin email withtrim()+toLowerCase()and uses a case-insensitive Prisma query, preventing mismatches caused by casing differences in env config - Custom plan Stripe webhook sync —
syncStripeDatanow readscustomPlanEnabled,customMarketingEmailLimit,customTransactionalEmailLimit, andcustomMonthlyPriceCentsfrom subscription metadata and persists them back to theTeamrecord 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
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
NotificationProvidermodel to store provider type, config, status, and team linkage - Notification log schema — added
NotificationLogmodel to store per-dispatch delivery outcomes and failure details - Notification provider API — added
notificationProvidertRPC router withlist,getById,create,update,delete,test,getLogs, andgetStats - Notification dispatcher services — added provider dispatch and event emission services (
notification-provider-service.tsandnotification-emitter.ts) - Notifications settings page — added
/settings/notificationsUI for provider management, testing, logs, and usage guidance - Notification integration reference — added
.references/notification-integration.md
Admin / Billing Operations
- Admin plan assignment flow — added
adminAssignPlanto 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 = -1for unlimited daily sending
Billing / Plan Source-of-Truth
- Perks derived from shared plan constants —
apps/web/src/lib/constants/payments.tsnow generates plan perks from@bytesend/libPLANS rather than static duplicated data - Billing plan cards from shared plans —
/settings/billingplan options now derive from the shared PLANS map to avoid UI/config drift
CI / Automation
- Issue summary workflow — added
.github/workflows/issue-summary.ymlto automatically summarize newly opened issues - Stale cleanup workflow — added
.github/workflows/stale-cleanup.ymlto clean up inactive issues and pull requests - CodeQL workflow — introduced
.github/workflows/codeql.ymland enableddevelopbranch triggers - JavaScript SDK release workflow — added
.github/workflows/npm-release.ymlto build and publish thebytesend-jspackage frompackages/sdkchanges onmain(plus manual dispatch) - Python SDK release workflow — added
.github/workflows/pypi-release.ymlto build and publish thebytesend-pythonpackage frompackages/python-sdkon pushes tomainand manual dispatch
Community / Governance
- Repository security policy — added
.github/SECURITY.mdwith 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.mdwith development workflow, PR expectations, and testing checklist - Support guide — added
.github/SUPPORT.mdwith support channels and security-report routing
GitHub Templates
- PR template — added
.github/PULL_REQUEST_TEMPLATE.mdto standardize change summaries, testing notes, and release-impact checks - Issue template config — added
.github/ISSUE_TEMPLATE/config.ymlwith contact links and blank-issue controls - New issue forms — added
.github/ISSUE_TEMPLATE/feature.ymland.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 —
/settingsnow serves as the General overview (team profile and core team settings) - Members tab split-out — members management moved to dedicated
/settings/memberstab 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 monorepo —
apps/smtp-serveris 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/ByteSendandapps/smtp-serverpaths 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.jsontheme colors/navigation and expandedapps/docs/introduction.mdxto surface new billing, alerting, and auth capabilities
References
- Internal references expanded — added
.references/README.md,smtp-auth-and-operations.md,release-playbook.md, andrepository-governance.md - Webhook reference improvements — expanded
.references/webhook-architecture.mdwith 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 rmbefore publishing platform images/manifests, avoidingis a manifest listrerun failures - Website tests pnpm version alignment — removed hardcoded pnpm version from
.github/workflows/website-test.ymlso CI uses the repositorypackageManagerversion (pnpm@9.0.0) - Docker publish tag strategy hardening —
.github/workflows/docker-publish.ymlnow publishes ref-aware tags (latest,develop, version tag, and commit SHA) with matching multi-arch manifests - Manual Docker publish branch support — wired
workflow_dispatchbranch input into checkout and tag resolution so manual runs build/publish the selected branch - Labeler rules refresh — updated
.github/labeler.ymlto align automated PR labeling with the current repository structure - Actions runtime forward-compatibility — added
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=trueacross 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 stability —
apps/web/src/server/service/domain-service.tsnow initializes DNS resolvers with runtime-safe fallbacks (promises API or callback API), preventingERR_INVALID_ARG_TYPEwhen DNS methods are partially mocked in tests - Usage unit test expectation alignment —
apps/web/src/lib/usage.unit.test.tsnow derives expected costs from exported usage constants instead of stale hardcoded values - Workspace SDK resolution in Vitest —
apps/web/vitest.config.tsnow aliasesbytesend-jstopackages/sdk/index.tsduring tests so unit suites do not depend on prebuilt SDKdistartifacts - Contact-service unit test isolation —
apps/web/src/server/service/contact-service.unit.test.tsnow mocksLimitService.checkContactsLimitto avoid transitiveTeamServicecache dependencies and prevent brittle failures - Campaign security test alignment —
apps/web/src/server/api/routers/campaign-security.trpc.test.tsupdated for current plan access expectations
SMTP Server
- SMTP Dockerfile context compatibility —
apps/smtp-server/Dockerfileno longer expectspnpm-lock.yamlin app-only build contexts and now uses an app-local install path that works with theapps/smtp-serverDocker build context - SMTP container entrypoint correction — fixed runtime command in
apps/smtp-server/Dockerfileto executedist/server.jsfrom the container working directory - SMTP Docker Corepack compatibility — pinned Do...
[0.2.4] - Compliance & Commerce
Added
Billing
- Stripe webhook automation —
pnpm stripe:seednow 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 andwhsec_*secret to theAppSettingtable. Development environments skip registration and print astripe listenhint instead getWebhookSecret()— new export instripe-config.tsthat resolves the webhook secret from theAppSettingtable; used as a fallback in the Stripe webhook route whenSTRIPE_WEBHOOK_SECRETenv var is not setstripe.webhookconfig keys —DB_CONFIG_KEYSinpackages/libnow includesstripe.webhook.endpoint_idandstripe.webhook.secretso the seed script and webhook route share a single source of truth for key names
Schema
extraDomainSlotscolumn — addedextraDomainSlots Int @default(0)to theTeammodel for tracking purchased additional domain slotsextraMemberSlotscolumn — addedextraMemberSlots Int @default(0)to theTeammodel for tracking purchased additional team member slots
Legal Pages (Cloud)
- Cookie Policy — new
/cookie-policypage covering UK PECR-compliant cookie usage; contact:legal@nodebyte.co.uk - DMCA Policy — new
/dmcapage with designated DMCA agent and takedown procedure; primary contact:dmca@nodebyte.co.uk - Acceptable Use Policy — new
/acceptable-usepage defining prohibited use for the email platform; contact:legal@nodebyte.co.uk - Data Processing Agreement — new
/dpapage (UK GDPR Art. 28) documenting sub-processors (NodeByte Hosting UK, Amazon SES EU/US, Upstash Redis EU); contact:legal@nodebyte.co.uk /legalhub — 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 toLimitReasoninlib/constants/plans.tsfor use across server guards and the upgrade modalLimitService.checkMarketingAccess(teamId)— new static method returningfalsefor Free plan teams in cloud mode; bypassed for self-hosted and admin/founder teams- Campaign API guard —
createCampaignandduplicateCampaigntRPC mutations now callcheckMarketingAccessupfront and throwFORBIDDENfor Free plan teams - Campaign service guard —
createCampaignFromApiandscheduleCampaignincampaign-service.tsnow callcheckMarketingAccessand throwByteSendApiError(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 enforcement —
domain.createDomainnow enforces the domain limit (base + extraDomainSlots) at the API level and returns descriptive error with currentCount - Team member limit enforcement —
team.createTeamInviteenforces the team member limit (base + extraMemberSlots) and includes currentCount in error message - Contact limit enforcement —
ContactQueueService.addBulkContactJobschecks the contact limit before queueing imports; includes currentCount in error - Webhook limit enforcement —
WebhookService.createWebhookalready enforced limits (reconfirmed) - Contact book limit enforcement —
contactBookService.createContactBookalready enforced limits (reconfirmed)
Plan Enforcement — Purchase Add-ons
purchaseAddonMemberSlotsmutation — new tRPC mutation (alongside existingpurchaseAddonDomainSlots) allows teams to buy additional team member slots at CA$5/slot/month- Separate addon price IDs —
PRICE_KEYS.addonnow includes bothdomainMonthlyandmemberMonthlyfor distinct pricing;createAddonCheckoutSessionacceptsaddonTypeparameter to select the correct price - Webhook addon tracking — Stripe webhook processing now distinguishes between
EXTRA_DOMAINandEXTRA_MEMBERaddon prices and updatesteam.extraDomainSlotsandteam.extraMemberSlotsindependently
Email Metering & Overage Billing
- Email meter event reporting —
EmailQueueServicenow reports successful emails to Stripebilling.meterEventAdjustmentsafter 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 devto add theextraMemberSlotscolumn to the Team table and create the new member addon price in Stripe viapnpm stripe:seed
Marketing Site
- Pricing plans updated — landing page pricing section (
page.tsxandPricingTiers.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/moto12,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/legalhub instead of individual policy links - Legal contact emails — all
hey@nodebyte.co.ukreferences inprivacy/page.tsxandterms/page.tsxreplaced withlegal@nodebyte.co.uk
Dashboard — Marketing Feature Gating (UI)
- Sidebar Marketing section locked on Free plan —
AppSidebarnow checkscurrentTeam.planand, 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 —
/campaignsrenders 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 —
/contactsrenders the same lock screen pattern for Free plan teams - Upgrade modal message —
UpgradeModalmessages Record now includesMARKETING_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_migrationstable 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. Localprisma/migrations/folder now has exactly one migration; DB records match
[0.2.3] - The Operator Update
Added
SMTP
- Per-team custom SMTP username — teams can now set a custom SMTP username instead of the shared
bytesenddefault. The username is stored on theTeammodel (smtpUsername String?;nullmeans use the global default). Validation enforces alphanumeric characters, underscores, dots, and hyphens (max 64 chars) POST /api/v1/smtp/authendpoint — new public API endpoint the SMTP server calls duringonAuthto 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 auth —
onAuthinapps/smtp-servernow calls the ByteSend API for credential validation instead of doing a static string comparison againstSMTP_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.getTeamDetailstRPC query — new procedure returningid,name, andsmtpUsernamefor the current teamteam.updateSmtpUsernametRPC mutation — team admins can update or reset their SMTP username. Passingnullreverts to the global default
Notifications
- Discord webhook notifications —
DISCORD_WEBHOOK_URLis 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— replacedtext-emerald-600 dark:text-emerald-400 bg-emerald-500/10,text-amber-600 dark:text-amber-400 bg-amber-500/10etc. withtext-green bg-green/15,text-yellow bg-yellow/15webhook-status-badge.tsxandwebhook-call-status-badge.tsx— replacedbg-gray-700/10 text-gray-400default state and removed fixedw-[130px]/w-[110px]in favour ofmin-w-24 px-2contact-list.tsx— replaced fixedw-[130px]status pills withmin-w-24 px-2
Card and table borders
email-list.tsx,contact-list.tsx,webhook-list.tsx,suppression-list.tsx,dashboard/reputation-metrics.tsx— replacedborder shadow/border rounded-xl shadowwithborder border-border/60 rounded-xlcontact-list.tsx— fixed typoborder-broder→border-border/60
Email preview
email-details.tsx— replaceddark:bg-slate-200 h-[350px]email preview container withbg-white h-88; card borders changed fromrounded-lg shadowtoborder-border/60 rounded-xl;border-gray-300 dark:border-gray-700timeline connector →border-border
Editor pages — dark-mode canvas
double-opt-in/page.tsx— settings cardborder rounded-lg shadow→border border-border/60 rounded-xl; editor wrapperrounded-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
Fixed
Email Delivery
- Emails stuck as QUEUED forever — when
getConfigurationSetName()returnednullthe job silently returned success to BullMQ without marking the email as failed, leaving it inQUEUEDstate indefinitely. Now explicitly marks the emailFAILEDwith 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
FAILEDwith no retry. Transient errors (TooManyRequestsException,ServiceUnavailableException, server-fault responses) are now re-thrown so BullMQ retries the job automatically - Zero retry budget on email jobs —
DEFAULT_QUEUE_OPTIONShad noattemptskey 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
errororstalledevent 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_keyDNS status and also whenlastCheckedAtwasnull, causing an endless re-registration cycle for newly added domains. Auto-reregister now only fires when the DKIM record is confirmedfoundin DNS (propagation confirmed) but SES has not acknowledged it for over 1 hour, and requires a non-nulllastCheckedAt - 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 setsisVerifying: falseand uses a Redis-backeddkimReregisteredflag (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_VERSIONto the string literal"unknown"when not provided by CI; the/api/versionroute immediately returned this sentinel without attempting the GitHub API fallback. The route now ignores"unknown"and"canary"as sentinel values, and the Docker ComposeAPP_VERSIONarg now defaults to empty so the GitHub release lookup runs when CI does not inject a version