Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions .github/labeler.yml

This file was deleted.

127 changes: 82 additions & 45 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: Publish Docker

on:
push:
branches:
- main
- develop
tags:
- "*"
workflow_dispatch:
Expand All @@ -16,6 +19,7 @@ jobs:
name: Build and publish platform containers
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
fail-fast: false
Expand All @@ -35,6 +39,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.branch || github.ref }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v3
Expand Down Expand Up @@ -62,35 +67,57 @@ jobs:
DOCKER_FILE: ${{ matrix.app.dockerfile }}
CONTEXT: ${{ matrix.app.context }}
run: |
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
GIT_SHA="$(git rev-parse HEAD)"
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
REF_TYPE="branch"
REF_NAME="${{ github.event.inputs.branch }}"
else
REF_TYPE="${GITHUB_REF_TYPE}"
REF_NAME="${GITHUB_REF_NAME}"
fi

TAGS="$GIT_SHA"
APP_VERSION="$GIT_SHA"

if [ "$REF_TYPE" = "tag" ]; then
TAGS="$TAGS $REF_NAME latest"
APP_VERSION="$REF_NAME"
elif [ "$REF_NAME" = "main" ]; then
TAGS="$TAGS latest"
APP_VERSION="latest"
elif [ "$REF_NAME" = "develop" ]; then
TAGS="$TAGS develop"
APP_VERSION="develop"
fi

IMAGE_TAG_ARGS=""
for TAG in $TAGS; do
IMAGE_TAG_ARGS="$IMAGE_TAG_ARGS -t bytesend/$APP-$BUILD_PLATFORM:$TAG -t ghcr.io/bytesend/$APP-$BUILD_PLATFORM:$TAG"
done

docker buildx build \
-f "$DOCKER_FILE" \
--platform "linux/$BUILD_PLATFORM" \
--progress=plain \
--build-arg APP_VERSION="$APP_VERSION" \
--build-arg GIT_SHA="$GIT_SHA" \
-t "bytesend/$APP-$BUILD_PLATFORM:latest" \
-t "bytesend/$APP-$BUILD_PLATFORM:$GIT_SHA" \
-t "bytesend/$APP-$BUILD_PLATFORM:$APP_VERSION" \
-t "ghcr.io/bytesend/$APP-$BUILD_PLATFORM:latest" \
-t "ghcr.io/bytesend/$APP-$BUILD_PLATFORM:$GIT_SHA" \
-t "ghcr.io/bytesend/$APP-$BUILD_PLATFORM:$APP_VERSION" \
$IMAGE_TAG_ARGS \
--push \
"$CONTEXT"

create_and_publish_manifest:
name: Create and publish manifest
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
needs: build_and_publish_platform_containers
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.branch || github.ref }}

- name: Login to DockerHub
uses: docker/login-action@v3
Expand All @@ -107,52 +134,62 @@ jobs:

- name: Create and push DockerHub manifest
run: |
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
GIT_SHA="$(git rev-parse HEAD)"
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
REF_TYPE="branch"
REF_NAME="${{ github.event.inputs.branch }}"
else
REF_TYPE="${GITHUB_REF_TYPE}"
REF_NAME="${GITHUB_REF_NAME}"
fi

TAGS="$GIT_SHA"
if [ "$REF_TYPE" = "tag" ]; then
TAGS="$TAGS $REF_NAME latest"
elif [ "$REF_NAME" = "main" ]; then
TAGS="$TAGS latest"
elif [ "$REF_NAME" = "develop" ]; then
TAGS="$TAGS develop"
fi

for APP_NAME in bytesend smtp-proxy; do
docker manifest create \
bytesend/$APP_NAME:latest \
--amend bytesend/$APP_NAME-amd64:latest \
--amend bytesend/$APP_NAME-arm64:latest

docker manifest create \
bytesend/$APP_NAME:$GIT_SHA \
--amend bytesend/$APP_NAME-amd64:$GIT_SHA \
--amend bytesend/$APP_NAME-arm64:$GIT_SHA

