Skip to content

Commit 8552d68

Browse files
authored
Merge pull request #3 from hsharghi/scheduled
Add scheduledAt parameter to send email
2 parents 65e5366 + 180217e commit 8552d68

File tree

5 files changed

+118
-23
lines changed

5 files changed

+118
-23
lines changed

README.md

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,29 +41,51 @@ You can send a single email by creating a `ResendEmail` object and retrieving th
4141
~~~~swift
4242
import Resend
4343

44-
let email: ResendEmail = .init(
45-
from: .init(email: "hadi@example.com", name: "Hadi"),
46-
to: ["hadi@domain.com"],
47-
subject: "running xctest",
48-
replyTo: [
49-
"hadi@example.com",
50-
"manager@example.com"
51-
],
52-
text: "sending email from XCTest suit",
53-
headers: [
54-
.init(name: "X-Entity-Ref-ID", value: "234H3-44"),
55-
.init(name: "X-Entity-Dep-ID", value: "SALE-03"),
56-
],
57-
attachments: [
58-
.init(content: .init(data: .init(contentsOf: .init(filePath: "path/to/a/file"))),
59-
filename: "sales.xlsx")
60-
],
61-
tags: [
62-
.init(name: "priority", value: "medium"),
63-
.init(name: "department", value: "sales")
64-
]
65-
)
44+
let email = ResendEmail(
45+
from: .init(email: "hadi@example.com", name: "Hadi"),
46+
to: ["hadi@domain.com"],
47+
subject: "running xctest",
48+
replyTo: [
49+
"hadi@example.com",
50+
"manager@example.com"
51+
],
52+
text: "sending email via Resend API",
53+
headers: [
54+
.init(name: "X-Entity-Ref-ID", value: "234H3-44"),
55+
.init(name: "X-Entity-Dep-ID", value: "SALE-03"),
56+
],
57+
attachments: [
58+
.init(content: .init(data: .init(contentsOf: .init(filePath: "path/to/a/file"))),
59+
filename: "sales.xlsx")
60+
],
61+
tags: [
62+
.init(name: "priority", value: "medium"),
63+
.init(name: "department", value: "sales")
64+
]
65+
)
66+
~~~~
67+
68+
Emails can be scheduled to be sent later. The date should be a `Date` object or a string in natural language (e.g.: in 1 min)
6669

70+
~~~~swift
71+
let email = ResendEmail(
72+
to: ["hadi@domain.com"],
73+
subject: "sending scheduled email",
74+
scheduledAt: "in an hour",
75+
text: "sending email via Resend API",
76+
)
77+
78+
let email = ResendEmail(
79+
to: ["hadi@domain.com"],
80+
subject: "sending scheduled email",
81+
scheduledAt: .date(Date(timeIntervalSinceNow: 60\*60)),
82+
text: "sending email via Resend API",
83+
)
84+
~~~~
85+
86+
Now the email can be sent using resend client
87+
88+
~~~~swift
6789
let id = try await resendClient.emails.send(email)
6890
~~~~
6991
`ResendEmail` supports both `text` and `html` content.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// EmailSchedule.swift
3+
// swift-resend
4+
//
5+
// Created by Hadi Sharghi on 7/2/25.
6+
//
7+
8+
import Foundation
9+
10+
public enum EmailSchedule {
11+
case date(Date)
12+
case string(String)
13+
}
14+
15+
extension EmailSchedule: ExpressibleByStringLiteral {
16+
public init(stringLiteral value: String) {
17+
self = .string(value)
18+
}
19+
}
20+
21+
22+
// Custom Codable implementation
23+
extension EmailSchedule: Codable {
24+
public func encode(to encoder: Encoder) throws {
25+
var container = encoder.singleValueContainer()
26+
27+
switch self {
28+
case .date(let date):
29+
// Encode Date as ISO 8601 string
30+
let formatter = ISO8601DateFormatter()
31+
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
32+
try container.encode(formatter.string(from: date))
33+
case .string(let string):
34+
// Encode String directly
35+
try container.encode(string)
36+
}
37+
}
38+
}

