Email Service Providers

v 16.0.0

Email Service Providers

The actual work of dispatching/sending an email is performed by an Email Service Provider (implements IEmailServiceProvider). Some providers requires settings, this can be implemented using a extension for the backoffice.

The package ships with four providers out of the box:

  • SMTP - Sends emails using MailKit
  • SMTP (Umbraco) - A MailKit-implementation that will reuse the Umbraco-settings in appSettings.json.
  • SMTP (Legacy) - Uses the obsoleted SmtpClient in System.Net.Mail.
  • SMTP Pickup Directory - Can be used during development/testing to place e-mails in a folder on the development/test-computer.

We also provide some open source implementations of providers that you can use in your project or use as a reference for your own custom implementations:

The Email Service Provider needs to be configured in the Administration-section for each Workspace.

Custom Email Service Provider

Use some of the open source providers above as inspiration. Here is a simple example of a "empty" provider:

using NewsletterStudio.Core.Models.System;
using NewsletterStudio.Core.Notifications;
using NewsletterStudio.Core.Sending;
using NewsletterStudio.Core.Sending.Providers;
using Umbraco.Cms.Core.Events;

namespace Demo.Web.Extensions.EmailServiceProvider;

public class CoolEmailCompanyEmailServiceProvider : IEmailServiceProvider
{
    private readonly IEventAggregator _eventAggregator;

    public string Alias => "coolEmail";

    public string DisplayName => "Cool Email";

    public Dictionary<string, object> Settings { get; set; }

    public CoolEmailCompanyEmailServiceProvider(IEventAggregator eventAggregator)
    {
        _eventAggregator = eventAggregator;
    }

    public SendOutConfiguration GetSendOutConfiguration()
    {
        return new SendOutConfiguration()
        {
            MaxItemsPerBatch = 10,
            SendBatchSize = 10
        };
    }

    public ErrorCollection ValidateSettings(Dictionary<string, object> settings)
    {
        var errors = new ErrorCollection();

        var apiKeyValue = settings["cc_apiKey"]?.ToString();

        if (string.IsNullOrEmpty(settings["cc_apiKey"]?.ToString()))
        {
            errors.Add(new ValidationError("cc_apiKey","API key is required"));
        }
        if (settings["cc_apiKey"]?.ToString() == "lorem")
        {
            errors.Add(new ValidationError("cc_apiKey", "API cannot be lorem"));
        }

        return errors;
    }

    public async Task SendAsync(List<SendEmailJob> batch)
    {
        foreach (var job in batch)
        {
            var fakeDtoForEmailService = new
            {
                sendTo = job.Message.To,
                body = job.Message.HtmlBody
            };

            // Fires the EmailSendingNotification to allow package consumers to make adjustments to
            // the model before calling upstream services.
            // This is optional for internal implementation but recommended if you plan to share
            await _eventAggregator.PublishAsync(new EmailSendingNotification(fakeDtoForEmailService)).ConfigureAwait(false);

            // Then send the email
            //_coolCompanyEmailApi.Send(fakeDtoForEmailService)

            var success = true;

            if (success)
            {
                job.Successful = true;
                job.ExternalId = "---id-if-provided-from-service";
            }
            else
            {
                job.ErrorMessage = "--error message---";
            }


        }

    }

    public async Task<CommandResult> SendAsync(EmailMessage message)
    {
        var fakeBulk = new List<SendEmailJob>();
        fakeBulk.Add(new SendEmailJob() { Message = message });

        await SendAsync(fakeBulk);

        var res = fakeBulk.First();

        if (res.Successful)
            return CommandResult.Successful();

        return CommandResult.Error(new ValidationError("", res.ErrorMessage));

    }

}

Adding the Email Service Provider to our list of services, in your startup code:

using NewsletterStudio.Core.Composing;
using Umbraco.Cms.Core.Composing;

namespace Demo.Web.Extensions.EmailServiceProvider;

public class CoolCompanyComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.NewsletterStudio().EmailServiceProviders.Append<CoolEmailCompanyEmailServiceProvider>();
    }
}

Then, in the Workspace administration, the new provider should show up here:

Configure custom provider

Adding UI for configuration

You can also provide an extension for the backoffice to mount a element for settings related to the provider.

First, create a element to render

import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, customElement, state } from '@newsletterstudio/umbraco/lit';
import { NsEmailServiceProviderUiBase } from '@newsletterstudio/umbraco/extensibility';
import {NS_ADMINISTRATION_WORKSPACE_CONTEXT, NsAdministrationWorkspaceContext} from '@newsletterstudio/umbraco/administration';
import { umbBindToValidation } from '@umbraco-cms/backoffice/validation';

@customElement('cool-email-email-service-provider-settings')
export class CoolEmailServiceProviderSettingsElement extends NsEmailServiceProviderUiBase<CoolEmailServiceProviderSettings> {
  
  #workspaceContext? : NsAdministrationWorkspaceContext;
  
  @state()
  workspaceKey? : string;
  
  constructor() {
    super();
    
    this.consumeContext(NS_ADMINISTRATION_WORKSPACE_CONTEXT,(instance?) => {
      this.#workspaceContext = instance;

      this.observe(this.#workspaceContext?.workspaceKey,(workspaceKey) => {
        this.workspaceKey = workspaceKey;
      });

    });
  }
  
  /**
   * Notice the name renderSettings(), the parameter provided will be an object will all settings
   * @param settings 
   * @returns 
   */
  renderSettings(settings : CoolEmailServiceProviderSettings) {
    return html`
      <ns-property
        label="API Key"
        description="Enter the API key for Cool Company" required>
        <uui-form-layout-item>
          <uui-input type="text"
                      .value=${settings.cc_apiKey ?? ''}
                      name="cc_apiKey"
                      @change=${(e:Event)=>this.updateValueFromEvent('cc_apiKey',e)}
                      label="API Key"}
                      ${umbBindToValidation(this,'$.cc_apiKey',settings.cc_apiKey)}
                      required></uui-input>
        </uui-form-layout-item>
      </ns-property>
          
    `

  }

  static styles = [UmbTextStyles, css`
    uui-input {width:100%;}
  `]
}

export default CoolEmailServiceProviderSettingsElement;

declare global {
  interface HTMLElementTagNameMap {
    'cool-email-email-service-provider-settings': CoolEmailServiceProviderSettingsElement;
  }
}

interface CoolEmailServiceProviderSettings {
  cc_apiKey : string;
}

Then register the element as a nsEmailServiceProviderSettingsUi extension.

import { ManifestEmailServiceProviderSettingsUi } from "@newsletterstudio/umbraco/extensibility";

const smtpCoolCompanyUi : ManifestEmailServiceProviderSettingsUi = {
  type: "nsEmailServiceProviderSettingsUi",
  name: "Cool Company Email Service Provider Settings",
  alias: "Cc.EmailServiceProviderSettings",
  element: () => import('./cool-email-email-service-provider-settings.element.js'),
  meta: {
    // this alias should match alias in IEmailServiceProvider-implementation
    alias : 'coolEmail' 
  }
};

export const manifests = [
    smtpCoolCompanyUi
]