Skip to content

Commit 3bf94d1

Browse files
Add EmailListAsync method (#71)
1 parent 8ee0029 commit 3bf94d1

File tree

9 files changed

+251
-2
lines changed

9 files changed

+251
-2
lines changed

src/Resend/EmailStatus.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,10 @@ public enum EmailStatus
7070
/// </summary>
7171
[JsonStringValue( "scheduled" )]
7272
Scheduled,
73+
74+
/// <summary>
75+
/// Email delivery failed.
76+
/// </summary>
77+
[JsonStringValue( "failed" )]
78+
Failed,
7379
}

src/Resend/IResend.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,21 @@ public interface IResend
5757
Task<ResendResponse<EmailReceipt>> EmailRetrieveAsync( Guid emailId, CancellationToken cancellationToken = default );
5858

5959

60+
/// <summary>
61+
/// Lists a page of email receipts.
62+
/// </summary>
63+
/// <param name="query">
64+
/// Pagination query.
65+
/// </param>
66+
/// <param name="cancellationToken">
67+
/// Cancellation token.
68+
/// </param>
69+
/// <returns>
70+
/// List of email receipts.
71+
/// </returns>
72+
Task<ResendResponse<PaginatedResult<EmailReceipt>>> EmailListAsync( PaginatedQuery? query = null, CancellationToken cancellationToken = default );
73+
74+
6075
/// <summary>
6176
/// Send a batch of emails, if and only if all messages are valid.
6277
/// </summary>

src/Resend/Pagination.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Resend;
4+
5+
6+
/// <summary />
7+
public class PaginatedQuery
8+
{
9+
/// <summary>
10+
/// Number of records to be returned.
11+
/// </summary>
12+
/// <remarks>
13+
/// Minimum 1, Default 20, Maximum 100.
14+
/// </remarks>
15+
public int? Limit { get; set; }
16+
17+
/// <summary>
18+
/// A cursor value after which records shall be returned.
19+
/// </summary>
20+
/// <remarks>
21+
/// A resource which matches this value shall not be included
22+
/// in the results.
23+
/// </remarks>
24+
public string? Before { get; set; }
25+
26+
/// <summary>
27+
/// A cursor value before which records shall be returned.
28+
/// </summary>
29+
/// <remarks>
30+
/// A resource which matches this value shall not be included
31+
/// in the results.
32+
/// </remarks>
33+
public string? After { get; set; }
34+
}
35+
36+
37+
/// <summary />
38+
public class PaginatedResult<T>
39+
{
40+
/// <summary>
41+
/// Whether there are any more resources beyond the requested limit.
42+
/// </summary>
43+
[JsonPropertyName( "has_more" )]
44+
public required bool HasMore { get; set; }
45+
46+
/// <summary>
47+
/// Page of resources matching the query criteria.
48+
/// </summary>
49+
[JsonPropertyName( "data" )]
50+
public required List<T> Data { get; set; }
51+
}

src/Resend/Resend.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
</PropertyGroup>
1515

1616
<ItemGroup>
17+
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.19" />
1718
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" />
1819
</ItemGroup>
1920

src/Resend/ResendClient.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.Extensions.Options;
1+
using Microsoft.AspNetCore.WebUtilities;
2+
using Microsoft.Extensions.Options;
23
using Resend.Payloads;
34
using System.Net;
45
using System.Net.Http.Headers;
@@ -93,6 +94,34 @@ public ResendClient( IOptionsSnapshot<ResendClientOptions> options, HttpClient h
9394
}
9495

9596

97+
/// <inheritdoc />
98+
public Task<ResendResponse<PaginatedResult<EmailReceipt>>> EmailListAsync( PaginatedQuery? query = null, CancellationToken cancellationToken = default )
99+
{
100+
var baseUrl = "/emails";
101+
var url = baseUrl;
102+
103+
if ( query != null )
104+
{
105+
var qs = new Dictionary<string, string?>();
106+
107+
if ( query.Limit.HasValue == true )
108+
qs.Add( "limit", query.Limit.Value.ToString() );
109+
110+
if ( query.Before != null )
111+
qs.Add( "before", query.Before );
112+
113+
if ( query.After != null )
114+
qs.Add( "after", query.After );
115+
116+
url = QueryHelpers.AddQueryString( baseUrl, qs );
117+
}
118+
119+
var req = new HttpRequestMessage( HttpMethod.Get, url );
120+
121+
return Execute<PaginatedResult<EmailReceipt>, PaginatedResult<EmailReceipt>>( req, ( x ) => x, cancellationToken );
122+
}
123+
124+
96125
/// <inheritdoc />
97126
public Task<ResendResponse<List<Guid>>> EmailBatchAsync( IEnumerable<EmailMessage> emails, CancellationToken cancellationToken = default )
98127
{

tests/Resend.Tests/ResendClientTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,23 @@ public async Task EmailRetrieve()
6363
}
6464

6565

66+
/// <summary />
67+
[Fact]
68+
public async Task EmailList()
69+
{
70+
var resp = await _resend.EmailListAsync( new PaginatedQuery()
71+
{
72+
Limit = 20,
73+
After = Guid.NewGuid().ToString(),
74+
} );
75+
76+
Assert.NotNull( resp );
77+
Assert.True( resp.Success );
78+
Assert.NotNull( resp.Content );
79+
Assert.True( resp.Content.HasMore );
80+
}
81+
82+
6683
/// <summary />
6784
[Fact]
6885
public async Task EmailBatch()

