Repository: vapor-community/sendgrid-kit
Pull request #26: Email validation api
Author: login: thoven87
Created: 2025-05-14T22:11:22Z | Merged: 2025-05-17T14:24:19Z

* Updated HTTP Client to 1.26.0
* [More information on the email validation API](https://www.twilio.com/docs/sendgrid/ui/managing-contacts/email-address-validation)


Diff:
diff --git a/Package.swift b/Package.swift
index 9094c6a..b6165f3 100644
--- a/Package.swift
+++ b/Package.swift
@@ -10,7 +10,7 @@ let package = Package(
         .library(name: "SendGridKit", targets: ["SendGridKit"])
     ],
     dependencies: [
-        .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.24.0")
+        .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.26.0")
     ],
     targets: [
         .target(
diff --git a/README.md b/README.md
index 8719c41..171596a 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,92 @@ let email = SendGridEmail(...)
 try await sendGridClient.send(email: email)
 ```
 
+### Email Validation API
+
+SendGridKit supports SendGrid's Email Validation API, which helps verify the validity of email addresses before sending:
+
+```swift
+import SendGridKit
+
+let sendGridClient = SendGridEmailValidationClient(httpClient: .shared, apiKey: "YOUR_API_KEY")
+
+// Create a validation request
+let validationRequest = EmailValidationRequest(email: "example@email.com")
+
+// Validate the email
+do {
+    let validationResponse = try await sendGridClient.validateEmail(validationRequest)
+    
+    // Check if the email is valid
+    if validationResponse.result.verdict == .valid {
+        print("Email is valid with score: \(validationResponse.result.score)")
+    } else {
+        print("Email is invalid: \(validationResponse.result.reason ?? "Unknown reason")")
+    }
+    
+    // Access detailed validation information
+    if validationResponse.checks.disposable {
+        print("Warning: This is a disposable email address")
+    }
+    
+    if validationResponse.checks.roleAccount {
+        print("Note: This is a role-based email address")
+    }
+} catch {
+    print("Validation failed: \(error)")
+}
+```
+
+#### Bulk Email Validation API
+
+For validating multiple email addresses at once, SendGridKit provides access to SendGrid's Bulk Email Validation API. This requires uploading a CSV file with email addresses:
+
+```swift
+import SendGridKit
+import Foundation
+
+let sendGridClient = SendGridEmailValidationClient(httpClient: .shared, apiKey: "YOUR_API_KEY")
+
+do {
+    // Step 1: Create a CSV file with email addresses
+    let csvContent = """
+    emails
+    user1@example.com
+    user2@example.com
+    user3@example.com
+    """
+    guard let csvData = csvContent.data(using: .utf8) else {
+        throw SomeError.invalidCSV
+    }
+
+    // Step 2: Upload the CSV file
+    let (uploadSuccess, jobId) = try await sendGridClient.uploadBulkValidationFile(
+        fileData: csvData,
+        fileType: .csv
+    )
+    
+    if !uploadSuccess {
+        throw SomeError.uploadError
+    }
+    
+    // Step 4: Check job status (poll until completed)
+    var jobStatusResponse = try await sendGridClient.checkBulkValidationStatus(jobId: jobId)
+    var jobStatus = jobStatusResponse.result
+    
+    while jobStatus.status != .done {
+        print("Job \(jobStatus.id) status: \(jobStatus.status) - \(jobStatus.segmentsProcessed)/\(jobStatus.segments) segments processed")
+        
+        // Wait before checking again (implement your own backoff strategy)
+        try? await Task.sleep(nanoseconds: 5_000_000_000) // 5 seconds
+        
+        jobStatusResponse = try await sendGridClient.checkBulkValidationStatus(jobId: jobId)
+        jobStatus = jobStatusResponse..result
+    }
+} catch {
+    print("Bulk validation failed: \(error)")
+}
+```
+
 ### Error handling
 
 If the request to the API failed for any reason a `SendGridError` is thrown, which has an `errors` property that contains an array of errors returned by the API.
diff --git a/Sources/SendGridKit/Models/BulkEmailValidation.swift b/Sources/SendGridKit/Models/BulkEmailValidation.swift
new file mode 100644
index 0000000..075f200
--- /dev/null
+++ b/Sources/SendGridKit/Models/BulkEmailValidation.swift
@@ -0,0 +1,151 @@
+import struct Foundation.Date
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkEmailValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkEmailValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+struct BulkEmailValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    let jobId: String
+
+    /// The URI where the file should be uploaded.
+    let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        let header: String
+
+        /// The value of the header.
+        let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct BulkEmailValidationJob: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let response: Response
+
+    public struct Response: Decodable, Sendable {
+        public let value: Value
+
+        public struct Value: Decodable, Sendable {
+            public let result: Result
+
+            /// The status result of a bulk email validation job.
+            public struct Result: Decodable, Sendable {
+                /// The unique identifier for the validation job.
+                public let id: String
+
+                /// Status of the validation job (e.g., "Queued", "Processing", "Completed").
+                public let status: BulkEmailValidationJobStatus
+
+                /// The total number of segments in the Bulk Email Address Validation Job.
+                /// There are 1,500 email addresses per segment. The value is 0 until the Job status is Processing.
+                public let segments: Int?
+
+                /// The number of segments processed at the time of the request.
+                /// 100 segments process in parallel at a time.
+                public let segmentsProcessed: Int?
+
+                /// Boolean indicating whether the results CSV file is available for download.
+                public let isDownloadAvailable: Bool?
+
+                /// The ISO8601 timestamp when the Job was created.
+                /// This is the time at which the upload request was sent to the upload_uri.
+                public let startedAt: Date
+
+                /// The ISO8601 timestamp when the Job was finished.
+                public let finishedAt: Date?
+
+                /// Array containing error messages related to the Bulk Email Address Validation Job.
+                /// Array is empty if no errors ocurred.
+                public let errors: [BulkValidationError]?
+
+                public struct BulkValidationError: Decodable, Sendable {
+                    /// Description of the error encountered during execution of the Bulk Email Address Validation Job.
+                    public let message: String
+                }
+
+                /// CodingKeys for mapping JSON fields to struct properties
+                private enum CodingKeys: String, CodingKey {
+                    case id, status, segments, errors
+                    case segmentsProcessed = "segments_processed"
+                    case isDownloadAvailable = "is_download_available"
+                    case startedAt = "started_at"
+                    case finishedAt = "finished_at"
+                }
+            }
+        }
+    }
+}
+
+/// BulkEmailValidationJobStatus
+public enum BulkEmailValidationJobStatus: String, Codable, Sendable {
+    case initiated = "Initiated"
+    case queued = "Queued"
+    case ready = "Ready"
+    case processing = "Processing"
+    case done = "Done"
+    case error = "Error"
+}
+
+/// The result of a bulk email validation operation.
+public struct BulkEmailValidationJobsResponse: Codable, Sendable {
+    /// The response structure containing the validation results.
+    public let result: [Result]
+
+    /// Nested response structure containing the results value.
+    public struct Result: Codable, Sendable {
+        /// The unique ID of the Bulk Email Address Validation Job.
+        public let id: String
+
+        /// The status of the Bulk Email Address Validation Job.
+        public let status: BulkEmailValidationJobStatus
+
+        /// The ISO8601 timestamp when the Job was created. This is the time at which the upload request was sent to the upload_uri.
+        public let startedAt: Date
+
+        /// The ISO8601 timestamp when the Job was finished.
+        public let finishedAt: Date?
+
+        private enum CodingKeys: String, CodingKey {
+            case id, status
+            case startedAt = "started_at"
+            case finishedAt = "finished_at"
+        }
+    }
+}
diff --git a/Sources/SendGridKit/Models/RealtimeEmailValidation.swift b/Sources/SendGridKit/Models/RealtimeEmailValidation.swift
new file mode 100644
index 0000000..117e29c
--- /dev/null
+++ b/Sources/SendGridKit/Models/RealtimeEmailValidation.swift
@@ -0,0 +1,129 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: Result
+
+    /// The overall result of email validation.
+    public struct Result: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String?
+
+        /// Granular checks for email address validity.
+        public let checks: Checks
+
+        /// The IP address associated with this email.
+        public let ipAddress: String?
+
+        /// The source of the validation, as per the API request.
+        public let source: String?
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+
+        /// Various checks performed during email validation.
+        public struct Checks: Decodable, Sendable {
+
+            /// Checks on the domain portion of the email address.
+            public let domain: Domain
+
+            /// Checks on the local part of the email address.
+            public let localPart: LocalPartInfo
+
+            /// Additional checks on the email address.
+            public let additional: AdditionalInfo
+
+            public struct AdditionalInfo: Decodable, Sendable {
+                /// Whether email sent to this address from your account has bounced.
+                public let hasKnownBounces: Bool
+                /// Whether our model predicts that the email address might bounce.
+                public let hasSuspectedBounces: Bool
+
+                enum CodingKeys: String, CodingKey {
+                    case hasKnownBounces = "has_known_bounces"
+                    case hasSuspectedBounces = "has_suspected_bounces"
+                }
+            }
+
+            public struct LocalPartInfo: Decodable, Sendable {
+                /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+                public let isSuspectedRoleAddress: Bool
+
+                enum CodingKeys: String, CodingKey {
+                    case isSuspectedRoleAddress = "is_suspected_role_address"
+                }
+            }
+
+            public struct Domain: Decodable, Sendable {
+                /// Whether the email address syntax is valid.
+                public let hasValidAddressSyntax: Bool
+                /// Whether the email has appropriate DNS records to deliver a message.
+                public let hasMXOrARecord: Bool
+                /// Whether the domain appears to be from a disposable email address service.
+                public let isSuspectedDisposableAddress: Bool
+
+                enum CodingKeys: String, CodingKey {
+                    case hasValidAddressSyntax = "has_valid_address_syntax"
+                    case hasMXOrARecord = "has_mx_or_a_record"
+                    case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+                }
+            }
+
+            /// CodingKeys for mapping JSON fields to struct properties
+            private enum CodingKeys: String, CodingKey {
+                case domain
+                case localPart = "local_part"
+                case additional
+            }
+        }
+
+        private enum CodingKeys: String, CodingKey {
+            case email
+            case verdict
+            case score
+            case local
+            case host
+            case suggestion
+            case checks
+            case source
+            case ipAddress = "ip_address"
+        }
+    }
+}
diff --git a/Sources/SendGridKit/SendGridClient.swift b/Sources/SendGridKit/SendGridClient.swift
index 5e4f599..eec7928 100644
--- a/Sources/SendGridKit/SendGridClient.swift
+++ b/Sources/SendGridKit/SendGridClient.swift
@@ -6,7 +6,7 @@ import NIOHTTP1
 
 /// A client for sending emails using the SendGrid API.
 public struct SendGridClient: Sendable {
-    private let apiURL: String
+    private let baseURL: String
     private let httpClient: HTTPClient
     private let apiKey: String
 
@@ -31,7 +31,7 @@ public struct SendGridClient: Sendable {
     public init(httpClient: HTTPClient, apiKey: String, forEU: Bool = false) {
         self.httpClient = httpClient
         self.apiKey = apiKey
-        self.apiURL = forEU ? "https://api.eu.sendgrid.com/v3/mail/send" : "https://api.sendgrid.com/v3/mail/send"
+        self.baseURL = forEU ? "https://api.eu.sendgrid.com/v3" : "https://api.sendgrid.com/v3"
     }
 
     /// Send an email using the SendGrid API.
@@ -41,8 +41,9 @@ public struct SendGridClient: Sendable {
         var headers = HTTPHeaders()
         headers.add(name: "Authorization", value: "Bearer \(self.apiKey)")
         headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
 
-        var request = HTTPClientRequest(url: self.apiURL)
+        var request = HTTPClientRequest(url: "\(self.baseURL)/mail/send")
         request.method = .POST
         request.headers = headers
         request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(email))
diff --git a/Sources/SendGridKit/SendGridEmailValidationClient.swift b/Sources/SendGridKit/SendGridEmailValidationClient.swift
new file mode 100644
index 0000000..0b4c3ee
--- /dev/null
+++ b/Sources/SendGridKit/SendGridEmailValidationClient.swift
@@ -0,0 +1,181 @@
+import AsyncHTTPClient
+import Foundation
+import NIO
+import NIOFoundationCompat
+import NIOHTTP1
+
+/// A client for validating emails using the SendGrid API.
+public struct SendGridEmailValidationClient: Sendable {
+    private let baseURL: String
+    private let httpClient: HTTPClient
+    private let apiKey: String
+
+    private let encoder: JSONEncoder = {
+        let encoder = JSONEncoder()
+        encoder.dateEncodingStrategy = .secondsSince1970
+        return encoder
+    }()
+
+    private let decoder: JSONDecoder = {
+        let decoder = JSONDecoder()
+        decoder.dateDecodingStrategy = .secondsSince1970
+        return decoder
+    }()
+
+    /// Initialize a new `SendGridClientEmailValidationClient`
+    ///
+    /// - Parameters:
+    ///   - httpClient: The `HTTPClient` to use for sending requests
+    ///   - apiKey: The SendGrid API key
+    ///   - forEU: Whether to use the API endpoint for global users and subusers or for EU regional subusers
+    public init(httpClient: HTTPClient, apiKey: String, forEU: Bool = false) {
+        self.httpClient = httpClient
+        self.apiKey = apiKey
+        self.baseURL = forEU ? "https://api.eu.sendgrid.com/v3" : "https://api.sendgrid.com/v3"
+    }
+
+    /// Validate an email address using the SendGrid Email Validation API.
+    ///
+    /// - Parameter validationRequest: The ``EmailValidationRequest`` containing the email to validate.
+    /// - Returns: An ``EmailValidationResponse`` with validation details.
+    public func validateEmail(_ validationRequest: EmailValidationRequest) async throws -> EmailValidationResponse {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email")
+        request.method = .POST
+        request.headers = headers
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(validationRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(EmailValidationResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    /// Request an upload URL for bulk email validation using the SendGrid Email Validation API.
+    ///
+    /// - Parameter fileType: The type of file to be uploaded (CSV or ZIP).
+    /// - Returns: A ``BulkValidationUploadURLResponse`` with the upload URL and details.
+    func getBulkValidationUploadURL(fileType: BulkEmailValidationUploadURLRequest.FileType) async throws
+        -> BulkEmailValidationUploadURLResponse
+    {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email/jobs")
+        request.method = .POST
+        request.headers = headers
+
+        let uploadRequest = BulkEmailValidationUploadURLRequest(fileType: fileType)
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(uploadRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(BulkEmailValidationUploadURLResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    public typealias UploadBulkValidationFileResult = (status: Bool, jobID: String)
+
+    /// Upload a file to the provided URL for bulk email validation.
+    ///
+    /// - Parameters:
+    ///   - fileData: The data of the file to upload (CSV or ZIP).
+    ///   - uploadResponse: The ``BulkValidationUploadURLResponse`` containing upload details.
+    /// - Returns: `true` if the upload was successful, and the job ID.
+    public func uploadBulkValidationFile(
+        fileData: Data,
+        fileType: BulkEmailValidationUploadURLRequest.FileType
+    ) async throws -> UploadBulkValidationFileResult {
+        // Request upload file URL
+        let uploadResponse = try await getBulkValidationUploadURL(fileType: fileType)
+
+        // Create a request to the upload URL
+        var request = HTTPClientRequest(url: uploadResponse.uploadUri)
+        request.method = .PUT  // Default to PUT method for S3 uploads
+
+        // Set the headers from the upload response
+        var headers = HTTPHeaders()
+        for headerData in uploadResponse.uploadHeaders {
+            headers.add(name: headerData.header, value: headerData.value)
+        }
+        request.headers = headers
+
+        // Add the file data as the request body
+        request.body = .bytes(fileData)
+
+        // Execute the request
+        let response = try await self.httpClient.execute(request, timeout: .seconds(180))
+
+        // Check if the upload was successful and return the job ID
+        return ((200...299).contains(response.status.code), uploadResponse.jobId)
+    }
+
+    /// Check the status of a bulk email validation job.
+    ///
+    /// - Parameter jobId: The ID of the bulk validation job.
+    /// - Returns: A ``BulkEmailValidationJob.Response.Value.Result`` with information about the job status.
+    public func checkBulkValidationStatus(jobId: String) async throws -> BulkEmailValidationJob.Response.Value.Result {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email/jobs/\(jobId)")
+        request.method = .GET
+        request.headers = headers
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            let data = try self.decoder.decode(BulkEmailValidationJob.self, from: body)
+            return data.response.value.result
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    /// Get the results of a completed bulk email validation job.
+    ///
+    /// - Parameter jobId: The ID of the completed bulk validation job.
+    /// - Returns: A ``BulkEmailValidationJobResponse`` containing the validation results for each email.
+    public func getBulkEmailValidationJobs() async throws -> BulkEmailValidationJobsResponse {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email/jobs")
+        request.method = .GET
+        request.headers = headers
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(60))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(BulkEmailValidationJobsResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+}
diff --git a/Tests/SendGridKitTests/SendGridKitTests.swift b/Tests/SendGridKitTests/SendGridKitTests.swift
index 6982a0a..dc97438 100644
--- a/Tests/SendGridKitTests/SendGridKitTests.swift
+++ b/Tests/SendGridKitTests/SendGridKitTests.swift
@@ -1,4 +1,5 @@
 import AsyncHTTPClient
+import Foundation
 import SendGridKit
 import Testing
 
@@ -8,9 +9,13 @@ struct SendGridKitTests {
     // TODO: Replace with `false` when you have a valid API key
     let credentialsAreInvalid = true
 
+    let emailValidationClient: SendGridEmailValidationClient
+
     init() {
         // TODO: Replace with a valid API key to test
         client = SendGridClient(httpClient: HTTPClient.shared, apiKey: "YOUR-API-KEY")
+        emailValidationClient = SendGridEmailValidationClient(
+            httpClient: .shared, apiKey: "YOUR-EMAIL-VALIDATION-API-KEY")
     }
 
     @Test("Send Email")
@@ -102,4 +107,177 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest)
+
+            // Verify response properties exist
+            #expect((0.0...1.0).contains(response.result.score))
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                #expect(response.result.ipAddress != nil)
+            } else {
+                #expect(!response.result.checks.domain.hasMXOrARecord)
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(!response.result.isEmpty)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status")
+    func getBulkValidationJobStatus() async throws {
+        try await withKnownIssue {
+            let jobsResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: "12345")
+            let errors = jobsResponse.errors
+            #expect(errors?.isEmpty == true)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Upload CSV File")
+    func uploadCSVFile() async throws {
+        let csvContent = """
+            email
+            test1@example.com
+            test2@example.com
+            test3@example.com
+            """
+        let csvData = csvContent.data(using: .utf8)!
+
+        try await withKnownIssue {
+            let (success, jobId) = try await emailValidationClient.uploadBulkValidationFile(
+                fileData: csvData,
+                fileType: .csv
+            )
+            #expect(success)
+            #expect(!jobId.isEmpty)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Decode validate email response")
+    func decodeValidateEmailResponse() async throws {
+        let response = """
+            {
+              "result": {
+                "email": "cedric@fogowl.com",
+                "verdict": "Valid",
+                "score": 0.85021,
+                "local": "cedric",
+                "host": "fogowl.com",
+                "checks": {
+                  "domain": {
+                    "has_valid_address_syntax": true,
+                    "has_mx_or_a_record": true,
+                    "is_suspected_disposable_address": false
+                  },
+                  "local_part": {
+                    "is_suspected_role_address": false
+                  },
+                  "additional": {
+                    "has_known_bounces": false,
+                    "has_suspected_bounces": false
+                  }
+                },
+                "ip_address": "192.168.1.1"
+              }
+            }
+            """
+        let responseData = try JSONDecoder().decode(EmailValidationResponse.self, from: response.data(using: .utf8)!)
+        #expect(responseData.result.score == 0.85021)
+    }
+
+    @Test("Decode Get bulk Email Address")
+    func decodeGetBulkEmailAddress() async throws {
+        let response = """
+            {
+              "result": [
+                {
+                  "id": "01HV9ZZQAFEXW18KFEPTB9YD5E",
+                  "status": "Queued",
+                  "started_at": 1712954639,
+                  "finished_at": 0
+                }
+              ]
+            }
+            """
+        let responseData = try JSONDecoder().decode(BulkEmailValidationJobsResponse.self, from: response.data(using: .utf8)!)
+        #expect(responseData.result.count == 1)
+    }
+
+    @Test("Decode Get validation Email Address Job")
+    func decodeGetEmailAddressJob() async throws {
+        let response = """
+            {
+              "response": {
+                "value": {
+                  "result": {
+                    "id": "01HV9ZZQAFEXW18KFEPTB9YD5E",
+                    "status": "Queued",
+                    "segments": 0,
+                    "segments_processed": 0,
+                    "is_download_available": false,
+                    "started_at": 1712954639,
+                    "finished_at": 0,
+                    "errors": []
+                  }
+                }
+              }
+            }
+            """
+        let responseData = try JSONDecoder().decode(BulkEmailValidationJob.self, from: response.data(using: .utf8)!)
+        #expect(responseData.response.value.result.id == "01HV9ZZQAFEXW18KFEPTB9YD5E")
+    }
+
+    @Test("Bulk Email Validation")
+    func bulkValidateEmail() async throws {
+        // Create a simple CSV with a few test emails
+        let csvContent = """
+            email
+            test1@example.com
+            test2@example.com
+            test3@example.com
+            """
+        let csvData = csvContent.data(using: .utf8)!
+
+        try await withKnownIssue {
+
+            // Step 1: Upload the CSV file to start the validation job (simulated in this test)
+            let (uploadSuccess, jobId) = try await emailValidationClient.uploadBulkValidationFile(
+                fileData: csvData,
+                fileType: .csv
+            )
+
+            #expect(uploadSuccess)
+
+            // Step 2: Check job status
+            let jobStatusResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: jobId)
+
+            // Verify job status properties
+            let result = jobStatusResponse
+            #expect(!result.id.isEmpty)
+            #expect(result.status == .processing)
+            #expect(result.segmentsProcessed != nil)
+            #expect(result.segments != nil)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
 }


Reviews:
user: login: fpseverino
body: Great job, I am very excited to add this API to the library!
I left some comments, thank you very much for the PR!
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-15T00:43:30Z
user: login: thoven87
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T00:51:15Z
user: login: thoven87
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T00:51:37Z
user: login: thoven87
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T00:51:55Z
user: login: thoven87
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T00:53:11Z
user: login: thoven87
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T00:53:33Z
user: login: fpseverino
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T01:07:44Z
user: login: fpseverino
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T01:08:06Z
user: login: thoven87
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T01:10:55Z
user: login: thoven87
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T01:36:20Z
user: login: fpseverino
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T12:45:58Z
user: login: fpseverino
body: A few more comments. Also, for all response types we have to make all properties optional, as we can't assume they will all be present when decoding them.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-15T13:30:15Z
user: login: fpseverino
body: > How do we update `uploadBulkValidationFile` for better code coverage? Or are you ok with the current drop in code coverage? 

For now, it is not important. The more you can raise it the better
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T13:31:28Z
user: login: thoven87
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T10:36:41Z
user: login: fpseverino
body: Looking much better, we're almost there! Thanks again
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-16T15:01:24Z
user: login: thoven87
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:20:47Z
user: login: thoven87
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:21:36Z
user: login: fpseverino
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T15:24:33Z
user: login: fpseverino
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T15:25:59Z
user: login: fpseverino
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T15:26:54Z
user: login: thoven87
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:27:05Z
user: login: fpseverino
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T15:34:22Z
user: login: fpseverino
body: OK, let's merge it!

A PR of mine will follow, before the official release, in which I will update the DocC documentation and other such things.
I warn you that I may change the names of some methods or structures.

Thank you once again!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-16T17:18:43Z

Review comments:
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: These two should be part of `EmailValidationResponse`, as per the [API docs](https://www.twilio.com/docs/sendgrid/api-reference/email-address-validation/validate-an-email#responses)
created_at: 2025-05-14T23:25:48Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
+    /// Whether the SMTP check was successful.
+    public let success: Bool
+
+    /// The reason for the SMTP check result.
+    public let reason: String?
+}
+
+/// Additional information about the email validation.
+public struct AdditionalInfo: Decodable, Sendable {
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: Where is this struct used?
created_at: 2025-05-14T23:36:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: What is this struct for?
created_at: 2025-05-14T23:40:03Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
+    /// Whether the SMTP check was successful.
+    public let success: Bool
+
+    /// The reason for the SMTP check result.
+    public let reason: String?
+}
+
+/// Additional information about the email validation.
+public struct AdditionalInfo: Decodable, Sendable {
+    /// The IP address used for validation.
+    public let ipAddress: String?
+
+    /// The source of the validation request.
+    public let source: String?
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case ipAddress = "ip_address"
+        case source
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+public struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let jobId: String
+
+    /// The URI where the file should be uploaded.
+    public let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    public let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    public struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        public let header: String
+
+        /// The value of the header.
+        public let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct BulkValidationJobResponse: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let result: BulkValidationJobResult
+}
+
+/// The status result of a bulk email validation job.
+public struct BulkValidationJobResult: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let id: String
+
+    /// Status of the validation job (e.g., "Queued", "Processing", "Completed").
+    public let status: BuildEmailValidationStatus
+
+    /// The total number of segments in the Bulk Email Address Validation Job.
+    /// There are 1,500 email addresses per segment. The value is 0 until the Job status is Processing.
+    public let segments: Int
+
+    /// The number of segments processed at the time of the request.
+    /// 100 segments process in parallel at a time.
+    public let segmentsProcessed: Int
+
+    /// Boolean indicating whether the results CSV file is available for download.
+    public let isDownloadAvailable: Bool
+
+    /// The ISO8601 timestamp when the Job was created.
+    /// This is the time at which the upload request was sent to the upload_uri.
+    public let startedAt: Double
+
+    /// The ISO8601 timestamp when the Job was finished.
+    public let finishedAt: Double
+
+    /// Array containing error messages related to the Bulk Email Address Validation Job.
+    /// Array is empty if no errors ocurred.
+    public let errors: [SendGridError.Description]
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: I think it is better to construct a small ad hoc struct here, as in reality the error returned by [this endpoint](https://www.twilio.com/docs/sendgrid/api-reference/email-address-validation/get-bulk-email-address-validation-job-by-id#responses) only contains a message, and not everything else that is part of `SendGridError.Description`
created_at: 2025-05-14T23:46:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
+    /// Whether the SMTP check was successful.
+    public let success: Bool
+
+    /// The reason for the SMTP check result.
+    public let reason: String?
+}
+
+/// Additional information about the email validation.
+public struct AdditionalInfo: Decodable, Sendable {
+    /// The IP address used for validation.
+    public let ipAddress: String?
+
+    /// The source of the validation request.
+    public let source: String?
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case ipAddress = "ip_address"
+        case source
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+public struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let jobId: String
+
+    /// The URI where the file should be uploaded.
+    public let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    public let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    public struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        public let header: String
+
+        /// The value of the header.
+        public let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct BulkValidationJobResponse: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let result: BulkValidationJobResult
+}
+
+/// The status result of a bulk email validation job.
+public struct BulkValidationJobResult: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let id: String
+
+    /// Status of the validation job (e.g., "Queued", "Processing", "Completed").
+    public let status: BuildEmailValidationStatus
+
+    /// The total number of segments in the Bulk Email Address Validation Job.
+    /// There are 1,500 email addresses per segment. The value is 0 until the Job status is Processing.
+    public let segments: Int
+
+    /// The number of segments processed at the time of the request.
+    /// 100 segments process in parallel at a time.
+    public let segmentsProcessed: Int
+
+    /// Boolean indicating whether the results CSV file is available for download.
+    public let isDownloadAvailable: Bool
+
+    /// The ISO8601 timestamp when the Job was created.
+    /// This is the time at which the upload request was sent to the upload_uri.
+    public let startedAt: Double
+
+    /// The ISO8601 timestamp when the Job was finished.
+    public let finishedAt: Double
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: It would be nice to decode them automatically and offer these properties as `Date`.
created_at: 2025-05-14T23:48:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
+    /// Whether the SMTP check was successful.
+    public let success: Bool
+
+    /// The reason for the SMTP check result.
+    public let reason: String?
+}
+
+/// Additional information about the email validation.
+public struct AdditionalInfo: Decodable, Sendable {
+    /// The IP address used for validation.
+    public let ipAddress: String?
+
+    /// The source of the validation request.
+    public let source: String?
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case ipAddress = "ip_address"
+        case source
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+public struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let jobId: String
+
+    /// The URI where the file should be uploaded.
+    public let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    public let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    public struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        public let header: String
+
+        /// The value of the header.
+        public let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct BulkValidationJobResponse: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let result: BulkValidationJobResult
+}
+
+/// The status result of a bulk email validation job.
+public struct BulkValidationJobResult: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let id: String
+
+    /// Status of the validation job (e.g., "Queued", "Processing", "Completed").
+    public let status: BuildEmailValidationStatus
+
+    /// The total number of segments in the Bulk Email Address Validation Job.
+    /// There are 1,500 email addresses per segment. The value is 0 until the Job status is Processing.
+    public let segments: Int
+
+    /// The number of segments processed at the time of the request.
+    /// 100 segments process in parallel at a time.
+    public let segmentsProcessed: Int
+
+    /// Boolean indicating whether the results CSV file is available for download.
+    public let isDownloadAvailable: Bool
+
+    /// The ISO8601 timestamp when the Job was created.
+    /// This is the time at which the upload request was sent to the upload_uri.
+    public let startedAt: Double
+
+    /// The ISO8601 timestamp when the Job was finished.
+    public let finishedAt: Double
+
+    /// Array containing error messages related to the Bulk Email Address Validation Job.
+    /// Array is empty if no errors ocurred.
+    public let errors: [SendGridError.Description]
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case id, status, segments, errors
+        case segmentsProcessed = "segments_processed"
+        case isDownloadAvailable = "is_download_available"
+        case startedAt = "started_at"
+        case finishedAt = "finished_at"
+    }
+}
+
+public enum BuildEmailValidationStatus: String, Codable, Sendable {
+    case initiated = "Initiated"
+    case queued = "Queued"
+    case ready = "Ready"
+    case processing = "Processing"
+    case done = "Done"
+    case error = "Error"
+}
+
+/// The result of a bulk email validation operation.
+public struct BulkEmailValidationJobResponse: Codable, Sendable {
+    /// The response structure containing the validation results.
+    public let result: [BulkValidationJobResponse]
+
+    /// Nested response structure containing the results value.
+    public struct BulkValidationJobResponse: Codable, Sendable {
+        /// The unique ID of the Bulk Email Address Validation Job.
+        public let id: String
+
+        /// The status of the Bulk Email Address Validation Job.
+        public let status: BuildEmailValidationStatus
+
+        /// The ISO8601 timestamp when the Job was created. This is the time at which the upload request was sent to the upload_uri.
+        public let startedAt: Double
+
+        /// The ISO8601 timestamp when the Job was finished.
+        public let finishedAt: Double
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: Here too, it would be nice to decode them automatically and offer these properties as `Date`.
created_at: 2025-05-14T23:49:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
+    /// Whether the SMTP check was successful.
+    public let success: Bool
+
+    /// The reason for the SMTP check result.
+    public let reason: String?
+}
+
+/// Additional information about the email validation.
+public struct AdditionalInfo: Decodable, Sendable {
+    /// The IP address used for validation.
+    public let ipAddress: String?
+
+    /// The source of the validation request.
+    public let source: String?
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case ipAddress = "ip_address"
+        case source
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+public struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let jobId: String
+
+    /// The URI where the file should be uploaded.
+    public let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    public let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    public struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        public let header: String
+
+        /// The value of the header.
+        public let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct BulkValidationJobResponse: Decodable, Sendable {
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: We have to find a better name for this and `BulkEmailValidationJobResponse` to better distinguish them.
created_at: 2025-05-14T23:52:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
+    /// Whether the SMTP check was successful.
+    public let success: Bool
+
+    /// The reason for the SMTP check result.
+    public let reason: String?
+}
+
+/// Additional information about the email validation.
+public struct AdditionalInfo: Decodable, Sendable {
+    /// The IP address used for validation.
+    public let ipAddress: String?
+
+    /// The source of the validation request.
+    public let source: String?
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case ipAddress = "ip_address"
+        case source
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+public struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let jobId: String
+
+    /// The URI where the file should be uploaded.
+    public let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    public let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    public struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        public let header: String
+
+        /// The value of the header.
+        public let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct BulkValidationJobResponse: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let result: BulkValidationJobResult
+}
+
+/// The status result of a bulk email validation job.
+public struct BulkValidationJobResult: Decodable, Sendable {
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: Put this inside `BulkValidationJobResponse` (or whatever its name will be) and rename it simply to `Result`.
In this way, its full name will be `BulkValidationJobResponse.Result`. Much simpler.
created_at: 2025-05-14T23:55:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
+    /// Whether the SMTP check was successful.
+    public let success: Bool
+
+    /// The reason for the SMTP check result.
+    public let reason: String?
+}
+
+/// Additional information about the email validation.
+public struct AdditionalInfo: Decodable, Sendable {
+    /// The IP address used for validation.
+    public let ipAddress: String?
+
+    /// The source of the validation request.
+    public let source: String?
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case ipAddress = "ip_address"
+        case source
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+public struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let jobId: String
+
+    /// The URI where the file should be uploaded.
+    public let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    public let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    public struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        public let header: String
+
+        /// The value of the header.
+        public let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct BulkValidationJobResponse: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let result: BulkValidationJobResult
+}
+
+/// The status result of a bulk email validation job.
+public struct BulkValidationJobResult: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let id: String
+
+    /// Status of the validation job (e.g., "Queued", "Processing", "Completed").
+    public let status: BuildEmailValidationStatus
+
+    /// The total number of segments in the Bulk Email Address Validation Job.
+    /// There are 1,500 email addresses per segment. The value is 0 until the Job status is Processing.
+    public let segments: Int
+
+    /// The number of segments processed at the time of the request.
+    /// 100 segments process in parallel at a time.
+    public let segmentsProcessed: Int
+
+    /// Boolean indicating whether the results CSV file is available for download.
+    public let isDownloadAvailable: Bool
+
+    /// The ISO8601 timestamp when the Job was created.
+    /// This is the time at which the upload request was sent to the upload_uri.
+    public let startedAt: Double
+
+    /// The ISO8601 timestamp when the Job was finished.
+    public let finishedAt: Double
+
+    /// Array containing error messages related to the Bulk Email Address Validation Job.
+    /// Array is empty if no errors ocurred.
+    public let errors: [SendGridError.Description]
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case id, status, segments, errors
+        case segmentsProcessed = "segments_processed"
+        case isDownloadAvailable = "is_download_available"
+        case startedAt = "started_at"
+        case finishedAt = "finished_at"
+    }
+}
+
+public enum BuildEmailValidationStatus: String, Codable, Sendable {
+    case initiated = "Initiated"
+    case queued = "Queued"
+    case ready = "Ready"
+    case processing = "Processing"
+    case done = "Done"
+    case error = "Error"
+}
+
+/// The result of a bulk email validation operation.
+public struct BulkEmailValidationJobResponse: Codable, Sendable {
+    /// The response structure containing the validation results.
+    public let result: [BulkValidationJobResponse]
+
+    /// Nested response structure containing the results value.
+    public struct BulkValidationJobResponse: Codable, Sendable {
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: Same here, much simpler
```suggestion
    public let result: [Result]

    /// Nested response structure containing the results value.
    public struct Result: Codable, Sendable {
```
created_at: 2025-05-14T23:57:19Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
+    /// Whether the SMTP check was successful.
+    public let success: Bool
+
+    /// The reason for the SMTP check result.
+    public let reason: String?
+}
+
+/// Additional information about the email validation.
+public struct AdditionalInfo: Decodable, Sendable {
+    /// The IP address used for validation.
+    public let ipAddress: String?
+
+    /// The source of the validation request.
+    public let source: String?
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case ipAddress = "ip_address"
+        case source
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+public struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let jobId: String
+
+    /// The URI where the file should be uploaded.
+    public let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    public let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    public struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        public let header: String
+
+        /// The value of the header.
+        public let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct BulkValidationJobResponse: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let result: BulkValidationJobResult
+}
+
+/// The status result of a bulk email validation job.
+public struct BulkValidationJobResult: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let id: String
+
+    /// Status of the validation job (e.g., "Queued", "Processing", "Completed").
+    public let status: BuildEmailValidationStatus
+
+    /// The total number of segments in the Bulk Email Address Validation Job.
+    /// There are 1,500 email addresses per segment. The value is 0 until the Job status is Processing.
+    public let segments: Int
+
+    /// The number of segments processed at the time of the request.
+    /// 100 segments process in parallel at a time.
+    public let segmentsProcessed: Int
+
+    /// Boolean indicating whether the results CSV file is available for download.
+    public let isDownloadAvailable: Bool
+
+    /// The ISO8601 timestamp when the Job was created.
+    /// This is the time at which the upload request was sent to the upload_uri.
+    public let startedAt: Double
+
+    /// The ISO8601 timestamp when the Job was finished.
+    public let finishedAt: Double
+
+    /// Array containing error messages related to the Bulk Email Address Validation Job.
+    /// Array is empty if no errors ocurred.
+    public let errors: [SendGridError.Description]
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case id, status, segments, errors
+        case segmentsProcessed = "segments_processed"
+        case isDownloadAvailable = "is_download_available"
+        case startedAt = "started_at"
+        case finishedAt = "finished_at"
+    }
+}
+
+public enum BuildEmailValidationStatus: String, Codable, Sendable {
+    case initiated = "Initiated"
+    case queued = "Queued"
+    case ready = "Ready"
+    case processing = "Processing"
+    case done = "Done"
+    case error = "Error"
+}
+
+/// The result of a bulk email validation operation.
+public struct BulkEmailValidationJobResponse: Codable, Sendable {
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: Here too, let's find a better name to distinguish it from `BulkValidationJobResponse`
created_at: 2025-05-14T23:58:47Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: If the `case` name is the same as the underlying string you don't need to specify it
```suggestion
        case csv
        /// A ZIP file containing a CSV file with email addresses.
        case zip
```
created_at: 2025-05-15T00:00:27Z
author_association: MEMBER
path: Sources/SendGridKit/SendGridClient.swift
user: login: fpseverino
body: Since the API keys are completely different, why don't we create a new, separate client just for address validation? This way we can ensure that the address validation API key is available when a user invokes one of its methods. What do you think?
created_at: 2025-05-15T00:08:43Z
author_association: MEMBER
diff_hunk: @@ -55,4 +72,152 @@ public struct SendGridClient: Sendable {
         // `JSONDecoder` will handle empty body by throwing decoding error
         throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
     }
+
+    /// Validate an email address using the SendGrid Email Validation API.
+    ///
+    /// - Parameter validationRequest: The ``EmailValidationRequest`` containing the email to validate.
+    /// - Returns: An ``EmailValidationResponse`` with validation details.
+    public func validateEmail(validationRequest: EmailValidationRequest) async throws -> EmailValidationResponse {
+        guard let apiKey = self.emailValidationAPIKey else {
+            throw SendGridError(id: "Email Validation API key not set")
+        }
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email")
+        request.method = .POST
+        request.headers = headers
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(validationRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(EmailValidationResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    /// Request an upload URL for bulk email validation using the SendGrid Email Validation API.
+    ///
+    /// - Parameter fileType: The type of file to be uploaded (CSV or ZIP).
+    /// - Returns: A ``BulkValidationUploadURLResponse`` with the upload URL and details.
+    public func getBulkValidationUploadURL(fileType: BulkValidationUploadURLRequest.FileType) async throws
path: Sources/SendGridKit/SendGridClient.swift
user: login: fpseverino
body: Can we merge this and `uploadBulkValidationFile`? In this way, the user only enters the file type and data, and they are automatically uploaded without having to call two different methods. Does this make sense?
created_at: 2025-05-15T00:17:50Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: ```suggestion
            #expect((0.0...1.0).contains(response.result.score))
```
created_at: 2025-05-15T00:24:28Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                print("Email is valid with score: \(response.result.score)")
+            } else {
+                print("Email is invalid: \(response.result.suggestion)")
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: We don't need `print`s, maybe add an `#expect` statement if it's appropriate
created_at: 2025-05-15T00:26:50Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                print("Email is valid with score: \(response.result.score)")
+            } else {
+                print("Email is invalid: \(response.result.suggestion)")
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for CSV")
+    func requestUploadURL() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: ```suggestion
            #expect(!response.uploadUri.isEmpty)
            #expect(!response.jobId.isEmpty)
```
created_at: 2025-05-15T00:27:38Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                print("Email is valid with score: \(response.result.score)")
+            } else {
+                print("Email is invalid: \(response.result.suggestion)")
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for CSV")
+    func requestUploadURL() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for Zip")
+    func requestUploadURLForZipArchive() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .zip)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: ```suggestion
            #expect(!response.uploadUri.isEmpty)
            #expect(!response.jobId.isEmpty)
```
created_at: 2025-05-15T00:28:19Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                print("Email is valid with score: \(response.result.score)")
+            } else {
+                print("Email is invalid: \(response.result.suggestion)")
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for CSV")
+    func requestUploadURL() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for Zip")
+    func requestUploadURLForZipArchive() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .zip)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(response.result.count > 0)
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: ```suggestion
            #expect(!response.result.isEmpty)
```
created_at: 2025-05-15T00:29:29Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                print("Email is valid with score: \(response.result.score)")
+            } else {
+                print("Email is invalid: \(response.result.suggestion)")
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for CSV")
+    func requestUploadURL() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for Zip")
+    func requestUploadURLForZipArchive() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .zip)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(response.result.count > 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status")
+    func getBulkValidationJobStatus() async throws {
+        try await withKnownIssue {
+            let jobsResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: "12345")
+            let errors = jobsResponse.response.value.result.errors
+            #expect(errors?.count == 0)
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: ```suggestion
            #expect(errors?.isEmpty)
```
created_at: 2025-05-15T00:30:02Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                print("Email is valid with score: \(response.result.score)")
+            } else {
+                print("Email is invalid: \(response.result.suggestion)")
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for CSV")
+    func requestUploadURL() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for Zip")
+    func requestUploadURLForZipArchive() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .zip)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(response.result.count > 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status")
+    func getBulkValidationJobStatus() async throws {
+        try await withKnownIssue {
+            let jobsResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: "12345")
+            let errors = jobsResponse.response.value.result.errors
+            #expect(errors?.count == 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status should throw when email validation API key is missing")
+    func getBulkValidationJobStatusShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.checkBulkValidationStatus(jobId: "12345")
+        }
+
+        #expect(error?.id?.isEmpty == false)
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: ```suggestion
        #expect(!error?.id?.isEmpty)
```
created_at: 2025-05-15T00:30:28Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                print("Email is valid with score: \(response.result.score)")
+            } else {
+                print("Email is invalid: \(response.result.suggestion)")
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for CSV")
+    func requestUploadURL() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for Zip")
+    func requestUploadURLForZipArchive() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .zip)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(response.result.count > 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status")
+    func getBulkValidationJobStatus() async throws {
+        try await withKnownIssue {
+            let jobsResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: "12345")
+            let errors = jobsResponse.response.value.result.errors
+            #expect(errors?.count == 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status should throw when email validation API key is missing")
+    func getBulkValidationJobStatusShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.checkBulkValidationStatus(jobId: "12345")
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Get Bulk validation jobs should throw when email validation API key is missing")
+    func getBulkValidationJobsShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.getBulkEmailValidationJobs()
+        }
+
+        #expect(error?.id?.isEmpty == false)
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: ```suggestion
        #expect(!error?.id?.isEmpty)
```
created_at: 2025-05-15T00:31:04Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                print("Email is valid with score: \(response.result.score)")
+            } else {
+                print("Email is invalid: \(response.result.suggestion)")
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for CSV")
+    func requestUploadURL() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for Zip")
+    func requestUploadURLForZipArchive() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .zip)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(response.result.count > 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status")
+    func getBulkValidationJobStatus() async throws {
+        try await withKnownIssue {
+            let jobsResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: "12345")
+            let errors = jobsResponse.response.value.result.errors
+            #expect(errors?.count == 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status should throw when email validation API key is missing")
+    func getBulkValidationJobStatusShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.checkBulkValidationStatus(jobId: "12345")
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Get Bulk validation jobs should throw when email validation API key is missing")
+    func getBulkValidationJobsShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.getBulkEmailValidationJobs()
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Validate email should throw when email validation API key is missing")
+    func validateEmailShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.validateEmail(validationRequest: .init(email: "some@email.com"))
+        }
+
+        #expect(error?.id?.isEmpty == false)
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: ```suggestion
        #expect(!error?.id?.isEmpty)
```
created_at: 2025-05-15T00:31:26Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                print("Email is valid with score: \(response.result.score)")
+            } else {
+                print("Email is invalid: \(response.result.suggestion)")
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for CSV")
+    func requestUploadURL() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for Zip")
+    func requestUploadURLForZipArchive() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .zip)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(response.result.count > 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status")
+    func getBulkValidationJobStatus() async throws {
+        try await withKnownIssue {
+            let jobsResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: "12345")
+            let errors = jobsResponse.response.value.result.errors
+            #expect(errors?.count == 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status should throw when email validation API key is missing")
+    func getBulkValidationJobStatusShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.checkBulkValidationStatus(jobId: "12345")
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Get Bulk validation jobs should throw when email validation API key is missing")
+    func getBulkValidationJobsShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.getBulkEmailValidationJobs()
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Validate email should throw when email validation API key is missing")
+    func validateEmailShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.validateEmail(validationRequest: .init(email: "some@email.com"))
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Upload CSV File")
+    func uploadCSVFile() async throws {
+        let csvContent = """
+            email
+            test1@example.com
+            test2@example.com
+            test3@example.com
+            """
+        let csvData = csvContent.data(using: .utf8)!
+
+        let response = """
+            {
+              "job_id": "01H793APATD899ESMY25ZNPNCF",
+              "upload_uri": "https://example.com/",
+              "upload_headers": [
+                {
+                  "header": "x-amz-server-side-encryption",
+                  "value": "aws:kms"
+                },
+                {
+                  "header": "content-type",
+                  "value": "text/csv"
+                }
+              ]
+            }
+            """
+
+        let responseData = try JSONDecoder().decode(BulkValidationUploadURLResponse.self, from: response.data(using: .utf8)!)
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.uploadBulkValidationFile(
+                fileData: csvData,
+                uploadResponse: responseData
+            )
+            #expect(response.0 == true)
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: ```suggestion
            #expect(response.0)
```
created_at: 2025-05-15T00:31:46Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                print("Email is valid with score: \(response.result.score)")
+            } else {
+                print("Email is invalid: \(response.result.suggestion)")
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for CSV")
+    func requestUploadURL() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for Zip")
+    func requestUploadURLForZipArchive() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .zip)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(response.result.count > 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status")
+    func getBulkValidationJobStatus() async throws {
+        try await withKnownIssue {
+            let jobsResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: "12345")
+            let errors = jobsResponse.response.value.result.errors
+            #expect(errors?.count == 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status should throw when email validation API key is missing")
+    func getBulkValidationJobStatusShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.checkBulkValidationStatus(jobId: "12345")
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Get Bulk validation jobs should throw when email validation API key is missing")
+    func getBulkValidationJobsShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.getBulkEmailValidationJobs()
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Validate email should throw when email validation API key is missing")
+    func validateEmailShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.validateEmail(validationRequest: .init(email: "some@email.com"))
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Upload CSV File")
+    func uploadCSVFile() async throws {
+        let csvContent = """
+            email
+            test1@example.com
+            test2@example.com
+            test3@example.com
+            """
+        let csvData = csvContent.data(using: .utf8)!
+
+        let response = """
+            {
+              "job_id": "01H793APATD899ESMY25ZNPNCF",
+              "upload_uri": "https://example.com/",
+              "upload_headers": [
+                {
+                  "header": "x-amz-server-side-encryption",
+                  "value": "aws:kms"
+                },
+                {
+                  "header": "content-type",
+                  "value": "text/csv"
+                }
+              ]
+            }
+            """
+
+        let responseData = try JSONDecoder().decode(BulkValidationUploadURLResponse.self, from: response.data(using: .utf8)!)
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.uploadBulkValidationFile(
+                fileData: csvData,
+                uploadResponse: responseData
+            )
+            #expect(response.0 == true)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Decode validate email response")
+    func decodeValidateEmailResponse() async throws {
+        let response = """
+            {
+              "result": {
+                "email": "cedric@fogowl.com",
+                "verdict": "Valid",
+                "score": 0.85021,
+                "local": "cedric",
+                "host": "fogowl.com",
+                "checks": {
+                  "domain": {
+                    "has_valid_address_syntax": true,
+                    "has_mx_or_a_record": true,
+                    "is_suspected_disposable_address": false
+                  },
+                  "local_part": {
+                    "is_suspected_role_address": false
+                  },
+                  "additional": {
+                    "has_known_bounces": false,
+                    "has_suspected_bounces": false
+                  }
+                },
+                "ip_address": "192.168.1.1"
+              }
+            }
+            """
+        let responseData = try JSONDecoder().decode(EmailValidationResponse.self, from: response.data(using: .utf8)!)
+        #expect(responseData.result.score == 0.85021)
+    }
+
+    @Test("Decode Get bulk Email Address")
+    func decodeGetBulkEmailAddress() async throws {
+        let response = """
+            {
+              "result": [
+                {
+                  "id": "01HV9ZZQAFEXW18KFEPTB9YD5E",
+                  "status": "Queued",
+                  "started_at": 1712954639,
+                  "finished_at": 0
+                }
+              ]
+            }
+            """
+        let responseData = try JSONDecoder().decode(BulkValidationJobResponse.self, from: response.data(using: .utf8)!)
+        #expect(responseData.result.count == 1)
+    }
+
+    @Test("Decode Get validation Email Address Job")
+    func decodeGetEmailAddressJob() async throws {
+        let response = """
+            {
+              "response": {
+                "value": {
+                  "result": {
+                    "id": "01HV9ZZQAFEXW18KFEPTB9YD5E",
+                    "status": "Queued",
+                    "segments": 0,
+                    "segments_processed": 0,
+                    "is_download_available": false,
+                    "started_at": 1712954639,
+                    "finished_at": 0,
+                    "errors": []
+                  }
+                }
+              }
+            }
+            """
+        let responseData = try JSONDecoder().decode(ValidationJobResponse.self, from: response.data(using: .utf8)!)
+        #expect(responseData.response.value.result.id == "01HV9ZZQAFEXW18KFEPTB9YD5E")
+    }
+
+    @Test("Bulk Email Validation")
+    func bulkValidateEmail() async throws {
+        // Create a simple CSV with a few test emails
+        let csvContent = """
+            email
+            test1@example.com
+            test2@example.com
+            test3@example.com
+            """
+        let csvData = csvContent.data(using: .utf8)!
+
+        try await withKnownIssue {
+            // Step 1: Get an upload URL
+            let uploadURLResponse = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+
+            // Verify upload URL response
+            _ = uploadURLResponse.jobId
+            _ = uploadURLResponse.uploadUri
+            _ = uploadURLResponse.uploadHeaders
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: Check them with `#expect`
created_at: 2025-05-15T00:32:38Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                print("Email is valid with score: \(response.result.score)")
+            } else {
+                print("Email is invalid: \(response.result.suggestion)")
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for CSV")
+    func requestUploadURL() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for Zip")
+    func requestUploadURLForZipArchive() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .zip)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(response.result.count > 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status")
+    func getBulkValidationJobStatus() async throws {
+        try await withKnownIssue {
+            let jobsResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: "12345")
+            let errors = jobsResponse.response.value.result.errors
+            #expect(errors?.count == 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status should throw when email validation API key is missing")
+    func getBulkValidationJobStatusShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.checkBulkValidationStatus(jobId: "12345")
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Get Bulk validation jobs should throw when email validation API key is missing")
+    func getBulkValidationJobsShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.getBulkEmailValidationJobs()
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Validate email should throw when email validation API key is missing")
+    func validateEmailShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.validateEmail(validationRequest: .init(email: "some@email.com"))
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Upload CSV File")
+    func uploadCSVFile() async throws {
+        let csvContent = """
+            email
+            test1@example.com
+            test2@example.com
+            test3@example.com
+            """
+        let csvData = csvContent.data(using: .utf8)!
+
+        let response = """
+            {
+              "job_id": "01H793APATD899ESMY25ZNPNCF",
+              "upload_uri": "https://example.com/",
+              "upload_headers": [
+                {
+                  "header": "x-amz-server-side-encryption",
+                  "value": "aws:kms"
+                },
+                {
+                  "header": "content-type",
+                  "value": "text/csv"
+                }
+              ]
+            }
+            """
+
+        let responseData = try JSONDecoder().decode(BulkValidationUploadURLResponse.self, from: response.data(using: .utf8)!)
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.uploadBulkValidationFile(
+                fileData: csvData,
+                uploadResponse: responseData
+            )
+            #expect(response.0 == true)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Decode validate email response")
+    func decodeValidateEmailResponse() async throws {
+        let response = """
+            {
+              "result": {
+                "email": "cedric@fogowl.com",
+                "verdict": "Valid",
+                "score": 0.85021,
+                "local": "cedric",
+                "host": "fogowl.com",
+                "checks": {
+                  "domain": {
+                    "has_valid_address_syntax": true,
+                    "has_mx_or_a_record": true,
+                    "is_suspected_disposable_address": false
+                  },
+                  "local_part": {
+                    "is_suspected_role_address": false
+                  },
+                  "additional": {
+                    "has_known_bounces": false,
+                    "has_suspected_bounces": false
+                  }
+                },
+                "ip_address": "192.168.1.1"
+              }
+            }
+            """
+        let responseData = try JSONDecoder().decode(EmailValidationResponse.self, from: response.data(using: .utf8)!)
+        #expect(responseData.result.score == 0.85021)
+    }
+
+    @Test("Decode Get bulk Email Address")
+    func decodeGetBulkEmailAddress() async throws {
+        let response = """
+            {
+              "result": [
+                {
+                  "id": "01HV9ZZQAFEXW18KFEPTB9YD5E",
+                  "status": "Queued",
+                  "started_at": 1712954639,
+                  "finished_at": 0
+                }
+              ]
+            }
+            """
+        let responseData = try JSONDecoder().decode(BulkValidationJobResponse.self, from: response.data(using: .utf8)!)
+        #expect(responseData.result.count == 1)
+    }
+
+    @Test("Decode Get validation Email Address Job")
+    func decodeGetEmailAddressJob() async throws {
+        let response = """
+            {
+              "response": {
+                "value": {
+                  "result": {
+                    "id": "01HV9ZZQAFEXW18KFEPTB9YD5E",
+                    "status": "Queued",
+                    "segments": 0,
+                    "segments_processed": 0,
+                    "is_download_available": false,
+                    "started_at": 1712954639,
+                    "finished_at": 0,
+                    "errors": []
+                  }
+                }
+              }
+            }
+            """
+        let responseData = try JSONDecoder().decode(ValidationJobResponse.self, from: response.data(using: .utf8)!)
+        #expect(responseData.response.value.result.id == "01HV9ZZQAFEXW18KFEPTB9YD5E")
+    }
+
+    @Test("Bulk Email Validation")
+    func bulkValidateEmail() async throws {
+        // Create a simple CSV with a few test emails
+        let csvContent = """
+            email
+            test1@example.com
+            test2@example.com
+            test3@example.com
+            """
+        let csvData = csvContent.data(using: .utf8)!
+
+        try await withKnownIssue {
+            // Step 1: Get an upload URL
+            let uploadURLResponse = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+
+            // Verify upload URL response
+            _ = uploadURLResponse.jobId
+            _ = uploadURLResponse.uploadUri
+            _ = uploadURLResponse.uploadHeaders
+
+            print("Got upload details with job ID: \(uploadURLResponse.jobId)")
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: No need for this print
created_at: 2025-05-15T00:33:07Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,256 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest: validationRequest)
+
+            // Verify response properties exist
+            _ = response.result.score
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                print("Email is valid with score: \(response.result.score)")
+            } else {
+                print("Email is invalid: \(response.result.suggestion)")
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for CSV")
+    func requestUploadURL() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Request Upload URL for Zip")
+    func requestUploadURLForZipArchive() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkValidationUploadURL(fileType: .zip)
+            #expect(response.uploadUri != "")
+            #expect(response.jobId != "")
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(response.result.count > 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status")
+    func getBulkValidationJobStatus() async throws {
+        try await withKnownIssue {
+            let jobsResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: "12345")
+            let errors = jobsResponse.response.value.result.errors
+            #expect(errors?.count == 0)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status should throw when email validation API key is missing")
+    func getBulkValidationJobStatusShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.checkBulkValidationStatus(jobId: "12345")
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Get Bulk validation jobs should throw when email validation API key is missing")
+    func getBulkValidationJobsShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.getBulkEmailValidationJobs()
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Validate email should throw when email validation API key is missing")
+    func validateEmailShouldThrow() async throws {
+        let error = await #expect(throws: SendGridError.self) {
+            try await client.validateEmail(validationRequest: .init(email: "some@email.com"))
+        }
+
+        #expect(error?.id?.isEmpty == false)
+    }
+
+    @Test("Upload CSV File")
+    func uploadCSVFile() async throws {
+        let csvContent = """
+            email
+            test1@example.com
+            test2@example.com
+            test3@example.com
+            """
+        let csvData = csvContent.data(using: .utf8)!
+
+        let response = """
+            {
+              "job_id": "01H793APATD899ESMY25ZNPNCF",
+              "upload_uri": "https://example.com/",
+              "upload_headers": [
+                {
+                  "header": "x-amz-server-side-encryption",
+                  "value": "aws:kms"
+                },
+                {
+                  "header": "content-type",
+                  "value": "text/csv"
+                }
+              ]
+            }
+            """
+
+        let responseData = try JSONDecoder().decode(BulkValidationUploadURLResponse.self, from: response.data(using: .utf8)!)
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.uploadBulkValidationFile(
+                fileData: csvData,
+                uploadResponse: responseData
+            )
+            #expect(response.0 == true)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Decode validate email response")
+    func decodeValidateEmailResponse() async throws {
+        let response = """
+            {
+              "result": {
+                "email": "cedric@fogowl.com",
+                "verdict": "Valid",
+                "score": 0.85021,
+                "local": "cedric",
+                "host": "fogowl.com",
+                "checks": {
+                  "domain": {
+                    "has_valid_address_syntax": true,
+                    "has_mx_or_a_record": true,
+                    "is_suspected_disposable_address": false
+                  },
+                  "local_part": {
+                    "is_suspected_role_address": false
+                  },
+                  "additional": {
+                    "has_known_bounces": false,
+                    "has_suspected_bounces": false
+                  }
+                },
+                "ip_address": "192.168.1.1"
+              }
+            }
+            """
+        let responseData = try JSONDecoder().decode(EmailValidationResponse.self, from: response.data(using: .utf8)!)
+        #expect(responseData.result.score == 0.85021)
+    }
+
+    @Test("Decode Get bulk Email Address")
+    func decodeGetBulkEmailAddress() async throws {
+        let response = """
+            {
+              "result": [
+                {
+                  "id": "01HV9ZZQAFEXW18KFEPTB9YD5E",
+                  "status": "Queued",
+                  "started_at": 1712954639,
+                  "finished_at": 0
+                }
+              ]
+            }
+            """
+        let responseData = try JSONDecoder().decode(BulkValidationJobResponse.self, from: response.data(using: .utf8)!)
+        #expect(responseData.result.count == 1)
+    }
+
+    @Test("Decode Get validation Email Address Job")
+    func decodeGetEmailAddressJob() async throws {
+        let response = """
+            {
+              "response": {
+                "value": {
+                  "result": {
+                    "id": "01HV9ZZQAFEXW18KFEPTB9YD5E",
+                    "status": "Queued",
+                    "segments": 0,
+                    "segments_processed": 0,
+                    "is_download_available": false,
+                    "started_at": 1712954639,
+                    "finished_at": 0,
+                    "errors": []
+                  }
+                }
+              }
+            }
+            """
+        let responseData = try JSONDecoder().decode(ValidationJobResponse.self, from: response.data(using: .utf8)!)
+        #expect(responseData.response.value.result.id == "01HV9ZZQAFEXW18KFEPTB9YD5E")
+    }
+
+    @Test("Bulk Email Validation")
+    func bulkValidateEmail() async throws {
+        // Create a simple CSV with a few test emails
+        let csvContent = """
+            email
+            test1@example.com
+            test2@example.com
+            test3@example.com
+            """
+        let csvData = csvContent.data(using: .utf8)!
+
+        try await withKnownIssue {
+            // Step 1: Get an upload URL
+            let uploadURLResponse = try await emailValidationClient.getBulkValidationUploadURL(fileType: .csv)
+
+            // Verify upload URL response
+            _ = uploadURLResponse.jobId
+            _ = uploadURLResponse.uploadUri
+            _ = uploadURLResponse.uploadHeaders
+
+            print("Got upload details with job ID: \(uploadURLResponse.jobId)")
+
+            // Step 2: Upload the CSV file to start the validation job (simulated in this test)
+            let (uploadSuccess, jobId) = try await client.uploadBulkValidationFile(
+                fileData: csvData,
+                uploadResponse: uploadURLResponse
+            )
+
+            #expect(uploadSuccess == true)
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: ```suggestion
            #expect(uploadSuccess)
```
created_at: 2025-05-15T00:33:27Z
author_association: MEMBER
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: Could you split the structs for Real Time Email Address Validation and Bulk Email Address Validation into two different files?
created_at: 2025-05-15T00:38:01Z
author_association: MEMBER
diff_hunk: @@ -61,6 +61,94 @@ let email = SendGridEmail(...)
 try await sendGridClient.send(email: email)
 ```
 
+### Email Validation API
+
+SendGridKit supports SendGrid's Email Validation API, which helps verify the validity of email addresses before sending:
+
+```swift
+import SendGridKit
+
+// Create a validation request
+let validationRequest = EmailValidationRequest(email: "example@email.com")
+
+// Validate the email
+do {
+    let validationResponse = try await sendGridClient.validateEmail(validationRequest: validationRequest)
+    
+    // Check if the email is valid
+    if validationResponse.result.valid {
path: README.md
user: login: fpseverino
body: ```suggestion
    if validationResponse.result.verdict == .valid {
```
created_at: 2025-05-15T00:40:21Z
author_association: MEMBER
diff_hunk: @@ -61,6 +61,94 @@ let email = SendGridEmail(...)
 try await sendGridClient.send(email: email)
 ```
 
+### Email Validation API
+
+SendGridKit supports SendGrid's Email Validation API, which helps verify the validity of email addresses before sending:
+
+```swift
+import SendGridKit
+
+// Create a validation request
+let validationRequest = EmailValidationRequest(email: "example@email.com")
+
+// Validate the email
+do {
+    let validationResponse = try await sendGridClient.validateEmail(validationRequest: validationRequest)
+    
+    // Check if the email is valid
+    if validationResponse.result.valid {
+        print("Email is valid with score: \(validationResponse.result.score)")
+    } else {
+        print("Email is invalid: \(validationResponse.result.reason ?? "Unknown reason")")
+    }
+    
+    // Access detailed validation information
+    if validationResponse.checks.disposable {
+        print("Warning: This is a disposable email address")
+    }
+    
+    if validationResponse.checks.roleAccount {
+        print("Note: This is a role-based email address")
+    }
+} catch {
+    print("Validation failed: \(error)")
+}
+```
+
+#### Bulk Email Validation API
+
+For validating multiple email addresses at once, SendGridKit provides access to SendGrid's Bulk Email Validation API. This requires uploading a CSV file with email addresses:
+
+```swift
+import SendGridKit
+import Foundation
+
+do {
+    // Step 1: Create a CSV file with email addresses
+    let csvContent = """
+    emails
+    user1@example.com
+    user2@example.com
+    user3@example.com
+    """
+    guard let csvData = csvContent.data(using: .utf8) else {
+        throw NSError(domain: "CSVConversionError", code: 1)
path: README.md
user: login: fpseverino
body: Please don't use `NSError`, in this context you could also invent an error type
created_at: 2025-05-15T00:41:36Z
author_association: MEMBER
diff_hunk: @@ -61,6 +61,94 @@ let email = SendGridEmail(...)
 try await sendGridClient.send(email: email)
 ```
 
+### Email Validation API
+
+SendGridKit supports SendGrid's Email Validation API, which helps verify the validity of email addresses before sending:
+
+```swift
+import SendGridKit
+
+// Create a validation request
+let validationRequest = EmailValidationRequest(email: "example@email.com")
+
+// Validate the email
+do {
+    let validationResponse = try await sendGridClient.validateEmail(validationRequest: validationRequest)
+    
+    // Check if the email is valid
+    if validationResponse.result.valid {
+        print("Email is valid with score: \(validationResponse.result.score)")
+    } else {
+        print("Email is invalid: \(validationResponse.result.reason ?? "Unknown reason")")
+    }
+    
+    // Access detailed validation information
+    if validationResponse.checks.disposable {
+        print("Warning: This is a disposable email address")
+    }
+    
+    if validationResponse.checks.roleAccount {
+        print("Note: This is a role-based email address")
+    }
+} catch {
+    print("Validation failed: \(error)")
+}
+```
+
+#### Bulk Email Validation API
+
+For validating multiple email addresses at once, SendGridKit provides access to SendGrid's Bulk Email Validation API. This requires uploading a CSV file with email addresses:
+
+```swift
+import SendGridKit
+import Foundation
+
+do {
+    // Step 1: Create a CSV file with email addresses
+    let csvContent = """
+    emails
+    user1@example.com
+    user2@example.com
+    user3@example.com
+    """
+    guard let csvData = csvContent.data(using: .utf8) else {
+        throw NSError(domain: "CSVConversionError", code: 1)
+    }
+    
+    // Step 2: Get an upload URL
+    let uploadURLResponse = try await sendGridClient.getBulkValidationUploadURL(fileType: .csv)
+    print("Got upload details with job ID: \(uploadURLResponse.jobId)")
+    
+    // Step 3: Upload the CSV file
+    let (uploadSuccess, jobId) = try await sendGridClient.uploadBulkValidationFile(
+        fileData: csvData,
+        uploadResponse: uploadURLResponse
+    )
+    
+    if !uploadSuccess {
+        throw NSError(domain: "UploadError", code: 2)
path: README.md
user: login: fpseverino
body: Here too please, I don't like `NSError`
created_at: 2025-05-15T00:42:24Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
+    /// Whether the SMTP check was successful.
+    public let success: Bool
+
+    /// The reason for the SMTP check result.
+    public let reason: String?
+}
+
+/// Additional information about the email validation.
+public struct AdditionalInfo: Decodable, Sendable {
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: thoven87
body: > Where is this struct used?

It's no longer needed since I moved it inside of the `ValidationChecks` struct
created_at: 2025-05-15T00:51:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: thoven87
body: > What is this struct for?

not needed
created_at: 2025-05-15T00:51:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: thoven87
body: > These two should be part of `EmailValidationResponse`, as per the [API docs](https://www.twilio.com/docs/sendgrid/api-reference/email-address-validation/validate-an-email#responses)

it's been moved 
created_at: 2025-05-15T00:51:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
+    /// Whether the SMTP check was successful.
+    public let success: Bool
+
+    /// The reason for the SMTP check result.
+    public let reason: String?
+}
+
+/// Additional information about the email validation.
+public struct AdditionalInfo: Decodable, Sendable {
+    /// The IP address used for validation.
+    public let ipAddress: String?
+
+    /// The source of the validation request.
+    public let source: String?
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case ipAddress = "ip_address"
+        case source
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+public struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let jobId: String
+
+    /// The URI where the file should be uploaded.
+    public let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    public let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    public struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        public let header: String
+
+        /// The value of the header.
+        public let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct BulkValidationJobResponse: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let result: BulkValidationJobResult
+}
+
+/// The status result of a bulk email validation job.
+public struct BulkValidationJobResult: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    public let id: String
+
+    /// Status of the validation job (e.g., "Queued", "Processing", "Completed").
+    public let status: BuildEmailValidationStatus
+
+    /// The total number of segments in the Bulk Email Address Validation Job.
+    /// There are 1,500 email addresses per segment. The value is 0 until the Job status is Processing.
+    public let segments: Int
+
+    /// The number of segments processed at the time of the request.
+    /// 100 segments process in parallel at a time.
+    public let segmentsProcessed: Int
+
+    /// Boolean indicating whether the results CSV file is available for download.
+    public let isDownloadAvailable: Bool
+
+    /// The ISO8601 timestamp when the Job was created.
+    /// This is the time at which the upload request was sent to the upload_uri.
+    public let startedAt: Double
+
+    /// The ISO8601 timestamp when the Job was finished.
+    public let finishedAt: Double
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: thoven87
body: > It would be nice to decode them automatically and offer these properties as `Date`.

I noticed that after the initial PR and current commit should reflect that.
created_at: 2025-05-15T00:53:11Z
author_association: CONTRIBUTOR
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: thoven87
body: > Could you split the structs for Real Time Email Address Validation and Bulk Email Address Validation into two different files?

Will do
created_at: 2025-05-15T00:53:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
+    /// Whether the SMTP check was successful.
+    public let success: Bool
+
+    /// The reason for the SMTP check result.
+    public let reason: String?
+}
+
+/// Additional information about the email validation.
+public struct AdditionalInfo: Decodable, Sendable {
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: Great, so can we remove it?
created_at: 2025-05-15T01:07:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: fpseverino
body: Ok, let's remove it then
created_at: 2025-05-15T01:08:06Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,279 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv = "csv"
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip = "zip"
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+    }
+}
+
+/// Various checks performed during email validation.
+public struct ValidationChecks: Decodable, Sendable {
+
+    /// Checks on the domain portion of the email address.
+    public let domain: Domain
+
+    /// Checks on the local part of the email address.
+    public let localPart: LocalPartInfo
+
+    /// Additional checks on the email address.
+    public let additional: AdditionalInfo
+
+    /// The source of the validation, as per the API request.
+    public let source: String
+
+    /// The IP address associated with this email.
+    public let ipAddress: String
+
+    public struct AdditionalInfo: Decodable, Sendable {
+        /// Whether email sent to this address from your account has bounced.
+        public let hasKnownBounces: Bool
+        /// Whether our model predicts that the email address might bounce.
+        public let hasSuspectedBounces: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasKnownBounces = "has_known_bounces"
+            case hasSuspectedBounces = "has_suspected_bounces"
+        }
+    }
+
+    public struct LocalPartInfo: Decodable, Sendable {
+        /// Whether the local part of email appears to be a role or group (e.g., hr, admin)
+        public let isSuspectedRoleAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case isSuspectedRoleAddress = "is_suspected_role_address"
+        }
+    }
+
+    public struct Domain: Decodable, Sendable {
+        /// Whether the email address syntax is valid.
+        public let hasValidAddressSyntax: Bool
+        /// Whether the email has appropriate DNS records to deliver a message.
+        public let hasMXOrARecord: Bool
+        /// Whether the domain appears to be from a disposable email address service.
+        public let isSuspectedDisposableAddress: Bool
+
+        enum CodingKeys: String, CodingKey {
+            case hasValidAddressSyntax = "has_valid_address_syntax"
+            case hasMXOrARecord = "has_mx_or_a_record"
+            case isSuspectedDisposableAddress = "is_suspected_disposable_address"
+        }
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case domain
+        case localPart = "local_part"
+        case additional
+        case source
+        case ipAddress = "ip_address"
+    }
+}
+
+/// Information about the SMTP check.
+public struct SMTPCheck: Codable, Sendable {
+    /// Whether the SMTP check was successful.
+    public let success: Bool
+
+    /// The reason for the SMTP check result.
+    public let reason: String?
+}
+
+/// Additional information about the email validation.
+public struct AdditionalInfo: Decodable, Sendable {
path: Sources/SendGridKit/Models/EmailValidation.swift
user: login: thoven87
body: done
created_at: 2025-05-15T01:10:55Z
author_association: CONTRIBUTOR
path: Sources/SendGridKit/SendGridClient.swift
user: login: thoven87
body: Understood! I think we can do away with just one client where both API keys are set. Should a user passes just the regular API key and tries to call the email validation API, the validations methods will throw. I also thought of creating another module for the Email validation API since one might not need this API. Do you have any preferences?
created_at: 2025-05-15T01:36:20Z
author_association: CONTRIBUTOR
path: Sources/SendGridKit/SendGridClient.swift
user: login: fpseverino
body: I think a separate module is overkill, although we could use a package trait for users of Swift 6.1.

In any case, I think a separate client is the best option.
There are no big duplicate resources because the user could (and should) instantiate both with the same HTTP client.
This way we don't have to throw errors because we know that the right API key is available for each job.
Also, each client has very different purposes, so it is appropriate for them to be distinct.
created_at: 2025-05-15T12:45:58Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,129 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
path: Sources/SendGridKit/Models/RealtimeEmailValidation.swift
user: login: fpseverino
body: 
```suggestion
    public let result: Result

    /// The overall result of email validation.
    public struct Result: Decodable, Sendable {
```
created_at: 2025-05-15T13:06:06Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,129 @@
+/// A request to validate an email address using SendGrid's Email Validation API.
+public struct EmailValidationRequest: Encodable {
+    /// The email address to validate.
+    public let email: String
+
+    /// Optional source of the email address being validated.
+    public let source: String?
+
+    /// Initialize a new `EmailValidationRequest`
+    ///
+    /// - Parameters:
+    ///   - email: The email address to validate
+    ///   - source: Optional source of the email address being validated
+    public init(email: String, source: String? = nil) {
+        self.email = email
+        self.source = source
+    }
+}
+
+/// The response from SendGrid's Email Validation API.
+public struct EmailValidationResponse: Decodable, Sendable {
+    /// The overall verdict for the email address.
+    public let result: ValidationResult
+
+    /// The overall result of email validation.
+    public struct ValidationResult: Decodable, Sendable {
+
+        /// The email address that was validated.
+        public let email: String
+
+        /// The verdict on the email address.
+        public let verdict: Verdict
+
+        /// The score representing the quality of the email address (0.0 to 1.0).
+        public let score: Double
+
+        /// The local part of the email address.
+        public let local: String
+
+        /// The domain of the email address.
+        public let host: String
+
+        /// A suggested correction in the event of domain name typos (e.g., gmial.com)
+        public let suggestion: String?
+
+        /// Granular checks for email address validity.
+        public let checks: ValidationChecks
+
+        /// The IP address associated with this email.
+        public let ipAddress: String?
+
+        /// The source of the validation, as per the API request.
+        public let source: String?
+
+        public enum Verdict: String, Codable, Sendable {
+            case valid = "Valid"
+            case risky = "Risky"
+            case invalid = "Invalid"
+        }
+
+        /// Various checks performed during email validation.
+        public struct ValidationChecks: Decodable, Sendable {
path: Sources/SendGridKit/Models/RealtimeEmailValidation.swift
user: login: fpseverino
body: 
```suggestion
        public let checks: Checks

        /// The IP address associated with this email.
        public let ipAddress: String?

        /// The source of the validation, as per the API request.
        public let source: String?

        public enum Verdict: String, Codable, Sendable {
            case valid = "Valid"
            case risky = "Risky"
            case invalid = "Invalid"
        }

        /// Various checks performed during email validation.
        public struct Checks: Decodable, Sendable {
```
created_at: 2025-05-15T13:08:51Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,150 @@
+import struct Foundation.Date
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
path: Sources/SendGridKit/Models/BulkEmailValidation.swift
user: login: fpseverino
body: 
```suggestion
public struct BulkEmailValidationUploadURLRequest: Encodable {
```
created_at: 2025-05-15T13:12:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,150 @@
+import struct Foundation.Date
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+struct BulkValidationUploadURLResponse: Decodable, Sendable {
path: Sources/SendGridKit/Models/BulkEmailValidation.swift
user: login: fpseverino
body: 
```suggestion
struct BulkEmailValidationUploadURLResponse: Decodable, Sendable {
```
created_at: 2025-05-15T13:12:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,150 @@
+import struct Foundation.Date
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    let jobId: String
+
+    /// The URI where the file should be uploaded.
+    let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        let header: String
+
+        /// The value of the header.
+        let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct ValidationJobResponse: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let response: JobResponse
+
+    public struct JobResponse: Decodable, Sendable {
+        public let value: ValidationJobValue
+
+        public struct ValidationJobValue: Decodable, Sendable {
+            public let result: Result
+
+            /// The status result of a bulk email validation job.
+            public struct Result: Decodable, Sendable {
+                /// The unique identifier for the validation job.
+                public let id: String
+
+                /// Status of the validation job (e.g., "Queued", "Processing", "Completed").
+                public let status: BuildEmailValidationStatus
+
+                /// The total number of segments in the Bulk Email Address Validation Job.
+                /// There are 1,500 email addresses per segment. The value is 0 until the Job status is Processing.
+                public let segments: Int?
+
+                /// The number of segments processed at the time of the request.
+                /// 100 segments process in parallel at a time.
+                public let segmentsProcessed: Int?
+
+                /// Boolean indicating whether the results CSV file is available for download.
+                public let isDownloadAvailable: Bool?
+
+                /// The ISO8601 timestamp when the Job was created.
+                /// This is the time at which the upload request was sent to the upload_uri.
+                public let startedAt: Date
+
+                /// The ISO8601 timestamp when the Job was finished.
+                public let finishedAt: Date?
+
+                /// Array containing error messages related to the Bulk Email Address Validation Job.
+                /// Array is empty if no errors ocurred.
+                public let errors: [BulkValidationError]?
+
+                public struct BulkValidationError: Decodable, Sendable {
+                    /// Description of the error encountered during execution of the Bulk Email Address Validation Job.
+                    public let message: String
+                }
+
+                /// CodingKeys for mapping JSON fields to struct properties
+                private enum CodingKeys: String, CodingKey {
+                    case id, status, segments, errors
+                    case segmentsProcessed = "segments_processed"
+                    case isDownloadAvailable = "is_download_available"
+                    case startedAt = "started_at"
+                    case finishedAt = "finished_at"
+                }
+            }
+        }
+    }
+}
+
+public enum BuildEmailValidationStatus: String, Codable, Sendable {
+    case initiated = "Initiated"
+    case queued = "Queued"
+    case ready = "Ready"
+    case processing = "Processing"
+    case done = "Done"
+    case error = "Error"
+}
+
+/// The result of a bulk email validation operation.
+public struct BulkEmailValidationJobResponse: Codable, Sendable {
path: Sources/SendGridKit/Models/BulkEmailValidation.swift
user: login: fpseverino
body: 
```suggestion
public struct BulkEmailValidationJobsResponse: Codable, Sendable {
```
created_at: 2025-05-15T13:14:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,150 @@
+import struct Foundation.Date
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    let jobId: String
+
+    /// The URI where the file should be uploaded.
+    let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        let header: String
+
+        /// The value of the header.
+        let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct ValidationJobResponse: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let response: JobResponse
+
+    public struct JobResponse: Decodable, Sendable {
+        public let value: ValidationJobValue
+
+        public struct ValidationJobValue: Decodable, Sendable {
+            public let result: Result
+
+            /// The status result of a bulk email validation job.
+            public struct Result: Decodable, Sendable {
+                /// The unique identifier for the validation job.
+                public let id: String
+
+                /// Status of the validation job (e.g., "Queued", "Processing", "Completed").
+                public let status: BuildEmailValidationStatus
+
+                /// The total number of segments in the Bulk Email Address Validation Job.
+                /// There are 1,500 email addresses per segment. The value is 0 until the Job status is Processing.
+                public let segments: Int?
+
+                /// The number of segments processed at the time of the request.
+                /// 100 segments process in parallel at a time.
+                public let segmentsProcessed: Int?
+
+                /// Boolean indicating whether the results CSV file is available for download.
+                public let isDownloadAvailable: Bool?
+
+                /// The ISO8601 timestamp when the Job was created.
+                /// This is the time at which the upload request was sent to the upload_uri.
+                public let startedAt: Date
+
+                /// The ISO8601 timestamp when the Job was finished.
+                public let finishedAt: Date?
+
+                /// Array containing error messages related to the Bulk Email Address Validation Job.
+                /// Array is empty if no errors ocurred.
+                public let errors: [BulkValidationError]?
+
+                public struct BulkValidationError: Decodable, Sendable {
+                    /// Description of the error encountered during execution of the Bulk Email Address Validation Job.
+                    public let message: String
+                }
+
+                /// CodingKeys for mapping JSON fields to struct properties
+                private enum CodingKeys: String, CodingKey {
+                    case id, status, segments, errors
+                    case segmentsProcessed = "segments_processed"
+                    case isDownloadAvailable = "is_download_available"
+                    case startedAt = "started_at"
+                    case finishedAt = "finished_at"
+                }
+            }
+        }
+    }
+}
+
+public enum BuildEmailValidationStatus: String, Codable, Sendable {
+    case initiated = "Initiated"
+    case queued = "Queued"
+    case ready = "Ready"
+    case processing = "Processing"
+    case done = "Done"
+    case error = "Error"
+}
+
+/// The result of a bulk email validation operation.
+public struct BulkEmailValidationJobResponse: Codable, Sendable {
+    /// The response structure containing the validation results.
+    public let result: [BulkValidationJobResponse]
+
+    /// Nested response structure containing the results value.
+    public struct BulkValidationJobResponse: Codable, Sendable {
path: Sources/SendGridKit/Models/BulkEmailValidation.swift
user: login: fpseverino
body: 
```suggestion
    public let result: [Result]

    /// Nested response structure containing the results value.
    public struct Result: Codable, Sendable {
```
created_at: 2025-05-15T13:15:20Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,150 @@
+import struct Foundation.Date
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    let jobId: String
+
+    /// The URI where the file should be uploaded.
+    let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        let header: String
+
+        /// The value of the header.
+        let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct ValidationJobResponse: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let response: JobResponse
+
+    public struct JobResponse: Decodable, Sendable {
+        public let value: ValidationJobValue
+
+        public struct ValidationJobValue: Decodable, Sendable {
+            public let result: Result
+
+            /// The status result of a bulk email validation job.
+            public struct Result: Decodable, Sendable {
+                /// The unique identifier for the validation job.
+                public let id: String
+
+                /// Status of the validation job (e.g., "Queued", "Processing", "Completed").
+                public let status: BuildEmailValidationStatus
+
+                /// The total number of segments in the Bulk Email Address Validation Job.
+                /// There are 1,500 email addresses per segment. The value is 0 until the Job status is Processing.
+                public let segments: Int?
+
+                /// The number of segments processed at the time of the request.
+                /// 100 segments process in parallel at a time.
+                public let segmentsProcessed: Int?
+
+                /// Boolean indicating whether the results CSV file is available for download.
+                public let isDownloadAvailable: Bool?
+
+                /// The ISO8601 timestamp when the Job was created.
+                /// This is the time at which the upload request was sent to the upload_uri.
+                public let startedAt: Date
+
+                /// The ISO8601 timestamp when the Job was finished.
+                public let finishedAt: Date?
+
+                /// Array containing error messages related to the Bulk Email Address Validation Job.
+                /// Array is empty if no errors ocurred.
+                public let errors: [BulkValidationError]?
+
+                public struct BulkValidationError: Decodable, Sendable {
+                    /// Description of the error encountered during execution of the Bulk Email Address Validation Job.
+                    public let message: String
+                }
+
+                /// CodingKeys for mapping JSON fields to struct properties
+                private enum CodingKeys: String, CodingKey {
+                    case id, status, segments, errors
+                    case segmentsProcessed = "segments_processed"
+                    case isDownloadAvailable = "is_download_available"
+                    case startedAt = "started_at"
+                    case finishedAt = "finished_at"
+                }
+            }
+        }
+    }
+}
+
+public enum BuildEmailValidationStatus: String, Codable, Sendable {
path: Sources/SendGridKit/Models/BulkEmailValidation.swift
user: login: fpseverino
body: 
```suggestion
public enum BulkEmailValidationJobStatus: String, Codable, Sendable {
```
created_at: 2025-05-15T13:16:17Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,150 @@
+import struct Foundation.Date
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    let jobId: String
+
+    /// The URI where the file should be uploaded.
+    let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        let header: String
+
+        /// The value of the header.
+        let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct ValidationJobResponse: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let response: JobResponse
+
+    public struct JobResponse: Decodable, Sendable {
+        public let value: ValidationJobValue
path: Sources/SendGridKit/Models/BulkEmailValidation.swift
user: login: fpseverino
body: Ok, I see that the [API docs](https://www.twilio.com/docs/sendgrid/api-reference/email-address-validation/get-bulk-email-address-validation-job-by-id#responses) are a bit confusing, since in one tab they say the response only has a `result` argument (so basically it's `ValidationJobValue`), but in another tab they have this nested `response`, `value` and then `result` arguments.
Have you tested this? What's the correct structure?

BTW, in any case we have to find better, simpler names for all of these `struct`s.
created_at: 2025-05-15T13:24:06Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,150 @@
+import struct Foundation.Date
+
+/// A request to get an upload URL for bulk email validation.
+public struct BulkValidationUploadURLRequest: Encodable {
+    /// The file type that will be uploaded.
+    public let fileType: FileType
+
+    /// Initialize a new `BulkValidationUploadURLRequest`
+    ///
+    /// - Parameter fileType: The file type that will be uploaded (CSV or ZIP)
+    public init(fileType: FileType) {
+        self.fileType = fileType
+    }
+
+    /// The type of file that will be uploaded for bulk validation.
+    public enum FileType: String, Codable, Sendable {
+        /// A CSV file containing email addresses.
+        case csv
+        /// A ZIP file containing a CSV file with email addresses.
+        case zip
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case fileType = "file_type"
+    }
+}
+
+/// The response containing upload details for bulk email validation.
+struct BulkValidationUploadURLResponse: Decodable, Sendable {
+    /// The unique identifier for the validation job.
+    let jobId: String
+
+    /// The URI where the file should be uploaded.
+    let uploadUri: String
+
+    /// Headers that should be included in the upload request.
+    let uploadHeaders: [UploadHeader]
+
+    /// Header for the upload request.
+    struct UploadHeader: Codable, Sendable {
+        /// The name of the header.
+        let header: String
+
+        /// The value of the header.
+        let value: String
+    }
+
+    /// CodingKeys for mapping JSON fields to struct properties
+    private enum CodingKeys: String, CodingKey {
+        case jobId = "job_id"
+        case uploadUri = "upload_uri"
+        case uploadHeaders = "upload_headers"
+    }
+}
+
+/// The response from initiating a bulk email validation job after upload.
+public struct ValidationJobResponse: Decodable, Sendable {
+    /// The response structure containing the job creation status.
+    public let response: JobResponse
+
+    public struct JobResponse: Decodable, Sendable {
+        public let value: ValidationJobValue
path: Sources/SendGridKit/Models/BulkEmailValidation.swift
user: login: thoven87
body: The API specs are published [here](https://github.com/twilio/sendgrid-oai/blob/main/spec/yaml/tsg_email_validation_v3.yaml)
created_at: 2025-05-16T10:36:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -102,4 +107,194 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest)
+
+            // Verify response properties exist
+            #expect((0.0...1.0).contains(response.result.score))
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                #expect(response.result.ipAddress != nil)
+            } else {
+                #expect(!response.result.checks.domain.hasMXOrARecord)
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(!response.result.isEmpty)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status")
+    func getBulkValidationJobStatus() async throws {
+        try await withKnownIssue {
+            let jobsResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: "12345")
+            let errors = jobsResponse.response.value.result.errors
+            #expect(errors?.isEmpty == true)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Upload CSV File")
+    func uploadCSVFile() async throws {
+        let csvContent = """
+            email
+            test1@example.com
+            test2@example.com
+            test3@example.com
+            """
+        let csvData = csvContent.data(using: .utf8)!
+
+        let _ = """
+            {
+              "job_id": "01H793APATD899ESMY25ZNPNCF",
+              "upload_uri": "https://example.com/",
+              "upload_headers": [
+                {
+                  "header": "x-amz-server-side-encryption",
+                  "value": "aws:kms"
+                },
+                {
+                  "header": "content-type",
+                  "value": "text/csv"
+                }
+              ]
+            }
+            """
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: What is this for?
created_at: 2025-05-16T14:47:24Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,180 @@
+import AsyncHTTPClient
+import Foundation
+import NIO
+import NIOFoundationCompat
+import NIOHTTP1
+
+/// A client for validating emails using the SendGrid API.
+public struct SendGridEmailValidationClient: Sendable {
+    private let baseURL: String
+    private let httpClient: HTTPClient
+    private let apiKey: String
+
+    private let encoder: JSONEncoder = {
+        let encoder = JSONEncoder()
+        encoder.dateEncodingStrategy = .secondsSince1970
+        return encoder
+    }()
+
+    private let decoder: JSONDecoder = {
+        let decoder = JSONDecoder()
+        decoder.dateDecodingStrategy = .secondsSince1970
+        return decoder
+    }()
+
+    /// Initialize a new `SendGridClientEmailValidationClient`
+    ///
+    /// - Parameters:
+    ///   - httpClient: The `HTTPClient` to use for sending requests
+    ///   - apiKey: The SendGrid API key
+    ///   - forEU: Whether to use the API endpoint for global users and subusers or for EU regional subusers
+    public init(httpClient: HTTPClient, apiKey: String, forEU: Bool = false) {
+        self.httpClient = httpClient
+        self.apiKey = apiKey
+        self.baseURL = forEU ? "https://api.eu.sendgrid.com/v3" : "https://api.sendgrid.com/v3"
+    }
+
+    /// Validate an email address using the SendGrid Email Validation API.
+    ///
+    /// - Parameter validationRequest: The ``EmailValidationRequest`` containing the email to validate.
+    /// - Returns: An ``EmailValidationResponse`` with validation details.
+    public func validateEmail(_ validationRequest: EmailValidationRequest) async throws -> EmailValidationResponse {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email")
+        request.method = .POST
+        request.headers = headers
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(validationRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(EmailValidationResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    /// Request an upload URL for bulk email validation using the SendGrid Email Validation API.
+    ///
+    /// - Parameter fileType: The type of file to be uploaded (CSV or ZIP).
+    /// - Returns: A ``BulkValidationUploadURLResponse`` with the upload URL and details.
+    func getBulkValidationUploadURL(fileType: BulkEmailValidationUploadURLRequest.FileType) async throws
+        -> BulkEmailValidationUploadURLResponse
+    {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email/jobs")
+        request.method = .POST
+        request.headers = headers
+
+        let uploadRequest = BulkEmailValidationUploadURLRequest(fileType: fileType)
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(uploadRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(BulkEmailValidationUploadURLResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    public typealias UploadBulkValidationFileResult = (status: Bool, jobID: String)
+
+    /// Upload a file to the provided URL for bulk email validation.
+    ///
+    /// - Parameters:
+    ///   - fileData: The data of the file to upload (CSV or ZIP).
+    ///   - uploadResponse: The ``BulkValidationUploadURLResponse`` containing upload details.
+    /// - Returns: `true` if the upload was successful, and the job ID.
+    public func uploadBulkValidationFile(
+        fileData: Data,
+        fileType: BulkEmailValidationUploadURLRequest.FileType
path: Sources/SendGridKit/SendGridEmailValidationClient.swift
user: login: fpseverino
body: Let's make `FileType` a top level public struct, and `BulkEmailValidationUploadURLRequest` an internal one. That way the API is a bit nicer
created_at: 2025-05-16T14:51:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,180 @@
+import AsyncHTTPClient
+import Foundation
+import NIO
+import NIOFoundationCompat
+import NIOHTTP1
+
+/// A client for validating emails using the SendGrid API.
+public struct SendGridEmailValidationClient: Sendable {
+    private let baseURL: String
+    private let httpClient: HTTPClient
+    private let apiKey: String
+
+    private let encoder: JSONEncoder = {
+        let encoder = JSONEncoder()
+        encoder.dateEncodingStrategy = .secondsSince1970
+        return encoder
+    }()
+
+    private let decoder: JSONDecoder = {
+        let decoder = JSONDecoder()
+        decoder.dateDecodingStrategy = .secondsSince1970
+        return decoder
+    }()
+
+    /// Initialize a new `SendGridClientEmailValidationClient`
+    ///
+    /// - Parameters:
+    ///   - httpClient: The `HTTPClient` to use for sending requests
+    ///   - apiKey: The SendGrid API key
+    ///   - forEU: Whether to use the API endpoint for global users and subusers or for EU regional subusers
+    public init(httpClient: HTTPClient, apiKey: String, forEU: Bool = false) {
+        self.httpClient = httpClient
+        self.apiKey = apiKey
+        self.baseURL = forEU ? "https://api.eu.sendgrid.com/v3" : "https://api.sendgrid.com/v3"
+    }
+
+    /// Validate an email address using the SendGrid Email Validation API.
+    ///
+    /// - Parameter validationRequest: The ``EmailValidationRequest`` containing the email to validate.
+    /// - Returns: An ``EmailValidationResponse`` with validation details.
+    public func validateEmail(_ validationRequest: EmailValidationRequest) async throws -> EmailValidationResponse {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email")
+        request.method = .POST
+        request.headers = headers
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(validationRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(EmailValidationResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    /// Request an upload URL for bulk email validation using the SendGrid Email Validation API.
+    ///
+    /// - Parameter fileType: The type of file to be uploaded (CSV or ZIP).
+    /// - Returns: A ``BulkValidationUploadURLResponse`` with the upload URL and details.
+    func getBulkValidationUploadURL(fileType: BulkEmailValidationUploadURLRequest.FileType) async throws
+        -> BulkEmailValidationUploadURLResponse
+    {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email/jobs")
+        request.method = .POST
+        request.headers = headers
+
+        let uploadRequest = BulkEmailValidationUploadURLRequest(fileType: fileType)
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(uploadRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(BulkEmailValidationUploadURLResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    public typealias UploadBulkValidationFileResult = (status: Bool, jobID: String)
+
+    /// Upload a file to the provided URL for bulk email validation.
+    ///
+    /// - Parameters:
+    ///   - fileData: The data of the file to upload (CSV or ZIP).
+    ///   - uploadResponse: The ``BulkValidationUploadURLResponse`` containing upload details.
+    /// - Returns: `true` if the upload was successful, and the job ID.
+    public func uploadBulkValidationFile(
+        fileData: Data,
+        fileType: BulkEmailValidationUploadURLRequest.FileType
+    ) async throws -> UploadBulkValidationFileResult {
+        // Request upload file URL
+        let uploadResponse = try await getBulkValidationUploadURL(fileType: fileType)
+
+        // Create a request to the upload URL
+        var request = HTTPClientRequest(url: uploadResponse.uploadUri)
+        request.method = .PUT  // Default to PUT method for S3 uploads
+
+        // Set the headers from the upload response
+        var headers = HTTPHeaders()
+        for headerData in uploadResponse.uploadHeaders {
+            headers.add(name: headerData.header, value: headerData.value)
+        }
+        request.headers = headers
+
+        // Add the file data as the request body
+        request.body = .bytes(fileData)
+
+        // Execute the request
+        let response = try await self.httpClient.execute(request, timeout: .seconds(180))
+
+        // Check if the upload was successful and return the job ID
+        return ((200...299).contains(response.status.code), uploadResponse.jobId)
+    }
+
+    /// Check the status of a bulk email validation job.
+    ///
+    /// - Parameter jobId: The ID of the bulk validation job.
+    /// - Returns: A ``ValidationJobResponse`` with information about the job status.
+    public func checkBulkValidationStatus(jobId: String) async throws -> ValidationJobResponse {
path: Sources/SendGridKit/SendGridEmailValidationClient.swift
user: login: fpseverino
body: I would probably just return `ValidationJobResponse.JobResponse.ValidationJobValue.Result`, which I would rename to `BulkEmailValidationJob.Response.Value.Result`
created_at: 2025-05-16T15:00:03Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,194 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest)
+
+            // Verify response properties exist
+            #expect((0.0...1.0).contains(response.result.score))
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                #expect(response.result.ipAddress != nil)
+            } else {
+                #expect(!response.result.checks.domain.hasMXOrARecord)
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(!response.result.isEmpty)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status")
+    func getBulkValidationJobStatus() async throws {
+        try await withKnownIssue {
+            let jobsResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: "12345")
+            let errors = jobsResponse.response.value.result.errors
+            #expect(errors?.isEmpty == true)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Upload CSV File")
+    func uploadCSVFile() async throws {
+        let csvContent = """
+            email
+            test1@example.com
+            test2@example.com
+            test3@example.com
+            """
+        let csvData = csvContent.data(using: .utf8)!
+
+        let _ = """
+            {
+              "job_id": "01H793APATD899ESMY25ZNPNCF",
+              "upload_uri": "https://example.com/",
+              "upload_headers": [
+                {
+                  "header": "x-amz-server-side-encryption",
+                  "value": "aws:kms"
+                },
+                {
+                  "header": "content-type",
+                  "value": "text/csv"
+                }
+              ]
+            }
+            """
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: thoven87
body: > What is this for?

This is an example response for `BulkEmailValidationUploadURLResponse` when `getBulkValidationUploadURL` is called
created_at: 2025-05-16T15:20:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,180 @@
+import AsyncHTTPClient
+import Foundation
+import NIO
+import NIOFoundationCompat
+import NIOHTTP1
+
+/// A client for validating emails using the SendGrid API.
+public struct SendGridEmailValidationClient: Sendable {
+    private let baseURL: String
+    private let httpClient: HTTPClient
+    private let apiKey: String
+
+    private let encoder: JSONEncoder = {
+        let encoder = JSONEncoder()
+        encoder.dateEncodingStrategy = .secondsSince1970
+        return encoder
+    }()
+
+    private let decoder: JSONDecoder = {
+        let decoder = JSONDecoder()
+        decoder.dateDecodingStrategy = .secondsSince1970
+        return decoder
+    }()
+
+    /// Initialize a new `SendGridClientEmailValidationClient`
+    ///
+    /// - Parameters:
+    ///   - httpClient: The `HTTPClient` to use for sending requests
+    ///   - apiKey: The SendGrid API key
+    ///   - forEU: Whether to use the API endpoint for global users and subusers or for EU regional subusers
+    public init(httpClient: HTTPClient, apiKey: String, forEU: Bool = false) {
+        self.httpClient = httpClient
+        self.apiKey = apiKey
+        self.baseURL = forEU ? "https://api.eu.sendgrid.com/v3" : "https://api.sendgrid.com/v3"
+    }
+
+    /// Validate an email address using the SendGrid Email Validation API.
+    ///
+    /// - Parameter validationRequest: The ``EmailValidationRequest`` containing the email to validate.
+    /// - Returns: An ``EmailValidationResponse`` with validation details.
+    public func validateEmail(_ validationRequest: EmailValidationRequest) async throws -> EmailValidationResponse {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email")
+        request.method = .POST
+        request.headers = headers
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(validationRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(EmailValidationResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    /// Request an upload URL for bulk email validation using the SendGrid Email Validation API.
+    ///
+    /// - Parameter fileType: The type of file to be uploaded (CSV or ZIP).
+    /// - Returns: A ``BulkValidationUploadURLResponse`` with the upload URL and details.
+    func getBulkValidationUploadURL(fileType: BulkEmailValidationUploadURLRequest.FileType) async throws
+        -> BulkEmailValidationUploadURLResponse
+    {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email/jobs")
+        request.method = .POST
+        request.headers = headers
+
+        let uploadRequest = BulkEmailValidationUploadURLRequest(fileType: fileType)
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(uploadRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(BulkEmailValidationUploadURLResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    public typealias UploadBulkValidationFileResult = (status: Bool, jobID: String)
+
+    /// Upload a file to the provided URL for bulk email validation.
+    ///
+    /// - Parameters:
+    ///   - fileData: The data of the file to upload (CSV or ZIP).
+    ///   - uploadResponse: The ``BulkValidationUploadURLResponse`` containing upload details.
+    /// - Returns: `true` if the upload was successful, and the job ID.
+    public func uploadBulkValidationFile(
+        fileData: Data,
+        fileType: BulkEmailValidationUploadURLRequest.FileType
path: Sources/SendGridKit/SendGridEmailValidationClient.swift
user: login: thoven87
body: > Let's make `FileType` a top level public struct, and `BulkEmailValidationUploadURLRequest` an internal one. That way the API is a bit nicer

I will rename `FileType` to `SendGridEmailValidationRequestFileType`
created_at: 2025-05-16T15:21:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,180 @@
+import AsyncHTTPClient
+import Foundation
+import NIO
+import NIOFoundationCompat
+import NIOHTTP1
+
+/// A client for validating emails using the SendGrid API.
+public struct SendGridEmailValidationClient: Sendable {
+    private let baseURL: String
+    private let httpClient: HTTPClient
+    private let apiKey: String
+
+    private let encoder: JSONEncoder = {
+        let encoder = JSONEncoder()
+        encoder.dateEncodingStrategy = .secondsSince1970
+        return encoder
+    }()
+
+    private let decoder: JSONDecoder = {
+        let decoder = JSONDecoder()
+        decoder.dateDecodingStrategy = .secondsSince1970
+        return decoder
+    }()
+
+    /// Initialize a new `SendGridClientEmailValidationClient`
+    ///
+    /// - Parameters:
+    ///   - httpClient: The `HTTPClient` to use for sending requests
+    ///   - apiKey: The SendGrid API key
+    ///   - forEU: Whether to use the API endpoint for global users and subusers or for EU regional subusers
+    public init(httpClient: HTTPClient, apiKey: String, forEU: Bool = false) {
+        self.httpClient = httpClient
+        self.apiKey = apiKey
+        self.baseURL = forEU ? "https://api.eu.sendgrid.com/v3" : "https://api.sendgrid.com/v3"
+    }
+
+    /// Validate an email address using the SendGrid Email Validation API.
+    ///
+    /// - Parameter validationRequest: The ``EmailValidationRequest`` containing the email to validate.
+    /// - Returns: An ``EmailValidationResponse`` with validation details.
+    public func validateEmail(_ validationRequest: EmailValidationRequest) async throws -> EmailValidationResponse {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email")
+        request.method = .POST
+        request.headers = headers
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(validationRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(EmailValidationResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    /// Request an upload URL for bulk email validation using the SendGrid Email Validation API.
+    ///
+    /// - Parameter fileType: The type of file to be uploaded (CSV or ZIP).
+    /// - Returns: A ``BulkValidationUploadURLResponse`` with the upload URL and details.
+    func getBulkValidationUploadURL(fileType: BulkEmailValidationUploadURLRequest.FileType) async throws
+        -> BulkEmailValidationUploadURLResponse
+    {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email/jobs")
+        request.method = .POST
+        request.headers = headers
+
+        let uploadRequest = BulkEmailValidationUploadURLRequest(fileType: fileType)
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(uploadRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(BulkEmailValidationUploadURLResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    public typealias UploadBulkValidationFileResult = (status: Bool, jobID: String)
+
+    /// Upload a file to the provided URL for bulk email validation.
+    ///
+    /// - Parameters:
+    ///   - fileData: The data of the file to upload (CSV or ZIP).
+    ///   - uploadResponse: The ``BulkValidationUploadURLResponse`` containing upload details.
+    /// - Returns: `true` if the upload was successful, and the job ID.
+    public func uploadBulkValidationFile(
+        fileData: Data,
+        fileType: BulkEmailValidationUploadURLRequest.FileType
path: Sources/SendGridKit/SendGridEmailValidationClient.swift
user: login: fpseverino
body: > I will rename `FileType` to `SendGridEmailValidationRequestFileType`

I think `FileType` by itself is fine. If you don't agree let's leave it as it is
created_at: 2025-05-16T15:24:33Z
author_association: MEMBER
diff_hunk: @@ -102,4 +107,194 @@ struct SendGridKitTests {
             credentialsAreInvalid
         }
     }
+
+    @Test("Email Validation")
+    func validateEmail() async throws {
+        let validationRequest = EmailValidationRequest(email: "test@example.com", source: "unit_test")
+
+        try await withKnownIssue {
+            let response = try await emailValidationClient.validateEmail(validationRequest)
+
+            // Verify response properties exist
+            #expect((0.0...1.0).contains(response.result.score))
+
+            // Basic assertions
+            if response.result.verdict == .valid {
+                #expect(response.result.ipAddress != nil)
+            } else {
+                #expect(!response.result.checks.domain.hasMXOrARecord)
+            }
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation jobs")
+    func getBulkValidationJobs() async throws {
+        try await withKnownIssue {
+            let response = try await emailValidationClient.getBulkEmailValidationJobs()
+            #expect(!response.result.isEmpty)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Get Bulk validation job status")
+    func getBulkValidationJobStatus() async throws {
+        try await withKnownIssue {
+            let jobsResponse = try await emailValidationClient.checkBulkValidationStatus(jobId: "12345")
+            let errors = jobsResponse.response.value.result.errors
+            #expect(errors?.isEmpty == true)
+        } when: {
+            credentialsAreInvalid
+        }
+    }
+
+    @Test("Upload CSV File")
+    func uploadCSVFile() async throws {
+        let csvContent = """
+            email
+            test1@example.com
+            test2@example.com
+            test3@example.com
+            """
+        let csvData = csvContent.data(using: .utf8)!
+
+        let _ = """
+            {
+              "job_id": "01H793APATD899ESMY25ZNPNCF",
+              "upload_uri": "https://example.com/",
+              "upload_headers": [
+                {
+                  "header": "x-amz-server-side-encryption",
+                  "value": "aws:kms"
+                },
+                {
+                  "header": "content-type",
+                  "value": "text/csv"
+                }
+              ]
+            }
+            """
path: Tests/SendGridKitTests/SendGridKitTests.swift
user: login: fpseverino
body: If it is not needed for the test itself let's remove it
created_at: 2025-05-16T15:25:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,180 @@
+import AsyncHTTPClient
+import Foundation
+import NIO
+import NIOFoundationCompat
+import NIOHTTP1
+
+/// A client for validating emails using the SendGrid API.
+public struct SendGridEmailValidationClient: Sendable {
+    private let baseURL: String
+    private let httpClient: HTTPClient
+    private let apiKey: String
+
+    private let encoder: JSONEncoder = {
+        let encoder = JSONEncoder()
+        encoder.dateEncodingStrategy = .secondsSince1970
+        return encoder
+    }()
+
+    private let decoder: JSONDecoder = {
+        let decoder = JSONDecoder()
+        decoder.dateDecodingStrategy = .secondsSince1970
+        return decoder
+    }()
+
+    /// Initialize a new `SendGridClientEmailValidationClient`
+    ///
+    /// - Parameters:
+    ///   - httpClient: The `HTTPClient` to use for sending requests
+    ///   - apiKey: The SendGrid API key
+    ///   - forEU: Whether to use the API endpoint for global users and subusers or for EU regional subusers
+    public init(httpClient: HTTPClient, apiKey: String, forEU: Bool = false) {
+        self.httpClient = httpClient
+        self.apiKey = apiKey
+        self.baseURL = forEU ? "https://api.eu.sendgrid.com/v3" : "https://api.sendgrid.com/v3"
+    }
+
+    /// Validate an email address using the SendGrid Email Validation API.
+    ///
+    /// - Parameter validationRequest: The ``EmailValidationRequest`` containing the email to validate.
+    /// - Returns: An ``EmailValidationResponse`` with validation details.
+    public func validateEmail(_ validationRequest: EmailValidationRequest) async throws -> EmailValidationResponse {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email")
+        request.method = .POST
+        request.headers = headers
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(validationRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(EmailValidationResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    /// Request an upload URL for bulk email validation using the SendGrid Email Validation API.
+    ///
+    /// - Parameter fileType: The type of file to be uploaded (CSV or ZIP).
+    /// - Returns: A ``BulkValidationUploadURLResponse`` with the upload URL and details.
+    func getBulkValidationUploadURL(fileType: BulkEmailValidationUploadURLRequest.FileType) async throws
+        -> BulkEmailValidationUploadURLResponse
+    {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email/jobs")
+        request.method = .POST
+        request.headers = headers
+
+        let uploadRequest = BulkEmailValidationUploadURLRequest(fileType: fileType)
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(uploadRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(BulkEmailValidationUploadURLResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    public typealias UploadBulkValidationFileResult = (status: Bool, jobID: String)
+
+    /// Upload a file to the provided URL for bulk email validation.
+    ///
+    /// - Parameters:
+    ///   - fileData: The data of the file to upload (CSV or ZIP).
+    ///   - uploadResponse: The ``BulkValidationUploadURLResponse`` containing upload details.
+    /// - Returns: `true` if the upload was successful, and the job ID.
+    public func uploadBulkValidationFile(
+        fileData: Data,
+        fileType: BulkEmailValidationUploadURLRequest.FileType
path: Sources/SendGridKit/SendGridEmailValidationClient.swift
user: login: fpseverino
body: > If you don't agree let's leave it as it is

I mean let's leave it as `BulkEmailValidationUploadURLRequest.FileType`, as it was before

created_at: 2025-05-16T15:26:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,180 @@
+import AsyncHTTPClient
+import Foundation
+import NIO
+import NIOFoundationCompat
+import NIOHTTP1
+
+/// A client for validating emails using the SendGrid API.
+public struct SendGridEmailValidationClient: Sendable {
+    private let baseURL: String
+    private let httpClient: HTTPClient
+    private let apiKey: String
+
+    private let encoder: JSONEncoder = {
+        let encoder = JSONEncoder()
+        encoder.dateEncodingStrategy = .secondsSince1970
+        return encoder
+    }()
+
+    private let decoder: JSONDecoder = {
+        let decoder = JSONDecoder()
+        decoder.dateDecodingStrategy = .secondsSince1970
+        return decoder
+    }()
+
+    /// Initialize a new `SendGridClientEmailValidationClient`
+    ///
+    /// - Parameters:
+    ///   - httpClient: The `HTTPClient` to use for sending requests
+    ///   - apiKey: The SendGrid API key
+    ///   - forEU: Whether to use the API endpoint for global users and subusers or for EU regional subusers
+    public init(httpClient: HTTPClient, apiKey: String, forEU: Bool = false) {
+        self.httpClient = httpClient
+        self.apiKey = apiKey
+        self.baseURL = forEU ? "https://api.eu.sendgrid.com/v3" : "https://api.sendgrid.com/v3"
+    }
+
+    /// Validate an email address using the SendGrid Email Validation API.
+    ///
+    /// - Parameter validationRequest: The ``EmailValidationRequest`` containing the email to validate.
+    /// - Returns: An ``EmailValidationResponse`` with validation details.
+    public func validateEmail(_ validationRequest: EmailValidationRequest) async throws -> EmailValidationResponse {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email")
+        request.method = .POST
+        request.headers = headers
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(validationRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(EmailValidationResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    /// Request an upload URL for bulk email validation using the SendGrid Email Validation API.
+    ///
+    /// - Parameter fileType: The type of file to be uploaded (CSV or ZIP).
+    /// - Returns: A ``BulkValidationUploadURLResponse`` with the upload URL and details.
+    func getBulkValidationUploadURL(fileType: BulkEmailValidationUploadURLRequest.FileType) async throws
+        -> BulkEmailValidationUploadURLResponse
+    {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email/jobs")
+        request.method = .POST
+        request.headers = headers
+
+        let uploadRequest = BulkEmailValidationUploadURLRequest(fileType: fileType)
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(uploadRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(BulkEmailValidationUploadURLResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    public typealias UploadBulkValidationFileResult = (status: Bool, jobID: String)
+
+    /// Upload a file to the provided URL for bulk email validation.
+    ///
+    /// - Parameters:
+    ///   - fileData: The data of the file to upload (CSV or ZIP).
+    ///   - uploadResponse: The ``BulkValidationUploadURLResponse`` containing upload details.
+    /// - Returns: `true` if the upload was successful, and the job ID.
+    public func uploadBulkValidationFile(
+        fileData: Data,
+        fileType: BulkEmailValidationUploadURLRequest.FileType
path: Sources/SendGridKit/SendGridEmailValidationClient.swift
user: login: thoven87
body: The reason why I want to rename it to something else if we move it to the top level, I find Swift to be lack  name space. It'll be confusing if another FileType is already defined.
created_at: 2025-05-16T15:27:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,180 @@
+import AsyncHTTPClient
+import Foundation
+import NIO
+import NIOFoundationCompat
+import NIOHTTP1
+
+/// A client for validating emails using the SendGrid API.
+public struct SendGridEmailValidationClient: Sendable {
+    private let baseURL: String
+    private let httpClient: HTTPClient
+    private let apiKey: String
+
+    private let encoder: JSONEncoder = {
+        let encoder = JSONEncoder()
+        encoder.dateEncodingStrategy = .secondsSince1970
+        return encoder
+    }()
+
+    private let decoder: JSONDecoder = {
+        let decoder = JSONDecoder()
+        decoder.dateDecodingStrategy = .secondsSince1970
+        return decoder
+    }()
+
+    /// Initialize a new `SendGridClientEmailValidationClient`
+    ///
+    /// - Parameters:
+    ///   - httpClient: The `HTTPClient` to use for sending requests
+    ///   - apiKey: The SendGrid API key
+    ///   - forEU: Whether to use the API endpoint for global users and subusers or for EU regional subusers
+    public init(httpClient: HTTPClient, apiKey: String, forEU: Bool = false) {
+        self.httpClient = httpClient
+        self.apiKey = apiKey
+        self.baseURL = forEU ? "https://api.eu.sendgrid.com/v3" : "https://api.sendgrid.com/v3"
+    }
+
+    /// Validate an email address using the SendGrid Email Validation API.
+    ///
+    /// - Parameter validationRequest: The ``EmailValidationRequest`` containing the email to validate.
+    /// - Returns: An ``EmailValidationResponse`` with validation details.
+    public func validateEmail(_ validationRequest: EmailValidationRequest) async throws -> EmailValidationResponse {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email")
+        request.method = .POST
+        request.headers = headers
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(validationRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(EmailValidationResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    /// Request an upload URL for bulk email validation using the SendGrid Email Validation API.
+    ///
+    /// - Parameter fileType: The type of file to be uploaded (CSV or ZIP).
+    /// - Returns: A ``BulkValidationUploadURLResponse`` with the upload URL and details.
+    func getBulkValidationUploadURL(fileType: BulkEmailValidationUploadURLRequest.FileType) async throws
+        -> BulkEmailValidationUploadURLResponse
+    {
+        var headers = HTTPHeaders()
+        headers.add(name: "Authorization", value: "Bearer \(apiKey)")
+        headers.add(name: "Content-Type", value: "application/json")
+        headers.add(name: "User-Agent", value: "Swift SendGridKit/3.0.0")
+
+        var request = HTTPClientRequest(url: "\(self.baseURL)/validations/email/jobs")
+        request.method = .POST
+        request.headers = headers
+
+        let uploadRequest = BulkEmailValidationUploadURLRequest(fileType: fileType)
+        request.body = try HTTPClientRequest.Body.bytes(self.encoder.encode(uploadRequest))
+
+        let response = try await self.httpClient.execute(request, timeout: .seconds(30))
+
+        // If the request is successful, decode the response
+        if (200...299).contains(response.status.code) {
+            let body = try await response.body.collect(upTo: 1024 * 1024)
+            return try self.decoder.decode(BulkEmailValidationUploadURLResponse.self, from: body)
+        }
+
+        // Otherwise, decode the error
+        throw try await self.decoder.decode(SendGridError.self, from: response.body.collect(upTo: 1024 * 1024))
+    }
+
+    public typealias UploadBulkValidationFileResult = (status: Bool, jobID: String)
+
+    /// Upload a file to the provided URL for bulk email validation.
+    ///
+    /// - Parameters:
+    ///   - fileData: The data of the file to upload (CSV or ZIP).
+    ///   - uploadResponse: The ``BulkValidationUploadURLResponse`` containing upload details.
+    /// - Returns: `true` if the upload was successful, and the job ID.
+    public func uploadBulkValidationFile(
+        fileData: Data,
+        fileType: BulkEmailValidationUploadURLRequest.FileType
path: Sources/SendGridKit/SendGridEmailValidationClient.swift
user: login: fpseverino
body: All right, so let's leave it as it was before
created_at: 2025-05-16T15:34:22Z
author_association: MEMBER

PR comments:
user: login: codecov[bot]
created_at: 2025-05-14T23:07:24Z
body: ## [Codecov](https://app.codecov.io/gh/vapor-community/sendgrid-kit/pull/26?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vapor-community) Report
Attention: Patch coverage is `77.23577%` with `28 lines` in your changes missing coverage. Please review.
> Project coverage is 89.47%. Comparing base [(`078282f`)](https://app.codecov.io/gh/vapor-community/sendgrid-kit/commit/078282f1a4be74a965fec249af5c6b536c4ffb89?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vapor-community) to head [(`ee49673`)](https://app.codecov.io/gh/vapor-community/sendgrid-kit/commit/ee49673e06db3e156eb832cf88126b6f23e15be2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vapor-community).
> Report is 1 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/vapor-community/sendgrid-kit/pull/26?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vapor-community) | Patch % | Lines |
|---|---|---|
| [...es/SendGridKit/SendGridEmailValidationClient.swift](https://app.codecov.io/gh/vapor-community/sendgrid-kit/pull/26?src=pr&el=tree&filepath=Sources%2FSendGridKit%2FSendGridEmailValidationClient.swift&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vapor-community#diff-U291cmNlcy9TZW5kR3JpZEtpdC9TZW5kR3JpZEVtYWlsVmFsaWRhdGlvbkNsaWVudC5zd2lmdA==) | 75.65% | [28 Missing :warning: ](https://app.codecov.io/gh/vapor-community/sendgrid-kit/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vapor-community) |

> :exclamation:  There is a different number of reports uploaded between BASE (078282f) and HEAD (ee49673). Click for more details.
> 
> <details><summary>HEAD has 1 upload less than BASE</summary>
>
>| Flag | BASE (078282f) | HEAD (ee49673) |
>|------|------|------|
>||3|2|
></details>

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##              main      #26       +/-   ##
============================================
- Coverage   100.00%   89.47%   -10.53%     
============================================
  Files            9       12        +3     
  Lines          145      266      +121     
============================================
+ Hits           145      238       +93     
- Misses           0       28       +28     
```

| [Files with missing lines](https://app.codecov.io/gh/vapor-community/sendgrid-kit/pull/26?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vapor-community) | Coverage Δ | |
|---|---|---|
| [...urces/SendGridKit/Models/BulkEmailValidation.swift](https://app.codecov.io/gh/vapor-community/sendgrid-kit/pull/26?src=pr&el=tree&filepath=Sources%2FSendGridKit%2FModels%2FBulkEmailValidation.swift&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vapor-community#diff-U291cmNlcy9TZW5kR3JpZEtpdC9Nb2RlbHMvQnVsa0VtYWlsVmFsaWRhdGlvbi5zd2lmdA==) | `100.00% <100.00%> (ø)` | |
| [...s/SendGridKit/Models/RealtimeEmailValidation.swift](https://app.codecov.io/gh/vapor-community/sendgrid-kit/pull/26?src=pr&el=tree&filepath=Sources%2FSendGridKit%2FModels%2FRealtimeEmailValidation.swift&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vapor-community#diff-U291cmNlcy9TZW5kR3JpZEtpdC9Nb2RlbHMvUmVhbHRpbWVFbWFpbFZhbGlkYXRpb24uc3dpZnQ=) | `100.00% <100.00%> (ø)` | |
| [Sources/SendGridKit/SendGridClient.swift](https://app.codecov.io/gh/vapor-community/sendgrid-kit/pull/26?src=pr&el=tree&filepath=Sources%2FSendGridKit%2FSendGridClient.swift&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vapor-community#diff-U291cmNlcy9TZW5kR3JpZEtpdC9TZW5kR3JpZENsaWVudC5zd2lmdA==) | `100.00% <100.00%> (ø)` | |
| [...es/SendGridKit/SendGridEmailValidationClient.swift](https://app.codecov.io/gh/vapor-community/sendgrid-kit/pull/26?src=pr&el=tree&filepath=Sources%2FSendGridKit%2FSendGridEmailValidationClient.swift&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vapor-community#diff-U291cmNlcy9TZW5kR3JpZEtpdC9TZW5kR3JpZEVtYWlsVmFsaWRhdGlvbkNsaWVudC5zd2lmdA==) | `75.65% <75.65%> (ø)` | |

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: thoven87
created_at: 2025-05-14T23:10:38Z
body: Phew -40% in code coverage dropped? Let's see if I can fix that :(
author_association: CONTRIBUTOR
user: login: thoven87
created_at: 2025-05-15T00:46:18Z
body: > Great job, I am very excited to add this API to the library! I left some comments, thank you very much for the PR!

Thanks @fpseverino I will address the rest comments in a few.
author_association: CONTRIBUTOR
user: login: thoven87
created_at: 2025-05-15T11:05:33Z
body: > Great job, I am very excited to add this API to the library! I left some comments, thank you very much for the PR!

Scratch what I said previously. How do we update `uploadBulkValidationFile` for better code coverage? Or are you ok with the current drop in code coverage?
author_association: CONTRIBUTOR
user: login: thoven87
created_at: 2025-05-16T18:08:38Z
body: > OK, let's merge it!
> 
> 
> 
> A PR of mine will follow, before the official release, in which I will update the DocC documentation and other such things.
> 
> I warn you that I may change the names of some methods or structures.
> 
> 
> 
> Thank you once again!

Unfortunately, you'll need to merge. I don't have permissions todo so.
author_association: CONTRIBUTOR