Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions server/Api/Admin/BroadcastRoutes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ public static void MapRoutes(IEndpointRouteBuilder app)

//return response;
}).WithOpenApi(op => {
op.Summary = "Queue a broadcast for your contacts";
op.Description = "This pops the messages for your broadcast into the queue and double checks the validation";
op.RequestBody.Description = "The markdown for the email";
op.Summary = "🤖 AI Chat Assistant";
op.Description = "Get AI-powered assistance for creating email content. Send a prompt and receive intelligent suggestions for your email campaigns.";
op.RequestBody.Description = "Your prompt or question for the AI assistant";
return op;
});
}).WithTags("Admin - AI Assistant");
//validate a broadcast
app.MapPost("/admin/queue-broadcast", ([FromBody] ValidationRequest req, [FromServices] IDb db) => {
var mardown = req.Markdown;
Expand All @@ -101,11 +101,11 @@ public static void MapRoutes(IEndpointRouteBuilder app)

return response;
}).WithOpenApi(op => {
op.Summary = "Queue a broadcast for your contacts";
op.Description = "This pops the messages for your broadcast into the queue and double checks the validation";
op.RequestBody.Description = "The markdown for the email";
op.Summary = "📤 Queue Email Broadcast";
op.Description = "Creates and queues a new broadcast email campaign from markdown content. Messages will be sent to all opted-in contacts. The markdown must include Subject, Summary, and Body sections.";
op.RequestBody.Description = "The markdown content for the email broadcast";
return op;
});
}).WithTags("Admin - Broadcasts");

app.MapPost("/admin/validate", ([FromBody] ValidationRequest req, [FromServices] IDb db) => {
if(req.Markdown == null){
Expand Down Expand Up @@ -135,15 +135,16 @@ public static void MapRoutes(IEndpointRouteBuilder app)
return response;
})
.WithOpenApi(op => {
op.Summary = "Validate the markdown for an email";
op.Description = "Before you send a broadcast, ping this endpoint to ensure that the markdown is valid";
op.RequestBody.Description = "The markdown for the email";
op.Summary = "Validate Email Markdown";
op.Description = "Validates markdown content for a broadcast email before sending. Returns validation status and estimated contact count. Use this to catch errors before queuing your broadcast.";
op.RequestBody.Description = "The markdown content to validate";
return op;
})
.Produces<ValidationResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status500InternalServerError);
.Produces(StatusCodes.Status500InternalServerError)
.WithTags("Admin - Broadcasts");
}

}
8 changes: 5 additions & 3 deletions server/Api/Admin/BulkOperationRoutes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ public static void MapRoutes(IEndpointRouteBuilder app)
Message = $"{tags.Count()} Tag(s) applied to {request.Emails.Count()} contacts"
};
}).WithOpenApi(op => {
op.Summary = "Tag a set of contacts";
op.Description = "Tag a set of contacts";
op.Summary = "🏷️ Bulk Tag Contacts";
op.Description = "Apply one or more tags to multiple contacts at once. Useful for organizing contacts into segments for targeted campaigns. Separate multiple tags with commas.";
op.RequestBody.Description = "List of email addresses and tag(s) to apply";
return op;
}).Produces<CommandResult>()
.Produces(500);
.Produces(500)
.WithTags("Admin - Bulk Operations");
}
}
8 changes: 5 additions & 3 deletions server/Api/Admin/ContactRoutes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ public static void MapRoutes(IEndpointRouteBuilder app)
response.Contacts = conn.Query<Contact>(sql, new {term});
return response;
}).WithOpenApi(op => {
op.Summary = "Find one or more contacts using a fuzzy match on email or name";
op.Description = "Find a set of contacts using a search term";
op.Summary = "🔍 Search Contacts";
op.Description = "Find contacts using fuzzy search on email or name fields. Returns all matching contacts for the provided search term.";
op.Parameters[0].Description = "Search term to match against contact names or email addresses";
return op;
}).Produces<ContactSearchResponse>()
.Produces(500);
.Produces(500)
.WithTags("Admin - Contacts");
}
}
public interface IQuantifiedList{
Expand Down
30 changes: 15 additions & 15 deletions server/Api/PublicRoutes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ public static void MapRoutes(IEndpointRouteBuilder app)

//public routes
app.MapGet("/about", () => "Tailwind Traders Mail Services API").WithOpenApi(op => {
op.Summary = "Information about the API";
op.Description = "This is the API for the Tailwind Traders Mail Services API";
op.Summary = "📋 Get API Information";
op.Description = "Returns basic information about the Tailwind Traders Mail Services API, including version and capabilities.";
return op;
});
}).WithTags("Public");