docker manifest create \
bytesend/$APP_NAME:$APP_VERSION \
--amend bytesend/$APP_NAME-amd64:$APP_VERSION \
--amend bytesend/$APP_NAME-arm64:$APP_VERSION

docker manifest push bytesend/$APP_NAME:latest
docker manifest push bytesend/$APP_NAME:$GIT_SHA
docker manifest push bytesend/$APP_NAME:$APP_VERSION
for TAG in $TAGS; do
docker manifest create \
bytesend/$APP_NAME:$TAG \
--amend bytesend/$APP_NAME-amd64:$TAG \
--amend bytesend/$APP_NAME-arm64:$TAG

docker manifest push bytesend/$APP_NAME:$TAG
done
done

- name: Create and push GitHub Container Registry manifest
run: |
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
GIT_SHA="$(git rev-parse HEAD)"
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
REF_TYPE="branch"
REF_NAME="${{ github.event.inputs.branch }}"
else
REF_TYPE="${GITHUB_REF_TYPE}"
REF_NAME="${GITHUB_REF_NAME}"
fi

TAGS="$GIT_SHA"
if [ "$REF_TYPE" = "tag" ]; then
TAGS="$TAGS $REF_NAME latest"
elif [ "$REF_NAME" = "main" ]; then
TAGS="$TAGS latest"
elif [ "$REF_NAME" = "develop" ]; then
TAGS="$TAGS develop"
fi

for APP_NAME in bytesend smtp-proxy; do
docker manifest create \
ghcr.io/bytesend/$APP_NAME:latest \
--amend ghcr.io/bytesend/$APP_NAME-amd64:latest \
--amend ghcr.io/bytesend/$APP_NAME-arm64:latest

docker manifest create \
ghcr.io/bytesend/$APP_NAME:$GIT_SHA \
--amend ghcr.io/bytesend/$APP_NAME-amd64:$GIT_SHA \
--amend ghcr.io/bytesend/$APP_NAME-arm64:$GIT_SHA

docker manifest create \
ghcr.io/bytesend/$APP_NAME:$APP_VERSION \
--amend ghcr.io/bytesend/$APP_NAME-amd64:$APP_VERSION \
--amend ghcr.io/bytesend/$APP_NAME-arm64:$APP_VERSION

docker manifest push ghcr.io/bytesend/$APP_NAME:latest
docker manifest push ghcr.io/bytesend/$APP_NAME:$GIT_SHA
docker manifest push ghcr.io/bytesend/$APP_NAME:$APP_VERSION
for TAG in $TAGS; do
docker manifest create \
ghcr.io/bytesend/$APP_NAME:$TAG \
--amend ghcr.io/bytesend/$APP_NAME-amd64:$TAG \
--amend ghcr.io/bytesend/$APP_NAME-arm64:$TAG

docker manifest push ghcr.io/bytesend/$APP_NAME:$TAG
done
done
60 changes: 60 additions & 0 deletions .github/workflows/npm-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Release JS Packages

on:
push:
branches:
- main
paths:
- "packages/sdk/**" # Trigger only changes in packages
- ".github/workflows/npm-release.yml"
workflow_dispatch:

permissions:
id-token: write # Required for OIDC
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node.js 20.x
uses: actions/setup-node@v4
with:
node-version: 20.x
registry-url: "https://registry.npmjs.org"
cache: pnpm
cache-dependency-path: pnpm-lock.yaml

- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 9.0.0

- name: Update npm for OIDC support
run: npm install -g npm@latest

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Publish with Trusted Publishing (OIDC)
if: ${{ secrets.NPM_TOKEN == '' }}
working-directory: packages/sdk
run: |
pnpm run build
npm publish --access public --no-git-checks --provenance

