Skip to content

Commit 4111d4e

Browse files
committed
SendGrid email provider and service
1 parent d930cec commit 4111d4e

File tree

8 files changed

+120
-11
lines changed

8 files changed

+120
-11
lines changed

Blogifier.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{6015AFDB-9
3333
docs\MySQL.md = docs\MySQL.md
3434
docs\Notifications.md = docs\Notifications.md
3535
docs\Security.md = docs\Security.md
36+
docs\SendGrid.md = docs\SendGrid.md
3637
docs\SqlServer.md = docs\SqlServer.md
3738
docs\Themes.md = docs\Themes.md
3839
docs\VideoAudio.md = docs\VideoAudio.md

docs/SendGrid.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Blogifier uses [SendGrid](https://sendgrid.com/) as email provider. It is one of the cloud
2+
email providers recommended for ASP.NET Core. It is free for under 100 daily emails and has
3+
paid options if someone needs more.
4+
5+
### SendGrid API
6+
To use SendGrid, blogger would need to sign up for service and get
7+
[API key](https://sendgrid.com/solutions/email-api/). Once you have API key, all that
8+
required is to update `appsettings.json`:
9+
10+
```cmd
11+
{
12+
"Blogifier": {
13+
"SendGridApiKey": "YOUR-SENDGRID-API-KEY",
14+
"SendGridEmailFrom": "admin@blog.com",
15+
"SendGridEmailFromName": "Blog admin"
16+
}
17+
}
18+
```
19+
20+
Currently, SendGrid only used by Newsletter widget. When guest subscribles to the blog via Newsletter,
21+
every time new post gets published, Blogifier goes over list of subscribers and sends notification
22+
about new publicatoin to every subscriber.
23+
24+
### Why not SMTP?
25+
Plain SMTP client routes calls to actual SMTP email server, like Gmail or HotMail.
26+
Unfortunately, most SMTP providers and hosters block 3rd party email traffic.
27+
Cloud providers have limitations, but much more reliable.

src/App/Pages/Admin/Posts/Edit.cshtml.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using Core.Services;
55
using Microsoft.AspNetCore.Mvc;
66
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
79
using System.Threading.Tasks;
810

911
namespace App.Pages.Admin.Posts
@@ -16,11 +18,13 @@ public class EditModel : AdminPageModel
1618

1719
IDataService _db;
1820
INotificationService _ns;
21+
ISendGridService _sg;
1922

20-
public EditModel(IDataService db, INotificationService ns)
23+
public EditModel(IDataService db, INotificationService ns, ISendGridService sg)
2124
{
2225
_db = db;
2326
_ns = ns;
27+
_sg = sg;
2428
}
2529

2630
public async Task OnGetAsync(int id)
@@ -83,6 +87,12 @@ public async Task<IActionResult> OnPostAsync()
8387
PostItem = item;
8488
Message = Resources.Saved;
8589

90+
if(PostItem.Status == SaveStatus.Publishing)
91+
{
92+
List<string> emails = _db.Newsletters.All().Select(n => n.Email).ToList();
93+
await _sg.SendNewsletters(PostItem, emails);
94+
}
95+
8696
return Redirect($"~/admin/posts/edit?id={PostItem.Id}");
8797
}
8898
}

src/App/app.db

0 Bytes
Binary file not shown.

src/App/appsettings.json

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
{
2-
"Foo": "foo",
32
"Blogifier": {
43
"DbProvider": "SQLite",
54
"ConnString": "DataSource=app.db",
6-
"Title": "Blog title",
7-
"Description": "Short description of the blog",
8-
"Cover": "data/admin/cover-desk.jpg",
9-
"Logo": "lib/img/logo-white.png",
10-
"Avatar": "lib/img/avatar.jpg",
11-
"Theme": "Standard",
12-
"BlogThemes": null,
13-
"ItemsPerPage": 10,
14-
"UseDescInPostList": true,
155
"ImportTypes": "zip,7z,xml,pdf,doc,docx,xls,xlsx,mp3,avi",
166
"ImageExtensions": "png,jpg,gif,bmp,tiff",
7+
"Avatar": "lib/img/avatar.jpg",
8+
"SendGridApiKey": "YOUR-SENDGRID-API-KEY",
9+
"SendGridEmailFrom": "admin@blog.com",
10+
"SendGridEmailFromName": "Blog admin",
1711
"DemoMode": false
1812
}
1913
}

src/Core/Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
1515
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.1.2" />
1616
<PackageReference Include="ReverseMarkdown" Version="2.0.0" />
17+
<PackageReference Include="Sendgrid" Version="9.10.0" />
1718
</ItemGroup>
1819

1920
</Project>

src/Core/Extensions/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public static IServiceCollection AddAppServices(this IServiceCollection services
3737
services.AddTransient<IImportService, ImportService>();
3838
services.AddTransient<INotificationService, NotificationService>();
3939
services.AddTransient<IWebService, WebService>();
40+
services.AddTransient<ISendGridService, SendGridService>();
4041

4142
services.AddTransient<UserManager<AppUser>>();
4243

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using Core.Data;
2+
using Microsoft.Extensions.Configuration;
3+
using Microsoft.Extensions.Logging;
4+
using SendGrid;
5+
using SendGrid.Helpers.Mail;
6+
using System.Collections.Generic;
7+
using System.Threading.Tasks;
8+
9+
namespace Core.Services
10+
{
11+
public interface ISendGridService
12+
{
13+
Task SendNewsletters(PostItem postItem, List<string> emails);
14+
Task SendEmail(string to, string subject, string content);
15+
}
16+
17+
public class SendGridService : ISendGridService
18+
{
19+
private readonly IConfiguration _config;
20+
private readonly ILogger _logger;
21+
22+
public SendGridService(IConfiguration config, ILogger<SendGridService> logger)
23+
{
24+
_config = config;
25+
_logger = logger;
26+
}
27+
28+
public async Task SendNewsletters(PostItem postItem, List<string> emails)
29+
{
30+
foreach (var email in emails)
31+
{
32+
var subject = "Newsletter: " + postItem.Title;
33+
var htmlContent = postItem.Description;
34+
35+
await SendEmail(email, subject, htmlContent);
36+
}
37+
}
38+
39+
public async Task SendEmail(string to, string subject, string content)
40+
{
41+
var section = _config.GetSection("Blogifier");
42+
43+
if(section != null)
44+
{
45+
var apiKey = section.GetValue<string>("SendGridApiKey");
46+
47+
if (!string.IsNullOrEmpty(apiKey))
48+
{
49+
try
50+
{
51+
var client = new SendGridClient(apiKey);
52+
53+
var fromEmail = section.GetValue<string>("SendGridEmailFrom") ?? "admin@blog.com";
54+
var fromName = section.GetValue<string>("SendGridEmailFromName") ?? "Blog admin";
55+
var from = new EmailAddress(fromEmail, fromName);
56+
57+
var msg = MailHelper.CreateSingleEmail(from, new EmailAddress(to), subject, content.StripHtml(), content);
58+
var response = await client.SendEmailAsync(msg);
59+
60+
if(response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
61+
{
62+
_logger.LogError("SendGrid service returned 'Unauthorized' - please verfiy SendGrid API key in configuration file");
63+
}
64+
}
65+
catch (System.Exception ex)
66+
{
67+
_logger.LogError(ex.Message);
68+
}
69+
}
70+
71+
}
72+
await Task.CompletedTask;
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)