Swift implementation of RFC 8058: Signaling One-Click Functionality for List Email Headers
This package provides a Swift implementation of the one-click unsubscribe mechanism defined in RFC 8058. RFC 8058 extends RFC 2369 list headers with enhanced security and user experience, allowing email clients to offer a single-click unsubscribe button.
- ✅ One-click unsubscribe implementation
- ✅ HTTPS-only security enforcement
- ✅ Opaque token support for security
- ✅ Constant-time token validation (prevents timing attacks)
- ✅ RFC-compliant header rendering
- ✅ IRI support via RFC 3987
- ✅ Foundation
URLcompatibility - ✅ Swift 6 strict concurrency support
- ✅ Full
Sendableconformance
dependencies: [
.package(url: "https://github.com/swift-standards/swift-rfc-8058", from: "0.1.0")
]import RFC_8058
import RFC_3987
import CryptoKit
// Generate cryptographically secure token
func generateSecureToken(subscriber: String, list: String, secret: String) -> String {
let data = "\(subscriber):\(list)".data(using: .utf8)!
let key = SymmetricKey(data: secret.data(using: .utf8)!)
let hmac = HMAC<SHA256>.authenticationCode(for: data, using: key)
return Data(hmac).base64EncodedString()
.replacing("+", with: "-")
.replacing("/", with: "_")
.replacing("=", with: "")
}
let token = generateSecureToken(
subscriber: "user@example.com",
list: "newsletter",
secret: "your-secret-key"
)
// Create one-click unsubscribe
let oneClick = try RFC_8058.OneClick.Unsubscribe(
baseURL: try RFC_3987.IRI("https://example.com/unsubscribe"),
opaqueToken: token
)let headers = [String: String](oneClickUnsubscribe: oneClick)
// [
// "List-Unsubscribe": "<https://example.com/unsubscribe/TOKEN>",
// "List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
// ]
// Include these headers in your email// In your HTTP POST handler
func handleUnsubscribe(request: Request) async throws -> Response {
let token = request.parameters.get("token")!
// Retrieve stored oneClick for this token
let oneClick = try await getOneClickUnsubscribe(token: token)
// Validate token (constant-time comparison)
guard oneClick.validate(token: token) else {
throw Abort(.unauthorized, reason: "Invalid token")
}
// Process unsubscription
try await unsubscribe(token: token)
return Response(status: .ok)
}// Foundation URLs work seamlessly via IRI.Representable
let oneClick = try RFC_8058.OneClick.Unsubscribe(
baseURL: URL(string: "https://example.com/unsubscribe")!,
opaqueToken: secureToken
)This implementation follows RFC 8058 precisely:
✅ HTTPS Required (RFC 8058 Section 3.1)
The message MUST have a List-Unsubscribe header field containing one or more HTTPS URIs.
✅ Opaque Tokens (RFC 8058 Section 3.2)
The URI SHOULD include an opaque identifier or another hard-to-forge component.
✅ No Context Required (RFC 8058 Section 3.1)
The POST request MUST NOT include cookies, HTTP authorization, or any other context information.
✅ Constant-Time Validation
- Prevents timing attacks that could be used to guess valid tokens
- Email includes
List-UnsubscribeandList-Unsubscribe-Postheaders - Email client presents unsubscribe button to user
- User clicks button
- Client performs HTTP POST with body:
List-Unsubscribe=One-Click - Server validates token and processes unsubscription
Per RFC 8058 Section 3.1:
POST /unsubscribe/TOKEN HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
List-Unsubscribe=One-ClickUse cryptographically secure methods:
import CryptoKit
// HMAC-based token (recommended)
let key = SymmetricKey(size: .bits256)
let data = "\(subscriber):\(list):\(timestamp)".data(using: .utf8)!
let hmac = HMAC<SHA256>.authenticationCode(for: data, using: key)
let token = Data(hmac).base64EncodedString()
.replacing("+", with: "-") // URL-safe
.replacing("/", with: "_") // URL-safe
.replacing("=", with: "") // Remove paddingAlways use the provided validate(token:) method:
// ✅ Good: Constant-time comparison
if oneClick.validate(token: requestToken) {
// Process unsubscription
}
// ❌ Bad: String comparison vulnerable to timing attacks
if oneClick.opaqueToken == requestToken { // Don't do this!
// Process unsubscription
}- Time-limit tokens: Include timestamp in token generation and reject expired tokens
- One-time use: Mark tokens as used after successful unsubscription
- Rate limiting: Limit unsubscribe requests per IP/token
- Logging: Log all unsubscribe attempts for security monitoring
One-click unsubscribe with security features:
public struct Unsubscribe {
public let httpsURI: RFC_3987.IRI // HTTPS URI with token
public let opaqueToken: String // Secure token
public func toEmailHeaders() -> [String: String]
public func validate(token: String) -> Bool // Constant-time
}RFC 8058 extends RFC 2369. You can use both together:
import RFC_2369
import RFC_8058
// RFC 2369: Traditional list headers
let listHeaders = try RFC_2369.List.Header(
help: try RFC_3987.IRI("https://example.com/help"),
subscribe: [try RFC_3987.IRI("https://example.com/subscribe")]
)
// RFC 8058: Enhanced one-click unsubscribe
let oneClick = try RFC_8058.OneClick.Unsubscribe(
baseURL: try RFC_3987.IRI("https://example.com/unsubscribe"),
opaqueToken: secureToken
)
// Combine headers
var emailHeaders = [String: String](listHeader: listHeaders)
emailHeaders.merge([String: String](oneClickUnsubscribe: oneClick)) { _, new in new }
// Result: Complete RFC 2369 + RFC 8058 compliance- Swift 6.0+
- macOS 14+, iOS 17+, tvOS 17+, watchOS 10+
- RFC 2369 - The Use of URLs as Meta-Syntax for Core Mail List Commands
- RFC 3987 - Internationalized Resource Identifiers (IRIs)
- RFC 6068 - The 'mailto' URI Scheme
- RFC 8058 - Signaling One-Click Functionality for List Email Headers
- swift-rfc-2369 - List email headers (foundation for RFC 8058)
- swift-rfc-3987 - IRI implementation
- swift-rfc-6068 - The 'mailto' URI Scheme (fallback unsubscribe)
- Gmail, Apple Mail, and others prioritize emails with RFC 8058 headers
- Improves inbox placement and deliverability
- Provides better user experience
- Reduces false spam reports
- Improves sender reputation
- Demonstrates compliance with best practices
- One-click unsubscribe (no website visit required)
- Works directly from email client UI
- Faster, more convenient
Licensed under Apache 2.0.
Contributions welcome! Please ensure:
- All tests pass
- Code follows existing style
- RFC 8058 compliance maintained
- Security best practices followed