app.MapGet("/unsubscribe/{key}", (string key, [FromServices] IDb db) => {
using(var conn = db.Connect()){
Expand All @@ -27,23 +27,23 @@ public static void MapRoutes(IEndpointRouteBuilder app)
return result.Updated > 0;
}
}).WithOpenApi(op => {
op.Summary = "Unsubscribe from the mailing list";
op.Description = "This is the API for the Tailwind Traders Mail Services API";
op.Parameters[0].Description = "This is the contact's unique key";
op.Summary = "🚫 Unsubscribe Contact";
op.Description = "Allows a contact to opt-out from the mailing list using their unique unsubscribe key. This key is typically included in email footers.";
op.Parameters[0].Description = "The contact's unique unsubscribe key (provided in email communications)";
return op;
});
}).WithTags("Public");

//this isn't implemented yet in terms of data
app.MapGet("/link/clicked/{key}", (string key, [FromServices] IDb db) => {
var cmd = new LinkClickedCommand(key);
var result = cmd.Execute();
return result;
}).WithOpenApi(op => {
op.Summary = "Track a link click";
op.Description = "This adds to the stats for a given email in a broadcast or a sequence";
op.Parameters[0].Description = "This is the link's unique key";
op.Summary = "🔗 Track Link Click";
op.Description = "Records when a contact clicks a tracked link in an email. This endpoint collects engagement metrics for broadcasts and email sequences.";
op.Parameters[0].Description = "The unique tracking key for the clicked link (embedded in email links)";
return op;
});
}).WithTags("Public");

app.MapPost("/signup", async ([FromBody] SignUpRequest req, [FromServices] IDb db) => {
var contact = new Contact{
Expand All @@ -55,11 +55,11 @@ public static void MapRoutes(IEndpointRouteBuilder app)
return result;

}).WithOpenApi(op => {
op.Summary = "Sign up for the mailing list";
op.Description = "This is the form endpoint for signing up for the mail list";
op.RequestBody.Description = "This is the contact's information";
op.Summary = "✉️ Sign Up New Contact";
op.Description = "Adds a new contact to the mailing list. This endpoint is typically used by website signup forms to collect new subscribers.";
op.RequestBody.Description = "Contact information including name and email address";
return op;
});
}).WithTags("Public");
}

}
35 changes: 31 additions & 4 deletions server/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,35 @@
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Version = "0.0.1",
Title = "Tailwind Traders Mail Services API",
Description = "Transactional and bulk email sending services for Tailwind Traders.",
Version = "v1.0.2",
Title = "📧 Tailwind Traders Mail Services API",
Description = @"
## Welcome to Tailwind Traders Mail Services! 🚀

A powerful, modern email service platform for transactional and bulk email sending.

### Features
- **Transactional Emails**: Send individual emails triggered by user actions
- **Bulk Campaigns**: Send email broadcasts to your entire contact list or segments
- **Contact Management**: Manage subscribers with easy opt-in/opt-out functionality
- **Click Tracking**: Monitor engagement with link click tracking
- **Tag-based Organization**: Organize contacts with flexible tagging

### Getting Started
1. Use the `/signup` endpoint to add new contacts
2. Create broadcasts with the admin endpoints
3. Track engagement with click tracking
4. Manage your list with bulk operations

For more information, visit our [GitHub repository](https://github.com/scubaninja/dotNET-mail-demo).",
Contact = new OpenApiContact
{
Name = "Rob Conery, Aaron Wislang, and the Tailwind Traders Team",
Url = new Uri("https://tailwindtraders.dev")
},
License = new OpenApiLicense
{
Name = "MIT",
Name = "MIT License",
Url = new Uri("https://opensource.org/license/mit/")
}
});
Expand All @@ -36,11 +54,20 @@

var app = builder.Build();

app.UseStaticFiles();
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
options.RoutePrefix = string.Empty;
options.DocumentTitle = "Tailwind Traders Mail API";
options.InjectStylesheet("/css/swagger-custom.css");
options.DefaultModelsExpandDepth(2);
options.DefaultModelExpandDepth(2);
options.DisplayRequestDuration();
options.EnableDeepLinking();
options.EnableFilter();
options.ShowExtensions();
});
var conn = DB.Postgres();
Tailwind.Mail.Api.PublicRoutes.MapRoutes(app);
Expand Down
Loading
Loading