A modern, type-safe Swift SDK for the Resend email API with full platform support.
- ✅ Complete API Coverage — All Resend API endpoints implemented
- ✅ Multi-Platform — iOS, macOS, tvOS, watchOS, Mac Catalyst, visionOS, and Linux
- ✅ Type-Safe — Fully typed Swift interfaces with async/await
- ✅ Modular Architecture — Use only what you need
- ✅ Vapor Integration — First-class support for server-side Swift
- ✅ Automatic Retry — Configurable exponential backoff with jitter
- ✅ Request Logging — Optional swift-log integration
- ✅ Cursor Pagination — AsyncSequence-based
listAll()on every resource - ✅ Webhook Verification — Svix-compatible HMAC-SHA256 signature validation
- ✅ Zero Dependencies — Core package has no external dependencies
This package is organized into four modules:
Core models and protocols with no dependencies. Contains all data models, request/response types, and protocol definitions.
import ResendCoreURLSession-based HTTP client for iOS, macOS, and Linux. Complete implementation of the Resend API.
import ResendKit
let resend = ResendClient(apiKey: "re_your_api_key")Vapor framework integration for server-side Swift applications.
import ResendVapor
app.resend.initialize(apiKey: "re_your_api_key")Convenience module that re-exports ResendCore and ResendKit.
import ResendAdd the package to your Package.swift:
dependencies: [
.package(url: "https://github.com/devswiftzone/Resend.git", from: "1.0.0")
]Then add the appropriate module to your target dependencies:
.target(
name: "YourTarget",
dependencies: [
.product(name: "Resend", package: "Resend"), // For iOS/macOS apps
// or
.product(name: "ResendVapor", package: "Resend"), // For Vapor apps
]
)import Resend
let resend = ResendClient(apiKey: "re_your_api_key")
let email = ResendEmail(
from: "onboarding@yourdomain.com",
to: ["user@example.com"],
subject: "Welcome!",
html: "<h1>Welcome!</h1><p>Thanks for signing up.</p>"
)
do {
let response = try await resend.email.send(email: email)
print("Email sent! ID: \(response.id)")
} catch {
print("Failed to send email: \(error)")
}import Vapor
import ResendVapor
func configure(_ app: Application) throws {
app.resend.initialize(apiKey: "re_your_api_key")
}
func routes(_ app: Application) throws {
app.post("send-email") { req async throws -> String in
let email = ResendEmail(
from: "noreply@yourdomain.com",
to: ["user@example.com"],
subject: "Hello from Vapor!",
html: "<p>This email was sent from a Vapor app.</p>"
)
let response = try await req.resend.email.send(email: email)
return "Email sent with ID: \(response.id)"
}
}Configure automatic retries for transient failures and rate limits:
let resend = ResendClient(
apiKey: "re_your_api_key",
retry: RetryConfiguration(
maxRetries: 3,
baseDelay: 1.0,
maxDelay: 30.0,
enableJitter: true,
retryableStatusCodes: [429, 502, 503, 504]
)
)
// Retries on 429/5xx and network errors with exponential backoff + jitterIntegrate with swift-log for observability:
import Logging
let logger = Logger(label: "resend")
let resend = ResendClient(apiKey: "re_your_api_key", logger: logger)
// Logs every request (method + URL), response (status + timing), and errorsEvery list endpoint supports listAll() which returns an AsyncSequence:
let domains = resend.domains.listAll(limit: 10)
for try await domain in domains {
print("Domain: \(domain.name)")
}
// Or use the iterator directly:
var iter = resend.domains.listAll().makeAsyncIterator()
while let domain = try await iter.next() {
print("Domain: \(domain.name)")
}Verify incoming webhooks using the Svix signing scheme:
do {
let valid = try WebhookSignature.verify(
payload: rawBody, // Raw request body as String
id: req.headers["svix-id"] ?? "",
timestamp: req.headers["svix-timestamp"] ?? "",
signatureHeader: req.headers["svix-signature"] ?? "",
secret: "whsec_your_signing_secret", // From Resend dashboard
tolerance: 300 // Replay protection window (seconds)
)
// valid == true — process the webhook
} catch {
// Invalid or expired — reject with 400
}// Create a webhook
let webhook = try await resend.webhooks.create(
endpoint: "https://example.com/handler",
events: ["email.sent", "email.bounced"]
)
// List all webhooks
let list = try await resend.webhooks.list(limit: nil, after: nil, before: nil)
// Update a webhook
try await resend.webhooks.update(
id: webhook.id,
endpoint: "https://updated.com/handler",
events: nil,
disabled: true
)
// Delete a webhook
try await resend.webhooks.delete(id: webhook.id)- Send email
- Send batch emails
- Retrieve email
- Update scheduled email
- Cancel scheduled email
- Create domain
- Get domain
- List domains
- Verify domain
- Update domain
- Delete domain
- Create API key
- List API keys
- Delete API key
- Create audience
- Get audience
- List audiences
- Delete audience
- Create contact
- Get contact
- List contacts
- Update contact
- Delete contact
- Create broadcast
- Get broadcast
- List broadcasts
- Update broadcast
- Send broadcast
- Delete broadcast
- Create webhook
- Get webhook
- List webhooks
- Update webhook
- Delete webhook
let attachment = EmailAttachment(
content: "base64_encoded_content",
filename: "invoice.pdf"
)
let email = ResendEmail(
from: "billing@yourdomain.com",
to: ["customer@example.com"],
subject: "Your Invoice",
html: "<p>Please find your invoice attached.</p>",
attachments: [attachment]
)
let response = try await resend.email.send(email: email)let domain = try await resend.domains.create(
name: "yourdomain.com",
region: "us-east-1",
customReturnPath: nil
)
let verified = try await resend.domains.verify(id: domain.id)
let updated = try await resend.domains.update(
id: domain.id, clickTracking: true, openTracking: true, tls: "enforced"
)for try await contact in resend.contacts.listAll(audienceId: audienceId, limit: 50) {
print("\(contact.email): \(contact.firstName ?? "") \(contact.lastName ?? "")")
}let broadcast = try await resend.broadcasts.create(
audienceId: "audience_id",
from: "newsletter@yourdomain.com",
subject: "Monthly Newsletter",
html: "<p>Check out our latest updates!</p>",
name: "January Newsletter"
)
let sent = try await resend.broadcasts.send(id: broadcast.id, scheduledAt: nil)class MyHTTPClient: HTTPClientProtocol {
func execute(_ request: HTTPRequest) async throws -> HTTPResponse {
// Custom implementation
}
}
let resend = ResendClient(
apiKey: "re_your_api_key",
httpClient: MyHTTPClient()
)| Platform | Minimum Version |
|---|---|
| iOS | 16+ |
| macOS | 13+ |
| tvOS | 16+ |
| watchOS | 9+ |
| Mac Catalyst | 16+ |
| visionOS | 1+ |
| Linux | Swift 6.0+ |
- Swift 6.0+
- For Vapor integration: Vapor 4.66.1+
This project uses SwiftLint to enforce code style.
# Install SwiftLint
brew install swiftlint
# Run lint
swiftlint lint
# Pre-commit hook (auto-installed)
# Runs SwiftLint on staged files before each commit
git config core.hooksPath .githooksFull API documentation is available using DocC:
swift package generate-documentationAll API methods throw errors that conform to Swift's Error protocol. The SDK provides ResendRetrieveError for API errors:
do {
let response = try await resend.email.send(email: email)
} catch let error as ResendRetrieveError {
print("API Error [\(error.statusCode)]: \(error.message)")
} catch {
print("Unexpected error: \(error)")
}Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License — see the LICENSE file for details.
Built with ❤️ for the Swift community.