Skip to content
Draft
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
85 changes: 79 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,81 @@ jobs:
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: release-assets
name: release-assets-macos
path: |
release/*.dmg
release/*.zip
release/*.yml
release/*.blockmap
retention-days: 7

# ── Job 2: Poll notarization on Linux (cheap runner for the wait) ────────
# ── Job 2: Build Linux distributables on Ubuntu ──────────────────────────
build-linux:
runs-on: ubuntu-latest
env:
NODE_OPTIONS: --max-old-space-size=4096
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install system dependencies
run: |
sudo apt-get update
# python3-setuptools provides distutils, which node-gyp needs to
# rebuild better-sqlite3 for Electron (Python 3.12 dropped it from stdlib).
sudo apt-get install -y xvfb libnss3 libatk-bridge2.0-0 libgtk-3-0 libxss1 libasound2t64 python3-setuptools

- name: Install dependencies
run: npm ci

- name: Type check
run: npm run typecheck

- name: Run unit tests
run: npm run test:unit

- name: Set version from git tag
if: startsWith(github.ref, 'refs/tags/v')
run: |
VERSION="${GITHUB_REF_NAME#v}"
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
IFS='.' read -ra PARTS <<< "$VERSION"
while [ ${#PARTS[@]} -lt 3 ]; do PARTS+=("0"); done
VERSION="${PARTS[0]}.${PARTS[1]}.${PARTS[2]}"
fi
echo "Setting package version to $VERSION"
npm version "$VERSION" --no-git-tag-version --allow-same-version

- name: Build
env:
MAIN_VITE_GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID_BACKUP_2 }}
MAIN_VITE_GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET_BACKUP_2 }}
VITE_POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
VITE_POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
run: npm run build

- name: Build Linux distributables
run: npx electron-builder --linux AppImage deb --publish never
timeout-minutes: 10

- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: release-assets-linux
path: |
release/*.AppImage
release/*.deb
release/*.yml
release/*.blockmap
retention-days: 7

# ── Job 3: Poll notarization on Linux (cheap runner for the wait) ────────
wait-for-notarization:
needs: build-and-sign
runs-on: ubuntu-latest
Expand Down Expand Up @@ -220,15 +286,15 @@ jobs:
if: always()
run: rm -f /tmp/apikey.p8

# ── Job 3: Staple notarization ticket on macOS ───────────────────────────
# ── Job 4: Staple notarization ticket on macOS ───────────────────────────
staple:
needs: [build-and-sign, wait-for-notarization]
runs-on: macos-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: release-assets
name: release-assets-macos
path: release

- name: Staple notarization ticket to DMG
Expand All @@ -249,11 +315,12 @@ jobs:
release/*.blockmap
retention-days: 7

# ── Job 4: Upload release assets (cheap Linux runner) ────────────────────
# ── Job 5: Upload release assets (cheap Linux runner) ────────────────────
release:
needs: [build-and-sign, staple]
needs: [build-and-sign, build-linux, staple]
if: |
needs.build-and-sign.result == 'success' &&
needs.build-linux.result == 'success' &&
needs.staple.result == 'success' &&
startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
Expand All @@ -264,6 +331,12 @@ jobs:
name: release-assets-stapled
path: release

- name: Download Linux artifacts
uses: actions/download-artifact@v4
with:
name: release-assets-linux
path: release

- name: Upload assets to release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Built with Electron, React, TypeScript, and Tailwind CSS.

<br />

[**Download for macOS**](https://github.com/ankitvgupta/exo/releases/latest) &nbsp;&bull;&nbsp; [Documentation](https://exo.email) &nbsp;&bull;&nbsp; [Changelog](https://github.com/ankitvgupta/exo/releases)
[**Download for macOS or Linux**](https://github.com/ankitvgupta/exo/releases/latest) &nbsp;&bull;&nbsp; [Documentation](https://exo.email) &nbsp;&bull;&nbsp; [Changelog](https://github.com/ankitvgupta/exo/releases)

<br />

Expand All @@ -33,7 +33,7 @@ Exo treats AI as a first-class citizen — not a bolted-on feature. Every email

## Getting Started

You can click the "Download .dmg" button above to download a Mac app that is ready for configuration. All you need to provide is Gmail API information (it has instructions) and an Anthropic API Key. If you're a developer, see the instructions at the bottom, or ask Claude Code to figure it out.
You can click the download button above to get a packaged app that is ready for configuration. macOS releases ship as a DMG, and Linux releases ship as AppImage and deb packages. All you need to provide is Gmail API information (it has instructions) and an Anthropic API Key. If you're a developer, see the instructions at the bottom, or ask Claude Code to figure it out.

## Features

Expand Down Expand Up @@ -158,6 +158,7 @@ https://github.com/user-attachments/assets/442f5320-2bec-4348-937d-48ad2100552e
- **Auto-update** — checks for updates daily with download progress, supports pre-release channels
- **Default mail app** — register as the system default mail handler (mailto: protocol)
- **macOS native** — hidden titlebar with traffic light buttons, code-signed and notarized
- **Linux packages** — AppImage and deb builds with native window chrome and mailto registration
- **Dark mode** — class-based theme toggle with smart inversion for email content
- **Inbox density** — comfortable, default, and compact density settings
- **PostHog analytics** — opt-in analytics with session replay for debugging (no PII)
Expand Down
Loading
Loading