Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
12 changes: 12 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
# Pre-commit hook: run SwiftLint on staged Swift files
set -euo pipefail

STAGED_SWIFT_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.swift$' || true)

if [ -z "$STAGED_SWIFT_FILES" ]; then
exit 0
fi

echo "🔍 Linting staged Swift files..."
swiftlint lint --strict $STAGED_SWIFT_FILES
72 changes: 72 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: "0 6 * * 1" # Every Monday at 6 AM

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

jobs:
lint:
name: Lint
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: SwiftLint
run: swiftlint lint --strict

build-macos:
name: Build & Test (macOS)
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Select Swift toolchain
run: swift --version
- name: Build
run: swift build
- name: Test
run: swift test

build-linux:
name: Build & Test (Linux)
runs-on: ubuntu-24.04
container:
image: swift:6.0-jammy
steps:
- uses: actions/checkout@v4
- name: Build
run: swift build
- name: Test
run: swift test

build-ios:
name: Build (iOS Simulator)
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Build for iOS
run: |
xcodebuild build \
-scheme Resend \
-destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=latest" \
-skipPackagePluginValidation \
| xcpretty && exit ${PIPESTATUS[0]}

build-visionos:
name: Build (visionOS Simulator)
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Build for visionOS
run: |
xcodebuild build \
-scheme Resend \
-destination "platform=visionOS Simulator,name=Apple Vision Pro,OS=latest" \
-skipPackagePluginValidation \
| xcpretty && exit ${PIPESTATUS[0]}
49 changes: 49 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Documentation

on:
push:
branches: [main]
release:
types: [published]

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
build-docs:
name: Generate DocC Documentation
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Select Swift toolchain
run: swift --version
- name: Generate Documentation
uses: swiftlang/github-workflows/swift-docc@main
with:
destination: /tmp/docs
hosting-base-path: Resend
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: /tmp/docs

deploy-docs:
name: Deploy to GitHub Pages
needs: build-docs
runs-on: ubuntu-24.04
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
64 changes: 61 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,66 @@
# macOS
.DS_Store
/.build
/Packages
.DS_Store?
.AppleDouble
.LSOverride
Icon?
.Spotlight-V100
.Trashes
Thumbs.db

# Xcode
xcuserdata/
DerivedData/
*.xcworkspace
*.xcuserstate
*.xcbkptlist
*.xcodespec
*.xcappdata
*.xcscheme
!*.xcscheme
build/

# Swift Package Manager
.build/
Packages/
*.swp
*.swo

# SwiftLint
.swiftlint.xcconfig

# Documentation
.docc-build/
.docc-output/
docs/

# Testing
.spi.yml
.spi

# Environment & Secrets
*.env
.env.*
.netrc
*.p8
*.p12
*.certSigningRequest

# IDEs
.idea/
*.iml
.vscode/
*.sw?
.vs/
*.sublime-workspace

# SwiftPM configuration
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

# Generated files
*.generated.swift

# Logs
*.log
51 changes: 51 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
disabled_rules:
- trailing_whitespace
- function_parameter_count
- nesting
- large_tuple
- for_where

opt_in_rules:
- empty_count
- redundant_discardable_let
- unused_optional_binding
- closure_spacing
- contains_over_first_not_nil
- convenience_type

analyzer_rules:
- unused_import
- unused_declaration

line_length: 340

type_body_length:
warning: 300
error: 500

file_length:
warning: 600
error: 1000

identifier_name:
min_length: 1
max_length: 50
allowed_symbols: ["_"]
excluded:
- id
- to
- cc
- ok

custom_rules:
no_force_unwrap:
name: "No Force Unwrap"
regex: '\?!|\.[!?]\('
message: "Avoid force unwrapping"
severity: error

excluded:
- .build
- .github
- .githooks
- Package.swift

This file was deleted.

129 changes: 129 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Changelog

All notable changes to this project will be documented in this file.

## [2.0.0] - 2025-01-XX

### 🎉 Major Rewrite - Complete API Coverage & Multi-Platform Support