Sources/Resend/Models/Request/Email/ResendEmail.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public struct ResendEmail {
2323
/// An array of recipients who will receive a blind carbon copy of your email. Each object within this array may contain the name, but must always contain the email, of a recipient.
2424
public var bcc: [EmailAddress]?
2525

26+
/// Schedule email to be sent later. The date should be in natural language (e.g.: in 1 min) or ISO 8601 format (e.g: 2024-08-05T11:52:01.858Z).
27+
public var scheduledAt: EmailSchedule?
28+
2629
/// An array of recipients who will receive replies and/or bounces.
2730
public var replyTo: [EmailAddress]?
2831

@@ -46,6 +49,7 @@ public struct ResendEmail {
4649
subject: String,
4750
cc: [EmailAddress]? = nil,
4851
bcc: [EmailAddress]? = nil,
52+
scheduledAt: EmailSchedule? = nil,
4953
replyTo: [EmailAddress]? = nil,
5054
text: String? = nil,
5155
html: String? = nil,
@@ -57,6 +61,7 @@ public struct ResendEmail {
5761
self.subject = subject
5862
self.cc = cc
5963
self.bcc = bcc
64+
self.scheduledAt = scheduledAt
6065
self.replyTo = replyTo
6166
self.text = text
6267
self.html = html
@@ -72,6 +77,7 @@ public struct ResendEmail {
7277
case subject
7378
case cc
7479
case bcc
80+
case scheduledAt = "scheduled_at"
7581
case replyTo = "reply_to"
7682
case text
7783
case html
@@ -91,6 +97,7 @@ extension ResendEmail: Encodable {
9197
try container.encodeIfPresent(subject, forKey: .subject)
9298
try container.encodeIfPresent(cc?.stringArray, forKey: .cc)
9399
try container.encodeIfPresent(bcc?.stringArray, forKey: .bcc)
100+
try container.encodeIfPresent(scheduledAt, forKey: .scheduledAt)
94101
try container.encodeIfPresent(replyTo?.stringArray, forKey: .replyTo)
95102
try container.encodeIfPresent(text, forKey: .text)
96103
try container.encodeIfPresent(html, forKey: .html)

Tests/SwiftResendTests/ResendError+ Equatable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import Foundation
99
@testable import Resend
1010

11-
extension ResendError: Equatable {
11+
extension ResendError: @retroactive Equatable {
1212
public static func == (lhs: ResendError, rhs: ResendError) -> Bool {
1313
lhs.identifier == rhs.identifier
1414
}

Tests/SwiftResendTests/SwiftResendTests.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,34 @@ final class SwiftResendTests: XCTestCase {
4444
XCTAssertNotNil(id)
4545
}
4646

47+
func testSendWithScheduleInNaturalLanuguage() async throws {
48+
let id = try await resend.emails.send(email: .init(
49+
from: .init(email: "hadi@softworks.ir", name: "Hadi"),
50+
to: ["hsharghi@gmail.com"],
51+
subject: "send later",
52+
scheduledAt: "in an hour",
53+
text: "sending email from XCTest suit with schedule",
54+
))
55+
XCTAssertNotNil(id)
56+
57+
let info = try await resend.emails.get(emailId: id)
58+
XCTAssertEqual("scheduled", info.lastEvent)
59+
}
60+
61+
func testSendWithScheduleWithDate() async throws {
62+
let id = try await resend.emails.send(email: .init(
63+
from: .init(email: "hadi@softworks.ir", name: "Hadi"),
64+
to: ["hsharghi@gmail.com"],
65+
subject: "send later",
66+
scheduledAt: .date(Date(timeIntervalSinceNow: 60*60)),
67+
text: "sending email from XCTest suit with schedule",
68+
))
69+
XCTAssertNotNil(id)
70+
71+
let info = try await resend.emails.get(emailId: id)
72+
XCTAssertEqual("scheduled", info.lastEvent)
73+
}
74+
4775
func testSendBatch() async throws {
4876
let response = try await resend.emails.sendBatch(emails: [
4977
.init(

0 commit comments

Comments
 (0)