Cosmos.EmailServices
9.0.1
dotnet add package Cosmos.EmailServices --version 9.0.1
NuGet\Install-Package Cosmos.EmailServices -Version 9.0.1
<PackageReference Include="Cosmos.EmailServices" Version="9.0.1" />
paket add Cosmos.EmailServices --version 9.0.1
#r "nuget: Cosmos.EmailServices, 9.0.1"
// Install Cosmos.EmailServices as a Cake Addin #addin nuget:?package=Cosmos.EmailServices&version=9.0.1 // Install Cosmos.EmailServices as a Cake Tool #tool nuget:?package=Cosmos.EmailServices&version=9.0.1
Cosmos.EmailServices - Multi-service IEmailSender
This is an IEmailSender implementation for Cosmos CMS and for use with any ASP.NET Core Identity web app. Instructions for setting up each of these email services are provided in this documentation.
In one package it provides the following services:
- Azure Communication Services - Email Services.
- SendGrid (Twilio)
- SMTP service that supports TLS, user name and password.
- NoOp Email, an email service for dev/test that does nothing.
Installation
To install the package using CLI, run the following command:
dotnet add package Cosmos.EmailServices
To install the package using NuGet Package Manager, run the following command:
Install-Package Cosmos.EmailServices
Usage
In the user secrets of an ASP.NET Core Identity web app, add one of the following configuration depending on the service you want to use.
Azure Communication Services - Email Services
To configure for Azure Communication services, add the following configuration to the user secrets:
{
"AdminEmail": "your@emailaddress.com", // This is the default 'from to address',
"ConnectionStrings": {
{
"AzureCommunicationConnection" : "[Your connection string here]"
}
}
SendGrid (Twilio)
To configure for SendGrid, add the following configuration:
{
"AdminEmail": "your@emailaddress.com", // This is the default 'from to address',
"CosmosSendGridApiKey": "[Your SendGrid key here]",
}
SMTP service that supports TLS, user name and password
To configure for an SMTP service, add the following configuration:
{
"AdminEmail": "", // This is the default 'from to address',
"SmtpEmailProviderOptions" : {
"Host": "smtp.yourhost.com",
"Port": 587,
"UsesSsl": true, // False if uses TLS
"UserName": "yourusername",
"Password": "yourpassword"
}
}
NoOp Email
If none of the three settings above are given, this will install a NoOp email service.
Program.cs or ConfigureServices
method of Startup.cs
Add the following using:
using Cosmos.EmailServices;
Then add the following line:
builder.Services.AddCosmosEmailServices(builder.Configuration);
Example
For a full working example of this package, see the Cosmos CMS source code.
In the mean time, here is an example of how to use the email service in a Razor Page:
// <copyright file="ResetPassword.cshtml.cs" company="Moonrise Software, LLC">
// Copyright (c) Moonrise Software, LLC. All rights reserved.
// Licensed under the GNU Public License, Version 3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
// See https://github.com/MoonriseSoftwareCalifornia/CosmosCMS
// for more information concerning the license and the contributors participating to this project.
// </copyright>
namespace Cosmos.Cms.Areas.Identity.Pages.Account
{
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Cosmos.Cms.Common.Services.Configurations;
using Cosmos.Common.Data;
using Cosmos.EmailServices;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
/// <summary>
/// Reset password page model.
/// </summary>
[AllowAnonymous]
[EnableRateLimiting("fixed")]
public class ResetPasswordModel : PageModel
{
private readonly IOptions<SiteSettings> options;
private readonly ICosmosEmailSender emailSender;
private readonly ApplicationDbContext dbContext;
private readonly ILogger<ForgotPasswordModel> logger;
private readonly UserManager<IdentityUser> userManager;
/// <summary>
/// Initializes a new instance of the <see cref="ResetPasswordModel"/> class.
/// </summary>
/// <param name="userManager">User manager.</param>
/// <param name="options">Site settings.</param>
/// <param name="emailSender">Email sender service.</param>
/// <param name="dbContext">Database context.</param>
/// <param name="logger">Log service.</param>
public ResetPasswordModel(
UserManager<IdentityUser> userManager,
IOptions<SiteSettings> options,
IEmailSender emailSender,
ApplicationDbContext dbContext,
ILogger<ForgotPasswordModel> logger)
{
this.userManager = userManager;
this.options = options;
this.emailSender = (ICosmosEmailSender)emailSender;
this.dbContext = dbContext;
this.logger = logger;
}
/// <summary>
/// Gets or sets input model.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// Get handler.
/// </summary>
/// <param name="code">Reset password verification code.</param>
/// <returns>Returns an <see cref="IActionResult"/>.</returns>
public IActionResult OnGet(string code = null)
{
if (code == null)
{
return BadRequest("A code must be supplied for password reset.");
}
Input = new InputModel
{
Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code))
};
return Page();
}
/// <summary>
/// Post handler.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var homePage = await dbContext.Pages.Select(s => new { s.Title, s.UrlPath }).FirstOrDefaultAsync(f => f.UrlPath == "root");
var websiteName = homePage.Title ?? Request.Host.Host;
var admins = await userManager.GetUsersInRoleAsync("Administrators");
var emailHandler = new EmailHandler(emailSender, logger);
var user = await userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
await emailHandler.SendGeneralInfoTemplateEmail(
"User without an account tried to change a password",
"System Notification",
websiteName,
Request.Host.Host,
$"<p>This is a notification that '{Input.Email},' who does not have an account on this website, tried to change a password for website '{Request.Host.Host}' on {DateTime.UtcNow.ToString()} (UTC). No password reset email was sent.</p>",
admins.Select(s => s.Email).ToList());
// Don't reveal that the user does not exist
return RedirectToPage("./ResetPasswordConfirmation");
}
var result = await userManager.ResetPasswordAsync(user, Input.Code, Input.Password);
if (result.Succeeded)
{
// Notify the administrators of a password change.
await emailHandler.SendGeneralInfoTemplateEmail(
"Password was changed.",
"System Notification",
websiteName,
Request.Host.Host,
$"<p>This is a notification that '{Input.Email}' changed their password for website '{Request.Host.Host}' on {DateTime.UtcNow.ToString()} (UTC).</p>",
admins.Select(s => s.Email).ToList());
// Notify the user of a password change.
await emailHandler.SendGeneralInfoTemplateEmail(
"Password was changed.",
"System Notification",
websiteName,
Request.Host.Host,
$"<p>This is a confirmation that your password was changed for website '{Request.Host.Host}' on {DateTime.UtcNow.ToString()} (UTC).</p>",
Input.Email);
return RedirectToPage("./ResetPasswordConfirmation");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
/// <summary>
/// Form input model.
/// </summary>
public class InputModel
{
/// <summary>
/// Gets or sets user email address.
/// </summary>
[Required]
[EmailAddress]
public string Email { get; set; }
/// <summary>
/// Gets or sets error message (if any).
/// </summary>
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
public string Password { get; set; }
/// <summary>
/// Gets or sets confirm password field.
/// </summary>
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
/// <summary>
/// Gets or sets code field.
/// </summary>
public string Code { get; set; }
}
}
}
EMail Templates
This package comes with a few Email templates that can be used to format emails.
Each template consists of two files. One for HTML formatted email and another for text.
Here are examples of two files found in the 'Templates' folder:
GeneralInfo.html GeneralInfoTXT.txt
Here is an example of the text file:
{{Subject}}
{{Subtitle}}
From: {{WebsiteName}}
{{Body}}
Note the double brackes. This are spots in the email where content is inserted.
If you add templates to this project, please add them to the EmailTemplates.resx
file.
Here is an example of how to use the general email templates in the code:
await emailHandler.SendGeneralInfoTemplateEmail(
"Password was changed.",
"System Notification",
websiteName,
Request.Host.Host,
$"<p>This is a confirmation that your password was changed for website '{Request.Host.Host}' on {DateTime.UtcNow.ToString()} (UTC).</p>",
Input.Email);
For more options, see class EmailHandler.cs
.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. |
-
net9.0
- Azure.Communication.Email (>= 1.0.1)
- Azure.Identity (>= 1.13.2)
- HtmlAgilityPack (>= 1.11.72)
- Microsoft.AspNetCore.Identity.UI (>= 9.0.1)
- SendGrid (>= 9.29.3)
- System.Configuration.ConfigurationManager (>= 9.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
9.0.1 | 93 | 1/20/2025 |
9.0.0.5 | 155 | 1/15/2025 |
8.1.0.1 | 84 | 1/7/2025 |
8.0.11.31 | 124 | 11/21/2024 |
8.0.10.28 | 142 | 11/14/2024 |
8.0.10.26 | 111 | 11/13/2024 |
8.0.10.24 | 115 | 11/7/2024 |
8.0.10.20 | 118 | 11/1/2024 |
8.0.10.18 | 140 | 10/29/2024 |
8.0.10.12 | 135 | 10/25/2024 |
8.0.10.4 | 103 | 10/16/2024 |
8.0.8.1 | 104 | 10/2/2024 |
8.0.7.7 | 97 | 10/1/2024 |
8.0.6.6 | 114 | 9/7/2024 |
8.0.6.2 | 108 | 8/27/2024 |
8.0.4.13 | 141 | 4/18/2024 |
8.0.4.12 | 109 | 3/29/2024 |
8.0.3.1 | 205 | 1/5/2024 |
8.0.0.3 | 158 | 12/18/2023 |
Documentation dependencies updated.