#### Added
- ✅ **Complete API Coverage**: All 53 Resend API endpoints now implemented
- Emails: send, batch send, retrieve, update, cancel (5/5)
- Domains: create, get, list, verify, update, delete (6/6)
- API Keys: create, list, delete (3/3)
- Audiences: create, get, list, delete (4/4)
- Contacts: create, get, list, update, delete (5/5)
- Broadcasts: create, get, list, update, send, delete (6/6)

- 🏗️ **Modular Architecture**: Separated into 4 distinct modules
- `ResendCore`: Core models and protocols (no dependencies)
- `ResendKit`: URLSession-based client for iOS/macOS/Linux
- `ResendVapor`: Vapor framework integration
- `Resend`: Convenience re-export module

- 🌍 **Multi-Platform Support**: Now supports
- iOS 15.0+
- macOS 12.0+
- tvOS 15.0+
- watchOS 8.0+
- Mac Catalyst 15.0+
- Linux (with Swift 6.0+)

- 📚 **Complete Documentation**
- DocC documentation for all public APIs
- Comprehensive README with examples
- Dedicated Vapor integration guide
- Inline code documentation

- 🔧 **New Models**
- `ResendDomain` & `DNSRecord`
- `ResendAPIKey` & `ResendAPIKeyListItem`
- `ResendAudience`
- `ResendContact`
- `ResendBroadcast` & `ResendBroadcastSendResponse`
- `ResendListResponse<T>` for paginated endpoints
- `ResendDeleteResponse`
- `ResendBatchResponse` & `ResendBatchError`

- 🎯 **Protocol-Based Design**
- `HTTPClientProtocol` for custom HTTP implementations
- `ResendClientProtocol` and specialized client protocols
- Easy to mock for testing

#### Changed
- ⚡ **Breaking**: Removed static singleton pattern in favor of instance-based API
- Old: `ResendClient.email.send()`
- New: `resend.email.send()` or `req.resend.email.send()` (Vapor)

- 🔄 **Breaking**: Vapor integration now requires explicit initialization
- Old: Auto-initialized from environment
- New: Must call `app.resend.initialize()` in configure.swift

- 📦 **Breaking**: Package structure reorganized
- Models moved from `ResendKit/Models` to `ResendCore/Models`
- Vapor code extracted to separate `ResendVapor` module

- 🚀 **Improved**: URLSession-based HTTP client (was AsyncHTTPClient)
- Better iOS/macOS compatibility
- Reduced dependencies
- ResendVapor still uses Vapor's AsyncHTTPClient for server-side

- 📝 **Enhanced**: All models now have proper public initializers

#### Removed
- ❌ **Breaking**: Removed AsyncHTTPClient dependency from core
- ❌ Removed hard-coded static configuration
- ❌ Removed MOdels directory (fixed typo to Models)

#### Fixed
- 🐛 Fixed Sendable conformance for Vapor integration (Swift 6.0)
- 🐛 Fixed typo in Models directory name
- 🐛 Fixed missing public access modifiers
- 🐛 Corrected CodingKeys for snake_case API fields

#### Migration Guide

**For iOS/macOS Apps:**
```swift
// Before (didn't work properly)
import Resend
ResendClient.initialized(httpClient: client, apiKey: apiKey)
let response = try await ResendClient.email.send(email: email)

// After
import Resend
let resend = ResendClient(apiKey: "re_your_api_key")
let response = try await resend.email.send(email: email)
```

**For Vapor Apps:**
```swift
// Before
import Resend
// Auto-initialized

// After
import ResendVapor

// In configure.swift
app.resend.initialize(apiKey: "re_your_api_key")
// or from environment
app.resend.initialize()

// In routes
try await req.resend.email.send(email: email)
```

## [1.0.0] - 2023-12-03

### Added
- Initial release
- Basic email sending functionality
- Vapor integration
- ResendEmail model with attachments and tags support

### Known Issues
- Only 2 of 53 API endpoints implemented
- Limited to macOS server-side only
- Hard dependency on Vapor for all use cases
- Domain management stubs only
Loading
Loading