Skip to content
Merged
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
7 changes: 7 additions & 0 deletions Mailtrap.sln
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.ContactLis
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.ContactFields", "examples\Mailtrap.Example.ContactFields\Mailtrap.Example.ContactFields.csproj", "{5CEEEC08-7F1F-4F75-BC8D-6E80C54C21FD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.ContactEvents", "examples\Mailtrap.Example.ContactEvents\Mailtrap.Example.ContactEvents.csproj", "{AA91FC07-FE50-4DE1-A388-7AA0DB35C84A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -185,6 +187,10 @@ Global
{5CEEEC08-7F1F-4F75-BC8D-6E80C54C21FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5CEEEC08-7F1F-4F75-BC8D-6E80C54C21FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5CEEEC08-7F1F-4F75-BC8D-6E80C54C21FD}.Release|Any CPU.Build.0 = Release|Any CPU
{AA91FC07-FE50-4DE1-A388-7AA0DB35C84A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA91FC07-FE50-4DE1-A388-7AA0DB35C84A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA91FC07-FE50-4DE1-A388-7AA0DB35C84A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA91FC07-FE50-4DE1-A388-7AA0DB35C84A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -211,6 +217,7 @@ Global
{E7B8C1F2-9A3D-4C2E-8B7A-6D2F3A1E4B5C} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB}
{08B23D8C-7AAC-4CE7-830C-4DFF9F296BFB} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB}
{5CEEEC08-7F1F-4F75-BC8D-6E80C54C21FD} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB}
{AA91FC07-FE50-4DE1-A388-7AA0DB35C84A} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0FF614CC-FEBC-4C66-B3FC-FCB73EE511D7}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
79 changes: 79 additions & 0 deletions examples/Mailtrap.Example.ContactEvents/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using Mailtrap;
using Mailtrap.Accounts;
using Mailtrap.Contacts;
using Mailtrap.Contacts.Models;
using Mailtrap.Contacts.Requests;
using Mailtrap.Contacts.Responses;
using Mailtrap.ContactEvents;
using Mailtrap.ContactEvents.Models;
using Mailtrap.ContactEvents.Requests;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;


HostApplicationBuilder hostBuilder = Host.CreateApplicationBuilder(args);

IConfigurationSection config = hostBuilder.Configuration.GetSection("Mailtrap");

hostBuilder.Services.AddMailtrapClient(config);

using IHost host = hostBuilder.Build();

ILogger<Program> logger = host.Services.GetRequiredService<ILogger<Program>>();
IMailtrapClient mailtrapClient = host.Services.GetRequiredService<IMailtrapClient>();

