aboutsummaryrefslogtreecommitdiff
path: root/lib/Emails.Transactional.Plugin/src/Endpoints/SendEndpoint.cs
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-08-28 21:54:26 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-08-28 21:54:26 -0400
commitb153adbd86e226ad805c2edbb90e4032d386a1b0 (patch)
tree9d3e5d7f2966c66e0264001cb38c67f74d6cf707 /lib/Emails.Transactional.Plugin/src/Endpoints/SendEndpoint.cs
parent964e81b81cdb430ecee8f67a68e3c616b3f339aa (diff)
Refactor overhaul, data extensions & Resend.com support
Diffstat (limited to 'lib/Emails.Transactional.Plugin/src/Endpoints/SendEndpoint.cs')
-rw-r--r--lib/Emails.Transactional.Plugin/src/Endpoints/SendEndpoint.cs160
1 files changed, 160 insertions, 0 deletions
diff --git a/lib/Emails.Transactional.Plugin/src/Endpoints/SendEndpoint.cs b/lib/Emails.Transactional.Plugin/src/Endpoints/SendEndpoint.cs
new file mode 100644
index 0000000..3e6f940
--- /dev/null
+++ b/lib/Emails.Transactional.Plugin/src/Endpoints/SendEndpoint.cs
@@ -0,0 +1,160 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: Emails.Transactional
+* File: SendEndpoint.cs
+*
+* SendEndpoint.cs is part of Emails.Transactional which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* Emails.Transactional is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* Emails.Transactional is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with Emails.Transactional. If not, see http://www.gnu.org/licenses/.
+*/
+
+
+using System;
+using System.Net;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Logging;
+using VNLib.Plugins;
+using VNLib.Plugins.Essentials;
+using VNLib.Plugins.Essentials.Extensions;
+using VNLib.Plugins.Essentials.Oauth;
+using VNLib.Plugins.Extensions.Validation;
+using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Loading.Sql;
+using VNLib.Plugins.Extensions.Data.Extensions;
+
+using Emails.Transactional.Mta;
+using Emails.Transactional.Templates;
+using Emails.Transactional.Transactions;
+
+namespace Emails.Transactional.Endpoints
+{
+
+ [ConfigurationName("send_endpoint")]
+ internal class SendEndpoint : O2EndpointBase
+ {
+ public const string OAUTH2_USER_SCOPE_PERMISSION = "email:send";
+
+ private static readonly EmailTransactionValidator Validator = new();
+
+ private readonly string FromAddress;
+
+ private readonly TransactionStore Transactions;
+ private readonly IMailTransferAgent EmailService;
+ private readonly ITemplateStorage Templates;
+
+ public SendEndpoint(PluginBase plugin, IConfigScope config)
+ {
+ string? path = config["path"].GetString();
+ FromAddress = config.GetRequiredProperty("from_address", e => e.GetString()!);
+
+ InitPathAndLog(path, plugin.Log);
+
+ //Load transactions
+ Transactions = new(plugin.GetContextOptions());
+
+ //init ail transfer agent
+ EmailService = plugin.GetOrCreateSingleton<MailTransferAgent>();
+
+ //Load templates
+ Templates = plugin.GetOrCreateSingleton<EmailTemplateStore>();
+ }
+
+ protected override async ValueTask<VfReturnType> PostAsync(HttpEntity entity)
+ {
+ //Make sure the user has the required scope to send an email
+ if (!entity.Session.HasScope(OAUTH2_USER_SCOPE_PERMISSION))
+ {
+ entity.CloseResponseError(HttpStatusCode.Forbidden, ErrorType.InvalidScope, "Your account does not have the required permissions scope");
+ return VfReturnType.VirtualSkip;
+ }
+
+ //Get the transaction request from the client
+ EmailTransaction? transaction = await entity.GetJsonFromFileAsync<EmailTransaction>();
+
+ if(transaction == null)
+ {
+ entity.CloseResponseError(HttpStatusCode.BadRequest, ErrorType.InvalidRequest, "No request body was received");
+ return VfReturnType.VirtualSkip;
+ }
+
+ TransactionResult webm = new()
+ {
+ Success = false
+ };
+
+ //Set a default from name/address if not set by user
+ transaction.From ??= FromAddress;
+ transaction.FromName ??= FromAddress;
+
+ //Specify user-id
+ transaction.UserId = entity.Session.UserID;
+
+ //validate the form
+ if (!Validator.Validate(transaction, webm))
+ {
+ //return errors
+ entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
+ return VfReturnType.VirtualSkip;
+ }
+
+ //Always add the template name to the model
+ transaction.Variables!["template_name"] = transaction.TemplateId!;
+
+ try
+ {
+ //Get the template
+ IEmailTemplate template = await Templates.GetTemplateAsync(transaction.TemplateId!, entity.EventCancellation);
+
+ //Render the template with the model
+ IEmailMessageData message = template.RenderTemplate(transaction.Variables!);
+
+ //Send the template
+ MtaResult result = await EmailService.SendEmailAsync(transaction, message, entity.EventCancellation);
+
+ //Set success flag
+ webm.Success = result.Success;
+ webm.SmtpStatus = result.Message;
+ }
+ //Handle a template lookup failure
+ catch(TemplateLookupFailedException ex)
+ {
+ Log.Debug("Failed to fetch template: {err}", transaction.TemplateId, ex.Message);
+
+ entity.CloseResponseError(HttpStatusCode.NotFound, ErrorType.InvalidRequest, "The requested template does not exist");
+ return VfReturnType.VirtualSkip;
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex);
+
+ //Write an error status message to the transaction store
+ webm.SmtpStatus = transaction.Result = ex.Message;
+ }
+
+ //Write transaction to db (we need to return the transaction id)
+ _ = await Transactions.AddOrUpdateAsync(transaction, entity.EventCancellation);
+
+ //Store the results object
+ webm.TransactionId = transaction.Id;
+
+ //Return the response object
+ entity.CloseResponse(webm);
+ return VfReturnType.VirtualSkip;
+ }
+ }
+}