- name: Publish with NPM token
if: ${{ secrets.NPM_TOKEN != '' }}
working-directory: packages/sdk
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
pnpm run build
npm publish --access public --no-git-checks
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **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`
- **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
- **JavaScript SDK release workflow** — added `.github/workflows/npm-release.yml` to build and publish the `bytesend-js` package from `packages/sdk` on pushes to `main` and manual dispatch

### Fixed

Expand All @@ -66,6 +71,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
#### 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.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 alignment** — `apps/web/src/lib/usage.unit.test.ts` now derives expected costs from exported usage constants instead of stale hardcoded values
- **Workspace SDK resolution in Vitest** — `apps/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 isolation** — `apps/web/src/server/service/contact-service.unit.test.ts` now mocks `LimitService.checkContactsLimit` to avoid transitive `TeamService` cache dependencies and prevent brittle failures

#### SMTP Server
- **SMTP Dockerfile context compatibility** — `apps/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 Docker image pnpm activation in `apps/smtp-server/Dockerfile` to `pnpm@9.0.0` (instead of `latest`) to avoid Corepack bootstrap/runtime failures in CI builds
- **SMTP package manager metadata** — added `packageManager: pnpm@9.0.0` to `apps/smtp-server/package.json` so Corepack does not auto-inject newer pnpm versions during container installs

### Security

- **SES callback SSRF hardening** — `apps/web/src/app/api/ses_callback/route.ts` no longer fetches user-provided `SubscribeURL` directly; it now constructs a trusted AWS SNS confirmation URL from validated `TopicArn`/`Token` components before issuing the request
Expand Down
2 changes: 1 addition & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ an individual is officially representing the community in public spaces.
## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders at support@bytesend.cloud. All complaints will
reported to the community leaders at support@nodebyte.co.uk. All complaints will
be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
Expand Down
4 changes: 2 additions & 2 deletions SUPPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ Need help with ByteSend?

## Commercial / Cloud Support

- support@bytesend.cloud
- support@nodebyte.co.uk

## Security Reports

Do not open public issues for vulnerabilities.

- See `SECURITY.md`
- Email: support@bytesend.cloud
- Email: support@nodebyte.co.uk
8 changes: 4 additions & 4 deletions apps/smtp-server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"

RUN corepack enable
RUN corepack prepare pnpm@latest --activate
RUN corepack prepare pnpm@9.0.0 --activate

FROM base AS builder
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
COPY package.json ./
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --no-frozen-lockfile

# Build the SMTP server
COPY src ./src
Expand All @@ -32,4 +32,4 @@ COPY --from=builder /app/node_modules ./node_modules
# 2465: Alternative SMTPS port
EXPOSE 25 465 587 2465 2587

CMD ["node", "apps/smtp-server/dist/server.js"]
CMD ["node", "dist/server.js"]
1 change: 1 addition & 0 deletions apps/smtp-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"keywords": [],
"author": "",
"packageManager": "pnpm@9.0.0",
"license": "ISC",
"dependencies": {
"@types/mailparser": "^3.4.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- Add missing Team extra member slots column expected by Prisma schema and tests
ALTER TABLE "Team"
ADD COLUMN IF NOT EXISTS "extraMemberSlots" INTEGER NOT NULL DEFAULT 0;
7 changes: 5 additions & 2 deletions apps/web/src/lib/usage.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getUsageDate,
getUsageTimestamp,
getUsageUnits,
UNIT_PRICE,
TRANSACTIONAL_UNIT_CONVERSION,
} from "~/lib/usage";

Expand All @@ -29,7 +30,9 @@ describe("usage helpers", () => {
});

it("calculates cost per email type", () => {
expect(getCost(10, EmailUsageType.MARKETING)).toBe(0.01);
expect(getCost(4, EmailUsageType.TRANSACTIONAL)).toBe(0.001);
expect(getCost(10, EmailUsageType.MARKETING)).toBe(10 * UNIT_PRICE);
expect(getCost(4, EmailUsageType.TRANSACTIONAL)).toBe(
Math.floor(4 / TRANSACTIONAL_UNIT_CONVERSION) * UNIT_PRICE,
);
});
});
Loading
Loading