tools/Resend.ApiServer/Controllers/EmailController.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,49 @@ public EmailReceipt EmailRetrieve( [FromRoute] Guid id )
5959
}
6060

6161

62+
/// <summary />
63+
[HttpGet]
64+
[Route( "emails" )]
65+
public PaginatedResult<EmailReceipt> EmailList(
66+
[FromQuery( Name = "limit" )] int? limit,
67+
[FromQuery( Name = "after" )] string? after,
68+
[FromQuery( Name = "before" )] string? before
69+
)
70+
{
71+
_logger.LogDebug( "EmailList" );
72+
73+
var pr = new PaginatedResult<EmailReceipt>()
74+
{
75+
HasMore = true,
76+
Data = new List<EmailReceipt>(),
77+
};
78+
79+
pr.Data.Add( new EmailReceipt()
80+
{
81+
Id = Guid.NewGuid(),
82+
Subject = "Demo #1",
83+
84+
85+
HtmlBody = "This is HTML!",
86+
MomentCreated = DateTime.UtcNow,
87+
LastEvent = EmailStatus.Delivered,
88+
} );
89+
90+
pr.Data.Add( new EmailReceipt()
91+
{
92+
Id = Guid.NewGuid(),
93+
Subject = "Demo #2",
94+
95+
96+
HtmlBody = "This is HTML!",
97+
MomentCreated = DateTime.UtcNow,
98+
LastEvent = EmailStatus.Delivered,
99+
} );
100+
101+
return pr;
102+
}
103+
104+
62105
/// <summary />
63106
[HttpPost]
64107
[Route( "emails/batch" )]
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using McMaster.Extensions.CommandLineUtils;
2+
using Spectre.Console;
3+
using System.Text.Json;
4+
5+
namespace Resend.Cli.Email;
6+
7+
/// <summary />
8+
[Command( "list", Description = "Lists emails which were previously sent" )]
9+
public class EmailListCommand
10+
{
11+
private readonly IResend _resend;
12+
13+
14+
/// <summary />
15+
[Argument( 0, Description = "Number of emails" )]
16+
public int? Limit { get; set; }
17+
18+
/// <summary />
19+
[Option( "-b|--before", CommandOptionType.SingleValue, Description = "Emails sent before id" )]
20+
public string? BeforeId { get; set; }
21+
22+
/// <summary />
23+
[Option( "-a|--after", CommandOptionType.SingleValue, Description = "Emails sent after id" )]
24+
public string? AfterId { get; set; }
25+
26+
/// <summary />
27+
[Option( "-j|--json", CommandOptionType.NoValue, Description = "Emit output as JSON array" )]
28+
public bool InJson { get; set; }
29+
30+
31+
/// <summary />
32+
public EmailListCommand( IResend resend )
33+
{
34+
_resend = resend;
35+
}
36+
37+
38+
/// <summary />
39+
public async Task<int> OnExecuteAsync()
40+
{
41+
var q = new PaginatedQuery()
42+
{
43+
Limit = this.Limit,
44+
Before = this.BeforeId,
45+
After = this.AfterId,
46+
};
47+
48+
var res = await _resend.EmailListAsync( q );
49+
var results = res.Content;
50+
51+
52+
/*
53+
*
54+
*/
55+
if ( this.InJson == true )
56+
{
57+
var jso = new JsonSerializerOptions() { WriteIndented = true, };
58+
59+
var json = JsonSerializer.Serialize( results, jso );
60+
Console.WriteLine( json );
61+
}
62+
else
63+
{
64+
var table = new Table();
65+
table.Border = TableBorder.SimpleHeavy;
66+
table.AddColumn( "Email Id" );
67+
table.AddColumn( "Subject" );
68+
table.AddColumn( "Date" );
69+
table.AddColumn( "Last Event" );
70+
71+
foreach ( var d in results.Data )
72+
{
73+
table.AddRow(
74+
new Markup( d.Id.ToString() ),
75+
new Markup( d.Subject ),
76+
new Markup( d.MomentCreated.ToString( "yyyy-MM-dd" ) ),
77+
new Markup( d.LastEvent?.ToString() ?? "" )
78+
);
79+
}
80+
81+
AnsiConsole.Write( table );
82+
}
83+
84+
return 0;
85+
}
86+
}

tools/Resend.Cli/EmailCommand.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace Resend.Cli;
66
[Command( "email", Description = "Send emails" )]
77
[Subcommand( typeof( Email.EmailBatchCommand ) )]
88
[Subcommand( typeof( Email.EmailCancelCommand ) )]
9+
[Subcommand( typeof( Email.EmailListCommand ) )]
910
[Subcommand( typeof( Email.EmailRescheduleCommand ) )]
1011
[Subcommand( typeof( Email.EmailRetrieveCommand ) )]
1112
[Subcommand( typeof( Email.EmailSendCommand ) )]
@@ -17,4 +18,4 @@ public int OnExecute( CommandLineApplication app )
1718
app.ShowHelp();
1819
return 1;
1920
}
20-
}
21+
}

0 commit comments

Comments
 (0)