diff --git a/src/Resend/Automation.cs b/src/Resend/Automation.cs
new file mode 100644
index 0000000..b28df6f
--- /dev/null
+++ b/src/Resend/Automation.cs
@@ -0,0 +1,59 @@
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Full automation resource including graph details.
+///
+public class Automation
+{
+ ///
+ /// Object type discriminator.
+ ///
+ [JsonPropertyName( "object" )]
+ public string Object { get; set; } = default!;
+
+ ///
+ /// Automation identifier.
+ ///
+ [JsonPropertyName( "id" )]
+ public Guid Id { get; set; }
+
+ ///
+ /// Display name.
+ ///
+ [JsonPropertyName( "name" )]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// Either enabled or disabled.
+ ///
+ [JsonPropertyName( "status" )]
+ public string Status { get; set; } = default!;
+
+ ///
+ /// When the automation was created.
+ ///
+ [JsonPropertyName( "created_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime MomentCreated { get; set; }
+
+ ///
+ /// When the automation was last updated.
+ ///
+ [JsonPropertyName( "updated_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime MomentUpdated { get; set; }
+
+ ///
+ /// Steps in execution order as stored by the API.
+ ///
+ [JsonPropertyName( "steps" )]
+ public List Steps { get; set; } = default!;
+
+ ///
+ /// Edges connecting steps.
+ ///
+ [JsonPropertyName( "edges" )]
+ public List Edges { get; set; } = default!;
+}
diff --git a/src/Resend/AutomationCreateData.cs b/src/Resend/AutomationCreateData.cs
new file mode 100644
index 0000000..97cb85e
--- /dev/null
+++ b/src/Resend/AutomationCreateData.cs
@@ -0,0 +1,34 @@
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Request body to create an automation.
+///
+public class AutomationCreateData
+{
+ ///
+ /// Display name.
+ ///
+ [JsonPropertyName( "name" )]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// Either enabled or disabled. Defaults to disabled when omitted.
+ ///
+ [JsonPropertyName( "status" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public string? Status { get; set; }
+
+ ///
+ /// Steps; must include at least one trigger step.
+ ///
+ [JsonPropertyName( "steps" )]
+ public List Steps { get; set; } = default!;
+
+ ///
+ /// Edges between steps (may be empty for single-step automations).
+ ///
+ [JsonPropertyName( "edges" )]
+ public List Edges { get; set; } = default!;
+}
diff --git a/src/Resend/AutomationDeleteResult.cs b/src/Resend/AutomationDeleteResult.cs
new file mode 100644
index 0000000..f2fff06
--- /dev/null
+++ b/src/Resend/AutomationDeleteResult.cs
@@ -0,0 +1,27 @@
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Response from deleting an automation.
+///
+public class AutomationDeleteResult
+{
+ ///
+ /// Object type discriminator.
+ ///
+ [JsonPropertyName( "object" )]
+ public string Object { get; set; } = default!;
+
+ ///
+ /// Automation identifier.
+ ///
+ [JsonPropertyName( "id" )]
+ public Guid Id { get; set; }
+
+ ///
+ /// Whether the automation was deleted.
+ ///
+ [JsonPropertyName( "deleted" )]
+ public bool Deleted { get; set; }
+}
diff --git a/src/Resend/AutomationEdge.cs b/src/Resend/AutomationEdge.cs
new file mode 100644
index 0000000..ac19d5d
--- /dev/null
+++ b/src/Resend/AutomationEdge.cs
@@ -0,0 +1,32 @@
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Connection between two automation steps.
+///
+///
+/// On create and update, and are step refs.
+/// On retrieve, they are internal step identifiers.
+///
+public class AutomationEdge
+{
+ ///
+ /// Source step ref or internal id.
+ ///
+ [JsonPropertyName( "from" )]
+ public string From { get; set; } = default!;
+
+ ///
+ /// Destination step ref or internal id.
+ ///
+ [JsonPropertyName( "to" )]
+ public string To { get; set; } = default!;
+
+ ///
+ /// Edge kind: default, condition_met, condition_not_met, timeout, event_received.
+ ///
+ [JsonPropertyName( "edge_type" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public string? EdgeType { get; set; }
+}
diff --git a/src/Resend/AutomationListQuery.cs b/src/Resend/AutomationListQuery.cs
new file mode 100644
index 0000000..4c71bbc
--- /dev/null
+++ b/src/Resend/AutomationListQuery.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Query parameters for .
+///
+public class AutomationListQuery : PaginatedQuery
+{
+ ///
+ /// Filter by enabled or disabled.
+ ///
+ [JsonIgnore]
+ public string? Status { get; set; }
+}
diff --git a/src/Resend/AutomationRun.cs b/src/Resend/AutomationRun.cs
new file mode 100644
index 0000000..0ce24b9
--- /dev/null
+++ b/src/Resend/AutomationRun.cs
@@ -0,0 +1,54 @@
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// A single automation run with per-step execution details.
+///
+public class AutomationRun
+{
+ ///
+ /// Object type discriminator.
+ ///
+ [JsonPropertyName( "object" )]
+ public string Object { get; set; } = default!;
+
+ ///
+ /// Run identifier.
+ ///
+ [JsonPropertyName( "id" )]
+ public Guid Id { get; set; }
+
+ ///
+ /// Run status.
+ ///
+ [JsonPropertyName( "status" )]
+ public string Status { get; set; } = default!;
+
+ ///
+ /// When the run started.
+ ///
+ [JsonPropertyName( "started_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime MomentStarted { get; set; }
+
+ ///
+ /// When the run finished, if applicable.
+ ///
+ [JsonPropertyName( "completed_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime? MomentCompleted { get; set; }
+
+ ///
+ /// When the run record was created.
+ ///
+ [JsonPropertyName( "created_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime MomentCreated { get; set; }
+
+ ///
+ /// Per-step execution details.
+ ///
+ [JsonPropertyName( "steps" )]
+ public List Steps { get; set; } = default!;
+}
diff --git a/src/Resend/AutomationRunListQuery.cs b/src/Resend/AutomationRunListQuery.cs
new file mode 100644
index 0000000..2572d33
--- /dev/null
+++ b/src/Resend/AutomationRunListQuery.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Query parameters for .
+///
+public class AutomationRunListQuery : PaginatedQuery
+{
+ ///
+ /// Comma-separated run statuses: running, completed, failed, cancelled.
+ ///
+ [JsonIgnore]
+ public string? Status { get; set; }
+}
diff --git a/src/Resend/AutomationRunStep.cs b/src/Resend/AutomationRunStep.cs
new file mode 100644
index 0000000..0512cd3
--- /dev/null
+++ b/src/Resend/AutomationRunStep.cs
@@ -0,0 +1,57 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Execution record for one step within an automation run.
+///
+public class AutomationRunStep
+{
+ ///
+ /// Step type.
+ ///
+ [JsonPropertyName( "type" )]
+ public string Type { get; set; } = default!;
+
+ ///
+ /// Step execution status.
+ ///
+ [JsonPropertyName( "status" )]
+ public string Status { get; set; } = default!;
+
+ ///
+ /// When the step started.
+ ///
+ [JsonPropertyName( "started_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime MomentStarted { get; set; }
+
+ ///
+ /// When the step finished, if applicable.
+ ///
+ [JsonPropertyName( "completed_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime? MomentCompleted { get; set; }
+
+ ///
+ /// Step output payload when present.
+ ///
+ [JsonPropertyName( "output" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public JsonElement? Output { get; set; }
+
+ ///
+ /// Error details when the step failed.
+ ///
+ [JsonPropertyName( "error" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public JsonElement? Error { get; set; }
+
+ ///
+ /// When the step record was created.
+ ///
+ [JsonPropertyName( "created_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime MomentCreated { get; set; }
+}
diff --git a/src/Resend/AutomationRunSummary.cs b/src/Resend/AutomationRunSummary.cs
new file mode 100644
index 0000000..4175583
--- /dev/null
+++ b/src/Resend/AutomationRunSummary.cs
@@ -0,0 +1,42 @@
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// A single automation run in a list response.
+///
+public class AutomationRunSummary
+{
+ ///
+ /// Run identifier.
+ ///
+ [JsonPropertyName( "id" )]
+ public Guid Id { get; set; }
+
+ ///
+ /// Run status.
+ ///
+ [JsonPropertyName( "status" )]
+ public string Status { get; set; } = default!;
+
+ ///
+ /// When the run started.
+ ///
+ [JsonPropertyName( "started_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime MomentStarted { get; set; }
+
+ ///
+ /// When the run finished, if applicable.
+ ///
+ [JsonPropertyName( "completed_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime? MomentCompleted { get; set; }
+
+ ///
+ /// When the run record was created.
+ ///
+ [JsonPropertyName( "created_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime MomentCreated { get; set; }
+}
diff --git a/src/Resend/AutomationStep.cs b/src/Resend/AutomationStep.cs
new file mode 100644
index 0000000..20c1495
--- /dev/null
+++ b/src/Resend/AutomationStep.cs
@@ -0,0 +1,22 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// A step returned when retrieving an automation (responses omit per-step ref).
+///
+public class AutomationStep
+{
+ ///
+ /// Step type.
+ ///
+ [JsonPropertyName( "type" )]
+ public string Type { get; set; } = default!;
+
+ ///
+ /// Step configuration; shape depends on .
+ ///
+ [JsonPropertyName( "config" )]
+ public JsonElement Config { get; set; }
+}
diff --git a/src/Resend/AutomationStepData.cs b/src/Resend/AutomationStepData.cs
new file mode 100644
index 0000000..60ab042
--- /dev/null
+++ b/src/Resend/AutomationStepData.cs
@@ -0,0 +1,28 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// A step in an automation graph for create and update requests.
+///
+public class AutomationStepData
+{
+ ///
+ /// Unique reference for this step, used by and .
+ ///
+ [JsonPropertyName( "key" )]
+ public string Ref { get; set; } = default!;
+
+ ///
+ /// Step type (for example trigger, send_email, delay).
+ ///
+ [JsonPropertyName( "type" )]
+ public string Type { get; set; } = default!;
+
+ ///
+ /// Step configuration; shape depends on .
+ ///
+ [JsonPropertyName( "config" )]
+ public JsonElement Config { get; set; }
+}
diff --git a/src/Resend/AutomationStopResult.cs b/src/Resend/AutomationStopResult.cs
new file mode 100644
index 0000000..04fe55d
--- /dev/null
+++ b/src/Resend/AutomationStopResult.cs
@@ -0,0 +1,27 @@
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Response from stopping an automation run (automation is set to disabled).
+///
+public class AutomationStopResult
+{
+ ///
+ /// Object type discriminator.
+ ///
+ [JsonPropertyName( "object" )]
+ public string Object { get; set; } = default!;
+
+ ///
+ /// Automation identifier.
+ ///
+ [JsonPropertyName( "id" )]
+ public Guid Id { get; set; }
+
+ ///
+ /// Updated status (for example disabled).
+ ///
+ [JsonPropertyName( "status" )]
+ public string Status { get; set; } = default!;
+}
diff --git a/src/Resend/AutomationSummary.cs b/src/Resend/AutomationSummary.cs
new file mode 100644
index 0000000..9af2cb9
--- /dev/null
+++ b/src/Resend/AutomationSummary.cs
@@ -0,0 +1,41 @@
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Automation summary returned when listing automations.
+///
+public class AutomationSummary
+{
+ ///
+ /// Automation identifier.
+ ///
+ [JsonPropertyName( "id" )]
+ public Guid Id { get; set; }
+
+ ///
+ /// Display name.
+ ///
+ [JsonPropertyName( "name" )]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// Either enabled or disabled.
+ ///
+ [JsonPropertyName( "status" )]
+ public string Status { get; set; } = default!;
+
+ ///
+ /// When the automation was created.
+ ///
+ [JsonPropertyName( "created_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime MomentCreated { get; set; }
+
+ ///
+ /// When the automation was last updated.
+ ///
+ [JsonPropertyName( "updated_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime MomentUpdated { get; set; }
+}
diff --git a/src/Resend/AutomationUpdateData.cs b/src/Resend/AutomationUpdateData.cs
new file mode 100644
index 0000000..059528f
--- /dev/null
+++ b/src/Resend/AutomationUpdateData.cs
@@ -0,0 +1,38 @@
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Request body to update an automation. Provide at least one field; when updating the graph,
+/// send and together.
+///
+public class AutomationUpdateData
+{
+ ///
+ /// Display name.
+ ///
+ [JsonPropertyName( "name" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public string? Name { get; set; }
+
+ ///
+ /// Either enabled or disabled.
+ ///
+ [JsonPropertyName( "status" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public string? Status { get; set; }
+
+ ///
+ /// Replacement step list when updating the graph.
+ ///
+ [JsonPropertyName( "steps" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public List? Steps { get; set; }
+
+ ///
+ /// Replacement edge list when updating the graph.
+ ///
+ [JsonPropertyName( "edges" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public List? Edges { get; set; }
+}
diff --git a/src/Resend/EventCreateData.cs b/src/Resend/EventCreateData.cs
new file mode 100644
index 0000000..476def3
--- /dev/null
+++ b/src/Resend/EventCreateData.cs
@@ -0,0 +1,23 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Request body to create an event definition.
+///
+public class EventCreateData
+{
+ ///
+ /// Event name (for example user.created).
+ ///
+ [JsonPropertyName( "name" )]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// Optional payload schema as flat key/type pairs.
+ ///
+ [JsonPropertyName( "schema" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public JsonElement? Schema { get; set; }
+}
diff --git a/src/Resend/EventResource.cs b/src/Resend/EventResource.cs
new file mode 100644
index 0000000..eb23c34
--- /dev/null
+++ b/src/Resend/EventResource.cs
@@ -0,0 +1,49 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// A named event definition (schema and metadata) used with automations.
+///
+public class EventResource
+{
+ ///
+ /// Object type discriminator.
+ ///
+ [JsonPropertyName( "object" )]
+ public string Object { get; set; } = default!;
+
+ ///
+ /// Event identifier.
+ ///
+ [JsonPropertyName( "id" )]
+ public Guid Id { get; set; }
+
+ ///
+ /// Event name (matches trigger configuration).
+ ///
+ [JsonPropertyName( "name" )]
+ public string Name { get; set; } = default!;
+
+ ///
+ /// Optional payload schema as flat key/type pairs (values: string, number, boolean, date).
+ ///
+ [JsonPropertyName( "schema" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public JsonElement? Schema { get; set; }
+
+ ///
+ /// When the event was created.
+ ///
+ [JsonPropertyName( "created_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime MomentCreated { get; set; }
+
+ ///
+ /// When the event was last updated.
+ ///
+ [JsonPropertyName( "updated_at" )]
+ [JsonConverter( typeof( JsonUtcDateTimeConverter ) )]
+ public DateTime MomentUpdated { get; set; }
+}
diff --git a/src/Resend/EventSendData.cs b/src/Resend/EventSendData.cs
new file mode 100644
index 0000000..f4c0078
--- /dev/null
+++ b/src/Resend/EventSendData.cs
@@ -0,0 +1,40 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Request body to send a named event (for example to trigger automations).
+///
+///
+/// Provide exactly one of or .
+///
+public class EventSendData
+{
+ ///
+ /// Event name (must match a trigger step).
+ ///
+ [JsonPropertyName( "event" )]
+ public string Event { get; set; } = default!;
+
+ ///
+ /// Contact to associate with the event.
+ ///
+ [JsonPropertyName( "contact_id" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public Guid? ContactId { get; set; }
+
+ ///
+ /// Email address to associate with the event.
+ ///
+ [JsonPropertyName( "email" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public string? Email { get; set; }
+
+ ///
+ /// Optional payload passed to templates and conditions.
+ ///
+ [JsonPropertyName( "payload" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public JsonElement? Payload { get; set; }
+}
diff --git a/src/Resend/EventSendResult.cs b/src/Resend/EventSendResult.cs
new file mode 100644
index 0000000..4afb4f1
--- /dev/null
+++ b/src/Resend/EventSendResult.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Response from accepting an event (HTTP 202).
+///
+public class EventSendResult
+{
+ ///
+ /// Object type discriminator.
+ ///
+ [JsonPropertyName( "object" )]
+ public string Object { get; set; } = default!;
+
+ ///
+ /// Event name that was accepted.
+ ///
+ [JsonPropertyName( "event" )]
+ public string Event { get; set; } = default!;
+}
diff --git a/src/Resend/EventUpdateData.cs b/src/Resend/EventUpdateData.cs
new file mode 100644
index 0000000..119d1cb
--- /dev/null
+++ b/src/Resend/EventUpdateData.cs
@@ -0,0 +1,16 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Resend;
+
+///
+/// Request body to update an event definition (typically the payload schema).
+///
+public class EventUpdateData
+{
+ ///
+ /// Payload schema as flat key/type pairs; include null to clear the schema.
+ ///
+ [JsonPropertyName( "schema" )]
+ public JsonElement? Schema { get; set; }
+}
diff --git a/src/Resend/IResend.cs b/src/Resend/IResend.cs
index e4de213..e892165 100644
--- a/src/Resend/IResend.cs
+++ b/src/Resend/IResend.cs
@@ -986,4 +986,166 @@ public interface IResend
Task> LogRetrieveAsync( Guid logId, CancellationToken cancellationToken = default );
#endregion
+
+ #region Automations and events
+
+ ///
+ /// Creates an automation.
+ ///
+ /// Automation definition.
+ /// Cancellation token.
+ /// New automation identifier.
+ ///
+ Task> AutomationCreateAsync( AutomationCreateData data, CancellationToken cancellationToken = default );
+
+ ///
+ /// Updates an automation.
+ ///
+ /// Automation identifier.
+ /// Fields to update.
+ /// Cancellation token.
+ /// Automation identifier.
+ ///
+ Task> AutomationUpdateAsync( Guid automationId, AutomationUpdateData data, CancellationToken cancellationToken = default );
+
+ ///
+ /// Retrieves an automation by identifier.
+ ///
+ /// Automation identifier.
+ /// Cancellation token.
+ /// Automation.
+ ///
+ Task> AutomationRetrieveAsync( Guid automationId, CancellationToken cancellationToken = default );
+
+ ///
+ /// Lists automations.
+ ///
+ /// Filters and pagination.
+ /// Cancellation token.
+ /// Page of automations.
+ ///
+ Task>> AutomationListAsync( AutomationListQuery? query = null, CancellationToken cancellationToken = default );
+
+ ///
+ /// Stops a running automation (sets status to disabled).
+ ///
+ /// Automation identifier.
+ /// Cancellation token.
+ /// Result including updated status.
+ ///
+ Task> AutomationStopAsync( Guid automationId, CancellationToken cancellationToken = default );
+
+ ///
+ /// Deletes an automation.
+ ///
+ /// Automation identifier.
+ /// Cancellation token.
+ /// Deletion confirmation.
+ ///
+ Task> AutomationDeleteAsync( Guid automationId, CancellationToken cancellationToken = default );
+
+ ///
+ /// Lists runs for an automation.
+ ///
+ /// Automation identifier.
+ /// Filters and pagination.
+ /// Cancellation token.
+ /// Page of runs.
+ ///
+ Task>> AutomationRunListAsync( Guid automationId, AutomationRunListQuery? query = null, CancellationToken cancellationToken = default );
+
+ ///
+ /// Retrieves a single automation run.
+ ///
+ /// Automation identifier.
+ /// Run identifier.
+ /// Cancellation token.
+ /// Run detail.
+ ///
+ Task> AutomationRunRetrieveAsync( Guid automationId, Guid runId, CancellationToken cancellationToken = default );
+
+ ///
+ /// Creates an event definition (name and optional payload schema).
+ ///
+ /// Event name and optional schema.
+ /// Cancellation token.
+ /// New event identifier (HTTP 201).
+ ///
+ Task> EventCreateAsync( EventCreateData data, CancellationToken cancellationToken = default );
+
+ ///
+ /// Retrieves an event by identifier.
+ ///
+ /// Event identifier.
+ /// Cancellation token.
+ /// Event definition.
+ ///
+ Task> EventRetrieveAsync( Guid eventId, CancellationToken cancellationToken = default );
+
+ ///
+ /// Retrieves an event by identifier or name.
+ ///
+ /// Event id (UUID) or name (for example user.created).
+ /// Cancellation token.
+ /// Event definition.
+ ///
+ Task> EventRetrieveAsync( string eventIdOrName, CancellationToken cancellationToken = default );
+
+ ///
+ /// Lists event definitions.
+ ///
+ /// Pagination query.
+ /// Cancellation token.
+ /// Page of events.
+ ///
+ Task>> EventListAsync( PaginatedQuery? query = null, CancellationToken cancellationToken = default );
+
+ ///
+ /// Updates an event definition (for example its payload schema).
+ ///
+ /// Event identifier.
+ /// Fields to update.
+ /// Cancellation token.
+ /// Response.
+ ///
+ Task EventUpdateAsync( Guid eventId, EventUpdateData data, CancellationToken cancellationToken = default );
+
+ ///
+ /// Updates an event definition by identifier or name.
+ ///
+ /// Event id (UUID) or name.
+ /// Fields to update.
+ /// Cancellation token.
+ /// Response.
+ ///
+ Task EventUpdateAsync( string eventIdOrName, EventUpdateData data, CancellationToken cancellationToken = default );
+
+ ///
+ /// Deletes an event definition.
+ ///
+ /// Event identifier.
+ /// Cancellation token.
+ /// Response.
+ ///
+ Task EventDeleteAsync( Guid eventId, CancellationToken cancellationToken = default );
+
+ ///
+ /// Deletes an event definition by identifier or name.
+ ///
+ /// Event id (UUID) or name.
+ /// Cancellation token.
+ /// Response.
+ ///
+ Task EventDeleteAsync( string eventIdOrName, CancellationToken cancellationToken = default );
+
+ ///
+ /// Sends a named event (for example to trigger automations).
+ ///
+ /// Event name, contact or email, and optional payload.
+ /// Cancellation token.
+ /// Accepted event metadata (HTTP 202).
+ ///
+ Task> EventSendAsync( EventSendData data, CancellationToken cancellationToken = default );
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Resend/ResendClient.Automations.cs b/src/Resend/ResendClient.Automations.cs
new file mode 100644
index 0000000..78a2101
--- /dev/null
+++ b/src/Resend/ResendClient.Automations.cs
@@ -0,0 +1,125 @@
+using Microsoft.AspNetCore.WebUtilities;
+using Resend.Payloads;
+using System.Net.Http.Json;
+
+namespace Resend;
+
+public partial class ResendClient
+{
+ ///
+ public Task> AutomationCreateAsync( AutomationCreateData data, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Post, "/automations" );
+ req.Content = JsonContent.Create( data );
+
+ return Execute( req, ( x ) => x.Id, cancellationToken );
+ }
+
+
+ ///
+ public Task> AutomationUpdateAsync( Guid automationId, AutomationUpdateData data, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Patch, $"/automations/{automationId}" );
+ req.Content = JsonContent.Create( data );
+
+ return Execute( req, ( x ) => x.Id, cancellationToken );
+ }
+
+
+ ///
+ public Task> AutomationRetrieveAsync( Guid automationId, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Get, $"/automations/{automationId}" );
+
+ return Execute( req, ( x ) => x, cancellationToken );
+ }
+
+
+ ///
+ public Task>> AutomationListAsync( AutomationListQuery? query = null, CancellationToken cancellationToken = default )
+ {
+ var baseUrl = "/automations";
+ var url = baseUrl;
+
+ if ( query != null )
+ {
+ var qs = new Dictionary();
+
+ if ( query.Limit.HasValue == true )
+ qs.Add( "limit", query.Limit.Value.ToString() );
+
+ if ( query.Before != null )
+ qs.Add( "before", query.Before );
+
+ if ( query.After != null )
+ qs.Add( "after", query.After );
+
+ if ( query.Status != null )
+ qs.Add( "status", query.Status );
+
+ url = QueryHelpers.AddQueryString( baseUrl, qs );
+ }
+
+ var req = new HttpRequestMessage( HttpMethod.Get, url );
+
+ return Execute, PaginatedResult>( req, ( x ) => x, cancellationToken );
+ }
+
+
+ ///
+ public Task> AutomationStopAsync( Guid automationId, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Post, $"/automations/{automationId}/stop" );
+
+ return Execute( req, ( x ) => x, cancellationToken );
+ }
+
+
+ ///
+ public Task> AutomationDeleteAsync( Guid automationId, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Delete, $"/automations/{automationId}" );
+
+ return Execute( req, ( x ) => x, cancellationToken );
+ }
+
+
+ ///
+ public Task>> AutomationRunListAsync( Guid automationId, AutomationRunListQuery? query = null, CancellationToken cancellationToken = default )
+ {
+ var baseUrl = $"/automations/{automationId}/runs";
+ var url = baseUrl;
+
+ if ( query != null )
+ {
+ var qs = new Dictionary();
+
+ if ( query.Limit.HasValue == true )
+ qs.Add( "limit", query.Limit.Value.ToString() );
+
+ if ( query.Before != null )
+ qs.Add( "before", query.Before );
+
+ if ( query.After != null )
+ qs.Add( "after", query.After );
+
+ if ( query.Status != null )
+ qs.Add( "status", query.Status );
+
+ url = QueryHelpers.AddQueryString( baseUrl, qs );
+ }
+
+ var req = new HttpRequestMessage( HttpMethod.Get, url );
+
+ return Execute, PaginatedResult>( req, ( x ) => x, cancellationToken );
+ }
+
+
+ ///
+ public Task> AutomationRunRetrieveAsync( Guid automationId, Guid runId, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Get, $"/automations/{automationId}/runs/{runId}" );
+
+ return Execute( req, ( x ) => x, cancellationToken );
+ }
+}
diff --git a/src/Resend/ResendClient.Events.cs b/src/Resend/ResendClient.Events.cs
new file mode 100644
index 0000000..888cef0
--- /dev/null
+++ b/src/Resend/ResendClient.Events.cs
@@ -0,0 +1,111 @@
+using Microsoft.AspNetCore.WebUtilities;
+using Resend.Payloads;
+using System.Net.Http.Json;
+
+namespace Resend;
+
+public partial class ResendClient
+{
+ ///
+ public Task> EventCreateAsync( EventCreateData data, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Post, "/events" );
+ req.Content = JsonContent.Create( data );
+
+ return Execute( req, ( x ) => x.Id, cancellationToken );
+ }
+
+
+ ///
+ public Task> EventRetrieveAsync( Guid eventId, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Get, $"/events/{eventId}" );
+
+ return Execute( req, ( x ) => x, cancellationToken );
+ }
+
+
+ ///
+ public Task> EventRetrieveAsync( string eventIdOrName, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Get, $"/events/{Uri.EscapeDataString( eventIdOrName )}" );
+
+ return Execute( req, ( x ) => x, cancellationToken );
+ }
+
+
+ ///
+ public Task>> EventListAsync( PaginatedQuery? query = null, CancellationToken cancellationToken = default )
+ {
+ var baseUrl = "/events";
+ var url = baseUrl;
+
+ if ( query != null )
+ {
+ var qs = new Dictionary();
+
+ if ( query.Limit.HasValue == true )
+ qs.Add( "limit", query.Limit.Value.ToString() );
+
+ if ( query.Before != null )
+ qs.Add( "before", query.Before );
+
+ if ( query.After != null )
+ qs.Add( "after", query.After );
+
+ url = QueryHelpers.AddQueryString( baseUrl, qs );
+ }
+
+ var req = new HttpRequestMessage( HttpMethod.Get, url );
+
+ return Execute, PaginatedResult>( req, ( x ) => x, cancellationToken );
+ }
+
+
+ ///
+ public Task EventUpdateAsync( Guid eventId, EventUpdateData data, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Patch, $"/events/{eventId}" );
+ req.Content = JsonContent.Create( data );
+
+ return Execute( req, cancellationToken );
+ }
+
+
+ ///
+ public Task EventUpdateAsync( string eventIdOrName, EventUpdateData data, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Patch, $"/events/{Uri.EscapeDataString( eventIdOrName )}" );
+ req.Content = JsonContent.Create( data );
+
+ return Execute( req, cancellationToken );
+ }
+
+
+ ///
+ public Task EventDeleteAsync( Guid eventId, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Delete, $"/events/{eventId}" );
+
+ return Execute( req, cancellationToken );
+ }
+
+
+ ///
+ public Task EventDeleteAsync( string eventIdOrName, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Delete, $"/events/{Uri.EscapeDataString( eventIdOrName )}" );
+
+ return Execute( req, cancellationToken );
+ }
+
+
+ ///
+ public Task> EventSendAsync( EventSendData data, CancellationToken cancellationToken = default )
+ {
+ var req = new HttpRequestMessage( HttpMethod.Post, "/events/send" );
+ req.Content = JsonContent.Create( data );
+
+ return Execute( req, ( x ) => x, cancellationToken );
+ }
+}
diff --git a/tests/Resend.Tests/ResendClientTests.Automations.cs b/tests/Resend.Tests/ResendClientTests.Automations.cs
new file mode 100644
index 0000000..6c444f3
--- /dev/null
+++ b/tests/Resend.Tests/ResendClientTests.Automations.cs
@@ -0,0 +1,175 @@
+using System.Text.Json;
+
+namespace Resend.Tests;
+
+///
+public partial class ResendClientTests
+{
+ ///
+ [Fact]
+ public async Task AutomationCreate()
+ {
+ var resp = await _resend.AutomationCreateAsync( new AutomationCreateData()
+ {
+ Name = "Welcome series",
+ Steps =
+ [
+ new AutomationStepData()
+ {
+ Ref = "trigger",
+ Type = "trigger",
+ Config = JsonDocument.Parse( "{\"event_name\":\"user.created\"}" ).RootElement,
+ },
+ ],
+ Edges = [],
+ } );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.NotEqual( Guid.Empty, resp.Content );
+ }
+
+
+ ///
+ [Fact]
+ public async Task AutomationUpdate()
+ {
+ var id = Guid.NewGuid();
+
+ var resp = await _resend.AutomationUpdateAsync( id, new AutomationUpdateData()
+ {
+ Status = "enabled",
+ } );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.Equal( id, resp.Content );
+ }
+
+
+ ///
+ [Fact]
+ public async Task AutomationRetrieve()
+ {
+ var id = Guid.Parse( "c9b16d4f-ba6c-4e2e-b044-6bf4404e57fd" );
+
+ var resp = await _resend.AutomationRetrieveAsync( id );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.Equal( id, resp.Content.Id );
+ Assert.Equal( "Welcome series", resp.Content.Name );
+ Assert.Equal( "trigger", resp.Content.Steps[ 0 ].Type );
+ }
+
+
+ ///
+ [Fact]
+ public async Task AutomationList()
+ {
+ var resp = await _resend.AutomationListAsync();
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.NotNull( resp.Content );
+ Assert.False( resp.Content.HasMore );
+ Assert.Equal( 2, resp.Content.Data.Count );
+ Assert.Equal( "Welcome series", resp.Content.Data[ 0 ].Name );
+ }
+
+
+ ///
+ [Fact]
+ public async Task AutomationList_WithQuery()
+ {
+ var resp = await _resend.AutomationListAsync( new AutomationListQuery()
+ {
+ Limit = 20,
+ Status = "enabled",
+ After = Guid.NewGuid().ToString(),
+ } );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.NotNull( resp.Content );
+ }
+
+
+ ///
+ [Fact]
+ public async Task AutomationStop()
+ {
+ var id = Guid.NewGuid();
+
+ var resp = await _resend.AutomationStopAsync( id );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.Equal( id, resp.Content.Id );
+ Assert.Equal( "disabled", resp.Content.Status );
+ }
+
+
+ ///
+ [Fact]
+ public async Task AutomationDelete()
+ {
+ var id = Guid.NewGuid();
+
+ var resp = await _resend.AutomationDeleteAsync( id );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.True( resp.Content.Deleted );
+ }
+
+
+ ///
+ [Fact]
+ public async Task AutomationRunList()
+ {
+ var automationId = Guid.NewGuid();
+
+ var resp = await _resend.AutomationRunListAsync( automationId );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.Equal( 2, resp.Content.Data.Count );
+ Assert.Equal( "completed", resp.Content.Data[ 0 ].Status );
+ Assert.Null( resp.Content.Data[ 1 ].MomentCompleted );
+ }
+
+
+ ///
+ [Fact]
+ public async Task AutomationRunList_WithQuery()
+ {
+ var automationId = Guid.NewGuid();
+
+ var resp = await _resend.AutomationRunListAsync( automationId, new AutomationRunListQuery()
+ {
+ Limit = 10,
+ Status = "running,completed",
+ } );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ }
+
+
+ ///
+ [Fact]
+ public async Task AutomationRunRetrieve()
+ {
+ var automationId = Guid.NewGuid();
+ var runId = Guid.Parse( "a1b2c3d4-e5f6-7890-abcd-ef1234567890" );
+
+ var resp = await _resend.AutomationRunRetrieveAsync( automationId, runId );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.Equal( runId, resp.Content.Id );
+ Assert.Equal( 2, resp.Content.Steps.Count );
+ Assert.Equal( "trigger", resp.Content.Steps[ 0 ].Type );
+ }
+}
diff --git a/tests/Resend.Tests/ResendClientTests.Events.cs b/tests/Resend.Tests/ResendClientTests.Events.cs
new file mode 100644
index 0000000..eef7a73
--- /dev/null
+++ b/tests/Resend.Tests/ResendClientTests.Events.cs
@@ -0,0 +1,150 @@
+using System.Text.Json;
+
+namespace Resend.Tests;
+
+///
+public partial class ResendClientTests
+{
+ ///
+ [Fact]
+ public async Task EventCreate()
+ {
+ var resp = await _resend.EventCreateAsync( new EventCreateData()
+ {
+ Name = "user.created",
+ Schema = JsonDocument.Parse( "{\"plan\":\"string\"}" ).RootElement,
+ } );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.NotEqual( Guid.Empty, resp.Content );
+ }
+
+
+ ///
+ [Fact]
+ public async Task EventRetrieve_ByGuid()
+ {
+ var id = Guid.Parse( "a1b2c3d4-e5f6-7890-abcd-ef1234567890" );
+
+ var resp = await _resend.EventRetrieveAsync( id );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.Equal( id, resp.Content.Id );
+ Assert.Equal( "user.created", resp.Content.Name );
+ }
+
+
+ ///
+ [Fact]
+ public async Task EventRetrieve_ByName()
+ {
+ var resp = await _resend.EventRetrieveAsync( "user.created" );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.Equal( "user.created", resp.Content.Name );
+ }
+
+
+ ///
+ [Fact]
+ public async Task EventList()
+ {
+ var resp = await _resend.EventListAsync();
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.Equal( 2, resp.Content.Data.Count );
+ Assert.Equal( "user.created", resp.Content.Data[ 0 ].Name );
+ Assert.Null( resp.Content.Data[ 1 ].Schema );
+ }
+
+
+ ///
+ [Fact]
+ public async Task EventList_WithPagination()
+ {
+ var resp = await _resend.EventListAsync( new PaginatedQuery()
+ {
+ Limit = 20,
+ After = Guid.NewGuid().ToString(),
+ } );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ }
+
+
+ ///
+ [Fact]
+ public async Task EventUpdate_ByName()
+ {
+ var resp = await _resend.EventUpdateAsync( "user.created", new EventUpdateData()
+ {
+ Schema = JsonDocument.Parse( "{\"plan\":\"string\",\"trial\":\"boolean\"}" ).RootElement,
+ } );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ }
+
+
+ ///
+ [Fact]
+ public async Task EventUpdate_ByGuid()
+ {
+ var id = Guid.NewGuid();
+
+ var resp = await _resend.EventUpdateAsync( id, new EventUpdateData()
+ {
+ Schema = JsonDocument.Parse( "{\"plan\":\"string\"}" ).RootElement,
+ } );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ }
+
+
+ ///
+ [Fact]
+ public async Task EventDelete_ByName()
+ {
+ var resp = await _resend.EventDeleteAsync( "user.created" );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ }
+
+
+ ///
+ [Fact]
+ public async Task EventDelete_ByGuid()
+ {
+ var resp = await _resend.EventDeleteAsync( Guid.NewGuid() );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ }
+
+
+ ///
+ [Fact]
+ public async Task EventSend_WithContactId()
+ {
+ var contactId = Guid.Parse( "7f2e4a3b-dfbc-4e9a-8b2c-5f3a1d6e7c8b" );
+
+ var resp = await _resend.EventSendAsync( new EventSendData()
+ {
+ Event = "user.created",
+ ContactId = contactId,
+ Payload = JsonDocument.Parse( "{\"plan\":\"pro\"}" ).RootElement,
+ } );
+
+ Assert.NotNull( resp );
+ Assert.True( resp.Success );
+ Assert.Equal( "user.created", resp.Content.Event );
+ Assert.Equal( "event", resp.Content.Object );
+ }
+}
diff --git a/tools/Resend.ApiServer/Controllers/AutomationController.cs b/tools/Resend.ApiServer/Controllers/AutomationController.cs
new file mode 100644
index 0000000..2446e61
--- /dev/null
+++ b/tools/Resend.ApiServer/Controllers/AutomationController.cs
@@ -0,0 +1,251 @@
+using Microsoft.AspNetCore.Mvc;
+using Resend;
+using Resend.Payloads;
+using System.Text.Json;
+
+namespace Resend.ApiServer.Controllers;
+
+///
+[ApiController]
+public class AutomationController : ControllerBase
+{
+ private readonly ILogger _logger;
+
+
+ ///
+ public AutomationController( ILogger logger )
+ {
+ _logger = logger;
+ }
+
+
+ ///
+ [HttpPost]
+ [Route( "automations" )]
+ public ObjectId AutomationCreate( [FromBody] AutomationCreateData request )
+ {
+ _logger.LogDebug( "AutomationCreate" );
+
+ return new ObjectId()
+ {
+ Object = "automation",
+ Id = Guid.NewGuid(),
+ };
+ }
+
+
+ ///
+ [HttpPatch]
+ [Route( "automations/{id}" )]
+ public ObjectId AutomationUpdate( [FromRoute] Guid id, [FromBody] AutomationUpdateData request )
+ {
+ _logger.LogDebug( "AutomationUpdate" );
+
+ return new ObjectId()
+ {
+ Object = "automation",
+ Id = id,
+ };
+ }
+
+
+ ///
+ [HttpGet]
+ [Route( "automations/{id}" )]
+ public Automation AutomationRetrieve( [FromRoute] Guid id )
+ {
+ _logger.LogDebug( "AutomationRetrieve" );
+
+ var stepA = Guid.Parse( "a1b2c3d4-e5f6-7890-abcd-ef1234567890" );
+ var stepB = Guid.Parse( "b2c3d4e5-f6a7-8901-bcde-f12345678901" );
+
+ return new Automation()
+ {
+ Object = "automation",
+ Id = id,
+ Name = "Welcome series",
+ Status = "enabled",
+ MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentUpdated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ Steps =
+ [
+ new AutomationStep()
+ {
+ Type = "trigger",
+ Config = JsonDocument.Parse( "{\"event_name\":\"user.created\"}" ).RootElement,
+ },
+ new AutomationStep()
+ {
+ Type = "send_email",
+ Config = JsonDocument.Parse( "{\"template_id\":\"tpl_xxxxxxxxx\",\"subject\":\"Welcome!\",\"from\":\"Acme \"}" ).RootElement,
+ },
+ ],
+ Edges =
+ [
+ new AutomationEdge()
+ {
+ From = stepA.ToString(),
+ To = stepB.ToString(),
+ EdgeType = "default",
+ },
+ ],
+ };
+ }
+
+
+ ///
+ [HttpGet]
+ [Route( "automations" )]
+ public PaginatedResult AutomationList(
+ [FromQuery] string? limit = null,
+ [FromQuery] string? before = null,
+ [FromQuery] string? after = null,
+ [FromQuery] string? status = null
+ )
+ {
+ _logger.LogDebug( "AutomationList" );
+
+ var id1 = Guid.Parse( "c9b16d4f-ba6c-4e2e-b044-6bf4404e57fd" );
+ var id2 = Guid.Parse( "662892b2-4270-4130-a186-33a19752319d" );
+
+ return new PaginatedResult()
+ {
+ HasMore = false,
+ Data =
+ [
+ new AutomationSummary()
+ {
+ Id = id1,
+ Name = "Welcome series",
+ Status = "enabled",
+ MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentUpdated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ },
+ new AutomationSummary()
+ {
+ Id = id2,
+ Name = "Re-engagement",
+ Status = "disabled",
+ MomentCreated = DateTime.Parse( "2025-09-15 08:30:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentUpdated = DateTime.Parse( "2025-09-20 14:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ },
+ ],
+ };
+ }
+
+
+ ///
+ [HttpPost]
+ [Route( "automations/{id}/stop" )]
+ public AutomationStopResult AutomationStop( [FromRoute] Guid id )
+ {
+ _logger.LogDebug( "AutomationStop" );
+
+ return new AutomationStopResult()
+ {
+ Object = "automation",
+ Id = id,
+ Status = "disabled",
+ };
+ }
+
+
+ ///
+ [HttpDelete]
+ [Route( "automations/{id}" )]
+ public AutomationDeleteResult AutomationDelete( [FromRoute] Guid id )
+ {
+ _logger.LogDebug( "AutomationDelete" );
+
+ return new AutomationDeleteResult()
+ {
+ Object = "automation",
+ Id = id,
+ Deleted = true,
+ };
+ }
+
+
+ ///
+ [HttpGet]
+ [Route( "automations/{automationId}/runs" )]
+ public PaginatedResult AutomationRunList(
+ [FromRoute] Guid automationId,
+ [FromQuery] string? limit = null,
+ [FromQuery] string? before = null,
+ [FromQuery] string? after = null,
+ [FromQuery] string? status = null
+ )
+ {
+ _logger.LogDebug( "AutomationRunList" );
+
+ var run1 = Guid.Parse( "a1b2c3d4-e5f6-7890-abcd-ef1234567890" );
+ var run2 = Guid.Parse( "b2c3d4e5-f6a7-8901-bcde-f12345678901" );
+
+ return new PaginatedResult()
+ {
+ HasMore = false,
+ Data =
+ [
+ new AutomationRunSummary()
+ {
+ Id = run1,
+ Status = "completed",
+ MomentStarted = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentCompleted = DateTime.Parse( "2025-10-01 12:05:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ },
+ new AutomationRunSummary()
+ {
+ Id = run2,
+ Status = "running",
+ MomentStarted = DateTime.Parse( "2025-10-02 08:30:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentCompleted = null,
+ MomentCreated = DateTime.Parse( "2025-10-02 08:30:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ },
+ ],
+ };
+ }
+
+
+ ///
+ [HttpGet]
+ [Route( "automations/{automationId}/runs/{runId}" )]
+ public AutomationRun AutomationRunRetrieve( [FromRoute] Guid automationId, [FromRoute] Guid runId )
+ {
+ _logger.LogDebug( "AutomationRunRetrieve" );
+
+ return new AutomationRun()
+ {
+ Object = "automation_run",
+ Id = runId,
+ Status = "completed",
+ MomentStarted = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentCompleted = DateTime.Parse( "2025-10-01 12:05:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ Steps =
+ [
+ new AutomationRunStep()
+ {
+ Type = "trigger",
+ Status = "completed",
+ MomentStarted = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentCompleted = DateTime.Parse( "2025-10-01 12:00:01.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ Output = null,
+ Error = null,
+ MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ },
+ new AutomationRunStep()
+ {
+ Type = "send_email",
+ Status = "completed",
+ MomentStarted = DateTime.Parse( "2025-10-01 12:00:01.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentCompleted = DateTime.Parse( "2025-10-01 12:00:02.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ Output = null,
+ Error = null,
+ MomentCreated = DateTime.Parse( "2025-10-01 12:00:01.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ },
+ ],
+ };
+ }
+}
diff --git a/tools/Resend.ApiServer/Controllers/EventController.cs b/tools/Resend.ApiServer/Controllers/EventController.cs
new file mode 100644
index 0000000..5e1d4f5
--- /dev/null
+++ b/tools/Resend.ApiServer/Controllers/EventController.cs
@@ -0,0 +1,137 @@
+using Microsoft.AspNetCore.Mvc;
+using Resend;
+using Resend.Payloads;
+using System.Text.Json;
+
+namespace Resend.ApiServer.Controllers;
+
+///
+[ApiController]
+public class EventController : ControllerBase
+{
+ private readonly ILogger _logger;
+
+
+ ///
+ public EventController( ILogger logger )
+ {
+ _logger = logger;
+ }
+
+
+ ///
+ [HttpPost]
+ [Route( "events" )]
+ [ProducesResponseType( typeof( ObjectId ), StatusCodes.Status201Created )]
+ public IActionResult EventCreate( [FromBody] EventCreateData request )
+ {
+ _logger.LogDebug( "EventCreate" );
+
+ return StatusCode( StatusCodes.Status201Created, new ObjectId()
+ {
+ Object = "event",
+ Id = Guid.NewGuid(),
+ } );
+ }
+
+
+ ///
+ [HttpGet]
+ [Route( "events/{id}" )]
+ public EventResource EventRetrieve( [FromRoute] string id )
+ {
+ _logger.LogDebug( "EventRetrieve" );
+
+ var isGuid = Guid.TryParse( id, out var eid );
+
+ return new EventResource()
+ {
+ Object = "event",
+ Id = isGuid ? eid : Guid.Parse( "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ),
+ Name = isGuid ? "user.created" : id,
+ Schema = JsonDocument.Parse( "{\"plan\":\"string\"}" ).RootElement,
+ MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentUpdated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ };
+ }
+
+
+ ///
+ [HttpGet]
+ [Route( "events" )]
+ public PaginatedResult EventList(
+ [FromQuery] string? limit = null,
+ [FromQuery] string? before = null,
+ [FromQuery] string? after = null
+ )
+ {
+ _logger.LogDebug( "EventList" );
+
+ var id1 = Guid.Parse( "a1b2c3d4-e5f6-7890-abcd-ef1234567890" );
+ var id2 = Guid.Parse( "b2c3d4e5-f6a7-8901-bcde-f12345678901" );
+
+ return new PaginatedResult()
+ {
+ HasMore = false,
+ Data =
+ [
+ new EventResource()
+ {
+ Object = "event",
+ Id = id1,
+ Name = "user.created",
+ Schema = JsonDocument.Parse( "{\"plan\":\"string\"}" ).RootElement,
+ MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentUpdated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ },
+ new EventResource()
+ {
+ Object = "event",
+ Id = id2,
+ Name = "user.upgraded",
+ Schema = null,
+ MomentCreated = DateTime.Parse( "2025-09-15 08:30:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ MomentUpdated = DateTime.Parse( "2025-09-20 14:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ),
+ },
+ ],
+ };
+ }
+
+
+ ///
+ [HttpPatch]
+ [Route( "events/{id}" )]
+ public IActionResult EventUpdate( [FromRoute] string id, [FromBody] EventUpdateData request )
+ {
+ _logger.LogDebug( "EventUpdate" );
+
+ return Ok();
+ }
+
+
+ ///
+ [HttpDelete]
+ [Route( "events/{id}" )]
+ public IActionResult EventDelete( [FromRoute] string id )
+ {
+ _logger.LogDebug( "EventDelete" );
+
+ return Ok();
+ }
+
+
+ ///
+ [HttpPost]
+ [Route( "events/send" )]
+ [ProducesResponseType( typeof( EventSendResult ), StatusCodes.Status202Accepted )]
+ public IActionResult EventSend( [FromBody] EventSendData request )
+ {
+ _logger.LogDebug( "EventSend" );
+
+ return StatusCode( StatusCodes.Status202Accepted, new EventSendResult()
+ {
+ Object = "event",
+ Event = request.Event,
+ } );
+ }
+}