From ecd28428d247a33a1c28a4b2f7b4db182d42f9f0 Mon Sep 17 00:00:00 2001 From: KM Koushik Date: Thu, 20 Mar 2025 21:56:12 +1100 Subject: [PATCH] dockerize smtp-proxy (#118) --- .github/workflows/publish.yml | 91 ++++++++++++++++------------- apps/smtp-server/Dockerfile | 45 ++++++++++++++ apps/smtp-server/docker-compose.yml | 29 +++++++++ apps/smtp-server/src/server.ts | 25 ++++---- apps/smtp-server/src/usage.js | 4 +- 5 files changed, 141 insertions(+), 53 deletions(-) create mode 100644 apps/smtp-server/Dockerfile create mode 100644 apps/smtp-server/docker-compose.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 78f195b..b697cc1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,6 +23,13 @@ jobs: os: - warp-ubuntu-latest-x64-2x - warp-ubuntu-latest-arm64-2x + app: + - name: unsend + dockerfile: ./docker/Dockerfile + context: . + - name: smtp-proxy + dockerfile: ./Dockerfile + context: ./apps/smtp-server steps: - uses: actions/checkout@v4 @@ -53,23 +60,23 @@ jobs: GIT_SHA="$(git rev-parse HEAD)" docker build \ - -f ./docker/Dockerfile \ + -f ${{ matrix.app.dockerfile }} \ --progress=plain \ - -t "unsend/unsend-$BUILD_PLATFORM:latest" \ - -t "unsend/unsend-$BUILD_PLATFORM:$GIT_SHA" \ - -t "unsend/unsend-$BUILD_PLATFORM:$APP_VERSION" \ - -t "ghcr.io/unsend-dev/unsend-$BUILD_PLATFORM:latest" \ - -t "ghcr.io/unsend-dev/unsend-$BUILD_PLATFORM:$GIT_SHA" \ - -t "ghcr.io/unsend-dev/unsend-$BUILD_PLATFORM:$APP_VERSION" \ - . + -t "unsend/${{ matrix.app.name }}-$BUILD_PLATFORM:latest" \ + -t "unsend/${{ matrix.app.name }}-$BUILD_PLATFORM:$GIT_SHA" \ + -t "unsend/${{ matrix.app.name }}-$BUILD_PLATFORM:$APP_VERSION" \ + -t "ghcr.io/unsend-dev/${{ matrix.app.name }}-$BUILD_PLATFORM:latest" \ + -t "ghcr.io/unsend-dev/${{ matrix.app.name }}-$BUILD_PLATFORM:$GIT_SHA" \ + -t "ghcr.io/unsend-dev/${{ matrix.app.name }}-$BUILD_PLATFORM:$APP_VERSION" \ + ${{ matrix.app.context }} - name: Push the docker image to DockerHub - run: docker push --all-tags "unsend/unsend-$BUILD_PLATFORM" + run: docker push --all-tags "unsend/${{ matrix.app.name }}-$BUILD_PLATFORM" env: BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-2x' && 'arm64' || 'amd64' }} - name: Push the docker image to GitHub Container Registry - run: docker push --all-tags "ghcr.io/unsend-dev/unsend-$BUILD_PLATFORM" + run: docker push --all-tags "ghcr.io/unsend-dev/${{ matrix.app.name }}-$BUILD_PLATFORM" env: BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-2x' && 'arm64' || 'amd64' }} @@ -103,45 +110,49 @@ jobs: APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')" GIT_SHA="$(git rev-parse HEAD)" - docker manifest create \ - unsend/unsend:latest \ - --amend unsend/unsend-amd64:latest \ - --amend unsend/unsend-arm64:latest \ + for APP_NAME in unsend smtp-proxy; do + docker manifest create \ + unsend/$APP_NAME:latest \ + --amend unsend/$APP_NAME-amd64:latest \ + --amend unsend/$APP_NAME-arm64:latest - docker manifest create \ - unsend/unsend:$GIT_SHA \ - --amend unsend/unsend-amd64:$GIT_SHA \ - --amend unsend/unsend-arm64:$GIT_SHA \ + docker manifest create \ + unsend/$APP_NAME:$GIT_SHA \ + --amend unsend/$APP_NAME-amd64:$GIT_SHA \ + --amend unsend/$APP_NAME-arm64:$GIT_SHA - docker manifest create \ - unsend/unsend:$APP_VERSION \ - --amend unsend/unsend-amd64:$APP_VERSION \ - --amend unsend/unsend-arm64:$APP_VERSION \ + docker manifest create \ + unsend/$APP_NAME:$APP_VERSION \ + --amend unsend/$APP_NAME-amd64:$APP_VERSION \ + --amend unsend/$APP_NAME-arm64:$APP_VERSION - docker manifest push unsend/unsend:latest - docker manifest push unsend/unsend:$GIT_SHA - docker manifest push unsend/unsend:$APP_VERSION + docker manifest push unsend/$APP_NAME:latest + docker manifest push unsend/$APP_NAME:$GIT_SHA + docker manifest push unsend/$APP_NAME:$APP_VERSION + 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)" - docker manifest create \ - ghcr.io/unsend-dev/unsend:latest \ - --amend ghcr.io/unsend-dev/unsend-amd64:latest \ - --amend ghcr.io/unsend-dev/unsend-arm64:latest \ + for APP_NAME in unsend smtp-proxy; do + docker manifest create \ + ghcr.io/unsend-dev/$APP_NAME:latest \ + --amend ghcr.io/unsend-dev/$APP_NAME-amd64:latest \ + --amend ghcr.io/unsend-dev/$APP_NAME-arm64:latest - docker manifest create \ - ghcr.io/unsend-dev/unsend:$GIT_SHA \ - --amend ghcr.io/unsend-dev/unsend-amd64:$GIT_SHA \ - --amend ghcr.io/unsend-dev/unsend-arm64:$GIT_SHA \ + docker manifest create \ + ghcr.io/unsend-dev/$APP_NAME:$GIT_SHA \ + --amend ghcr.io/unsend-dev/$APP_NAME-amd64:$GIT_SHA \ + --amend ghcr.io/unsend-dev/$APP_NAME-arm64:$GIT_SHA - docker manifest create \ - ghcr.io/unsend-dev/unsend:$APP_VERSION \ - --amend ghcr.io/unsend-dev/unsend-amd64:$APP_VERSION \ - --amend ghcr.io/unsend-dev/unsend-arm64:$APP_VERSION \ + docker manifest create \ + ghcr.io/unsend-dev/$APP_NAME:$APP_VERSION \ + --amend ghcr.io/unsend-dev/$APP_NAME-amd64:$APP_VERSION \ + --amend ghcr.io/unsend-dev/$APP_NAME-arm64:$APP_VERSION - docker manifest push ghcr.io/unsend-dev/unsend:latest - docker manifest push ghcr.io/unsend-dev/unsend:$GIT_SHA - docker manifest push ghcr.io/unsend-dev/unsend:$APP_VERSION + docker manifest push ghcr.io/unsend-dev/$APP_NAME:latest + docker manifest push ghcr.io/unsend-dev/$APP_NAME:$GIT_SHA + docker manifest push ghcr.io/unsend-dev/$APP_NAME:$APP_VERSION + done diff --git a/apps/smtp-server/Dockerfile b/apps/smtp-server/Dockerfile new file mode 100644 index 0000000..9db1e75 --- /dev/null +++ b/apps/smtp-server/Dockerfile @@ -0,0 +1,45 @@ +# Stage 1: Build stage +FROM node:20-alpine AS builder + +# Install pnpm (package manager) globally +RUN npm install -g pnpm + +# Set working directory for the application +WORKDIR /app + +# Copy configuration files first +COPY package.json tsconfig.json tsup.config.ts ./ + +# Install dependencies (including devDependencies) +RUN pnpm install + +# Copy the source code +COPY src/ ./src/ + +# Build the application +RUN pnpm run build + + +# Remove development dependencies to reduce size +RUN pnpm prune --prod + +# Stage 2: Production stage +FROM node:20-alpine AS production + +# Set working directory in the final image +WORKDIR /app + +# Copy necessary files from the builder stage: production node_modules, build output, and package definition +COPY --from=builder /app/node_modules ./node_modules + +# Copy only the necessary files from builder +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/package.json ./package.json + + +# Expose SMTP ports (standard SMTP, SMTPS, and alternative ports) +EXPOSE 25 465 587 2465 2587 + + +# Run the SMTP server +CMD ["node", "dist/server.js"] diff --git a/apps/smtp-server/docker-compose.yml b/apps/smtp-server/docker-compose.yml new file mode 100644 index 0000000..ec2882a --- /dev/null +++ b/apps/smtp-server/docker-compose.yml @@ -0,0 +1,29 @@ +name: unsend-smtp-server + +services: + smtp-server: + container_name: unsend-smtp-server + image: unsend/smtp-proxy:latest + # Pass necessary environment variables + environment: + SMTP_AUTH_USERNAME: "unsend" # can be anything, just use the same while sending emails + UNSEND_BASE_URL: "https://app.unsend.dev" # your self hosted unsend instance url + + # Uncomment this if you have SSL certificates. port 465 and 2465 will be using SSL + # UNSEND_API_KEY_PATH: "/certs/server.key" + # UNSEND_API_CERT_PATH: "/certs/server.crt" + # If you have SSL certificates, mount them here (read-only recommended) + + # volumes: + # - ./certs/server.key:/certs/server.key:ro + # - ./certs/server.crt:/certs/server.crt:ro + + # Expose the SMTP ports + ports: + - "25:25" + - "587:587" + - "2587:2587" + - "465:465" + - "2465:2465" + # Restart always or on-failure, depending on preference + restart: unless-stopped diff --git a/apps/smtp-server/src/server.ts b/apps/smtp-server/src/server.ts index 56b20a5..da6f966 100644 --- a/apps/smtp-server/src/server.ts +++ b/apps/smtp-server/src/server.ts @@ -81,10 +81,9 @@ const serverOptions: SMTPServerOptions = { replyTo: parsed.replyTo?.text, }; - console.log("Parsed email data:", emailObject); // Debug statement - sendEmailToUnsend(emailObject, session.user) .then(() => callback()) + .then(() => console.log("Email sent successfully to: ", emailObject.to)) .catch((error) => { console.error("Failed to send email:", error.message); callback(error); @@ -104,18 +103,22 @@ const serverOptions: SMTPServerOptions = { }; function startServers() { - // Implicit SSL/TLS for ports 465 and 2465 - [465, 2465].forEach((port) => { - const server = new SMTPServer({ ...serverOptions, secure: true }); + if (SSL_KEY_PATH && SSL_CERT_PATH) { + // Implicit SSL/TLS for ports 465 and 2465 + [465, 2465].forEach((port) => { + const server = new SMTPServer({ ...serverOptions, secure: true }); - server.listen(port, () => { - console.log(`Implicit SSL/TLS SMTP server is listening on port ${port}`); - }); + server.listen(port, () => { + console.log( + `Implicit SSL/TLS SMTP server is listening on port ${port}` + ); + }); - server.on("error", (err) => { - console.error(`Error occurred on port ${port}:`, err); + server.on("error", (err) => { + console.error(`Error occurred on port ${port}:`, err); + }); }); - }); + } // STARTTLS for ports 25, 587, and 2587 [25, 587, 2587].forEach((port) => { diff --git a/apps/smtp-server/src/usage.js b/apps/smtp-server/src/usage.js index 3e82093..7fa570c 100644 --- a/apps/smtp-server/src/usage.js +++ b/apps/smtp-server/src/usage.js @@ -1,8 +1,8 @@ const nodemailer = require("nodemailer"); const transporter = nodemailer.createTransport({ - host: "smtp.unsend.dev", - port: 2587, + host: "localhost", + port: 25, secure: false, auth: { user: "unsend",