try
{
var accountId = 12345;
IAccountResource accountResource = mailtrapClient.Account(accountId);

// Get resource for contacts collection
IContactCollectionResource contactsResource = accountResource.Contacts();

// Get all contacts for account
IList<Contact> contacts = await contactsResource.GetAll();

Contact? contact = contacts.Count > 0 ? contacts[0] : null;
if (contact is null)
{
logger.LogWarning("No contact found. Creating.");

// Create contact
var createContactRequest = new CreateContactRequest("email@example.com");
createContactRequest.Fields.Add("first_name", "John");
createContactRequest.Fields.Add("last_name", "Doe");
CreateContactResponse createContactResponse = await contactsResource.Create(createContactRequest);
contact = createContactResponse.Contact;
}
else
{
logger.LogInformation("Contact found with Id {ContactId} and Email {ContactEmail}.", contact.Id, contact.Email);
}

// Get resource for contact events collection
IContactsEventCollectionResource contactsEventsResource = contactsResource.Events(contact.Id);

// Create contacts event
var createContactsEventRequest = new CreateContactsEventRequest("MyFirstContactsEvent");
createContactsEventRequest.Params.Add("user_id", 101);
createContactsEventRequest.Params.Add("user_name", "John Smith");
createContactsEventRequest.Params.Add("is_active", true);
createContactsEventRequest.Params.Add("last_seen", null);
ContactsEvent contactsEvent = await contactsEventsResource.Create(createContactsEventRequest);

logger.LogInformation("Contacts Event created: {Name}", contactsEvent.Name);
logger.LogInformation("ID: {ContactId}", contactsEvent.ContactId);
logger.LogInformation("Created At: {ContactEmail}", contactsEvent.ContactEmail);
foreach (KeyValuePair<string, object?> param in contactsEvent.Params)
{
logger.LogInformation("Param: {ParamKey} = {ParamValue}", param.Key, param.Value);
}
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred during API call.");
Environment.ExitCode = 1;
return;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"profiles": {
"Project": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}
17 changes: 17 additions & 0 deletions examples/Mailtrap.Example.ContactEvents/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Warning",
"Microsoft": "Warning"
},
"Debug": {
"LogLevel": {
"Default": "Debug"
}
}
},
"Mailtrap": {
"ApiToken": "<API_KEY>"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Mailtrap.ContactEvents;

/// <summary>
/// Represents contacts event collection resource.
/// </summary>
public interface IContactsEventCollectionResource : IRestResource
{
/// <summary>
/// Creates a new contacts event with details specified by <paramref name="request"/>.
/// </summary>
///
/// <param name="request">
/// Request containing contacts event details for creation.
/// </param>
///
/// <param name="cancellationToken">
/// Token to control operation cancellation.
/// </param>
///
/// <returns>
/// Created contacts event details.
/// </returns>
public Task<ContactsEvent> Create(CreateContactsEventRequest request, CancellationToken cancellationToken = default);
}
48 changes: 48 additions & 0 deletions src/Mailtrap.Abstractions/ContactEvents/Models/ContactsEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
namespace Mailtrap.ContactEvents.Models;

/// <summary>
/// Represents Contacts Event details.
/// </summary>
public sealed record ContactsEvent
{
/// <summary>
/// Gets Contact identifier.
/// </summary>
///
/// <value>
/// Contact identifier.
/// </value>
[JsonPropertyName("contact_id")]
public string ContactId { get; set; } = string.Empty;

/// <summary>
/// Gets Contact email.
/// </summary>
///
/// <value>
/// Contact email.
/// </value>
[JsonPropertyName("contact_email")]
public string ContactEmail { get; set; } = string.Empty;

/// <summary>
/// Gets Contacts Event name.
/// </summary>
///
/// <value>
/// Contacts Event name.
/// </value>
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;

/// <summary>
/// Gets Contacts event params.
/// </summary>
///
/// <value>
/// Contacts event params.
/// </value>
[JsonPropertyName("params")]
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
public IDictionary<string, object?> Params { get; } = new Dictionary<string, object?>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
namespace Mailtrap.ContactEvents.Requests;

/// <summary>
/// Request object for creating a contacts event.
/// </summary>
public sealed record CreateContactsEventRequest : IValidatable
{
/// <summary>
/// Gets contacts event name.
/// </summary>
///
/// <value>
/// Contacts event name.
/// </value>
[JsonPropertyName("name")]
[JsonRequired]
public string Name { get; set; }

/// <summary>
/// Gets Contacts event params.
/// </summary>
///
/// <remarks>
/// <inheritdoc cref="CreateContactsEventRequest" path="/param[@name=name]"/>.
/// </remarks>
///
/// <value>
/// Contacts event params.
/// </value>
[JsonPropertyName("params")]
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
public IDictionary<string, object?> Params { get; } = new Dictionary<string, object?>();

/// <summary>
/// Primary instance constructor.
/// </summary>
///
/// <param name="name">
/// Contact event name.
/// </param>
///
/// <param name="parameters">
/// Contacts event parameters.
/// </param>
///
/// <remarks>
/// <paramref name="name"/> must be 1-255 characters long.
/// </remarks>
///
/// <exception cref="ArgumentNullException">
/// When <paramref name="name"/> is <see langword="null"/> or <see cref="string.Empty"/>.
/// </exception>
public CreateContactsEventRequest(string name, IDictionary<string, object?>? parameters = null)
{
Ensure.NotNullOrEmpty(name, nameof(name));

Name = name;

if (parameters is not null)
{
// Defensive copy to prevent post-ctor mutation.
Params = parameters is Dictionary<string, object?> dict
? new Dictionary<string, object?>(dict) // defensive copy when already a Dictionary<TKey, TValue>
: new Dictionary<string, object?>(parameters); // otherwise enumerate once
}
}

/// <summary>
/// Parameterless constructor for serializers.
/// </summary>
[JsonConstructor]
public CreateContactsEventRequest()
{
Name = string.Empty;
}

/// <inheritdoc />
public ValidationResult Validate()
{
return CreateContactsEventRequestValidator.Instance
.Validate(this)
.ToMailtrapValidationResult();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Mailtrap.ContactEvents.Validators;


/// <summary>
/// Validator for Create contacts event requests.<br />
/// Ensures contacts event's Name is not empty and length is within the allowed range.<br/>
/// Ensures all parameter keys (if any) are non-empty, and within the allowed length.
/// </summary>
public sealed class CreateContactsEventRequestValidator : AbstractValidator<CreateContactsEventRequest>
{
/// <summary>
/// Static validator instance for reuse.
/// </summary>
public static CreateContactsEventRequestValidator Instance { get; } = new();

/// <summary>
/// Primary constructor.
/// </summary>
public CreateContactsEventRequestValidator()
{
RuleFor(r => r.Name).NotEmpty().Length(1, 255);

When(r => r.Params != null && r.Params.Count > 0, () =>
{
RuleForEach(r => r.Params.Keys)
.Cascade(CascadeMode.Stop)
.NotEmpty()
.MaximumLength(255);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ public interface IContactCollectionResource : IRestResource
/// </exception>
public IContactsFieldResource Field(long fieldId);

/// <summary>
/// Gets contacts events collection resource for the account, represented by this resource instance.
/// </summary>
///
/// <param name="contactId">
/// Unique Contact ID to get resource for.
/// </param>
///
/// <returns>
/// <see cref="IContactsEventCollectionResource"/> for the account, represented by this resource instance.
/// </returns>
public IContactsEventCollectionResource Events(string contactId);

/// <summary>
/// Gets contacts.
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions src/Mailtrap.Abstractions/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
global using Mailtrap.ContactFields.Models;
global using Mailtrap.ContactFields.Requests;
global using Mailtrap.ContactFields.Validators;
global using Mailtrap.ContactEvents;
global using Mailtrap.ContactEvents.Models;
global using Mailtrap.ContactEvents.Requests;
global using Mailtrap.ContactEvents.Validators;
global using Mailtrap.Emails;
global using Mailtrap.Emails.Models;
global using Mailtrap.Emails.Requests;
Expand Down
13 changes: 13 additions & 0 deletions src/Mailtrap/ContactEvents/ContactsEventCollectionResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Mailtrap.ContactEvents;

/// <summary>
/// Implementation of Contacts Event Collection resource.
/// </summary>
internal sealed class ContactsEventCollectionResource : RestResource, IContactsEventCollectionResource
{
public ContactsEventCollectionResource(IRestResourceCommandFactory restResourceCommandFactory, Uri resourceUri)
: base(restResourceCommandFactory, resourceUri) { }

public async Task<ContactsEvent> Create(CreateContactsEventRequest request, CancellationToken cancellationToken = default)
=> await Create<CreateContactsEventRequest, ContactsEvent>(request, cancellationToken).ConfigureAwait(false);
}
Loading