Initial commit
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PowderCoating.Application.Interfaces;
|
||||
using SendGrid;
|
||||
using SendGrid.Helpers.Mail;
|
||||
|
||||
namespace PowderCoating.Infrastructure.Services;
|
||||
|
||||
public class EmailService : IEmailService
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<EmailService> _logger;
|
||||
private readonly IHostEnvironment _hostEnvironment;
|
||||
|
||||
public EmailService(IConfiguration configuration, ILogger<EmailService> logger, IHostEnvironment hostEnvironment)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
_hostEnvironment = hostEnvironment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an email via SendGrid. In non-production environments the subject is prefixed with
|
||||
/// "[ENV]" (e.g. "[Development]", "[Staging]") so recipients immediately know the email came
|
||||
/// from a test environment and don't act on it as if it were real.
|
||||
/// </summary>
|
||||
public async Task<(bool Success, string? ErrorMessage)> SendEmailAsync(
|
||||
string toEmail,
|
||||
string toName,
|
||||
string subject,
|
||||
string plainTextBody,
|
||||
string? htmlBody = null,
|
||||
byte[]? attachmentData = null,
|
||||
string? attachmentFilename = null,
|
||||
string? attachmentContentType = null,
|
||||
string? replyToEmail = null,
|
||||
string? replyToName = null)
|
||||
{
|
||||
var apiKey = _configuration["SendGrid:ApiKey"];
|
||||
if (string.IsNullOrWhiteSpace(apiKey) || apiKey.StartsWith("your-") || apiKey == "SG.placeholder")
|
||||
{
|
||||
_logger.LogWarning("SendGrid API key is not configured. Email to {ToEmail} skipped.", toEmail);
|
||||
return (false, "SendGrid not configured");
|
||||
}
|
||||
|
||||
if (!_hostEnvironment.IsProduction())
|
||||
subject = $"[{_hostEnvironment.EnvironmentName}] {subject}";
|
||||
|
||||
var fromEmail = _configuration["SendGrid:FromEmail"] ?? "noreply@example.com";
|
||||
var fromName = _configuration["SendGrid:FromName"] ?? "Powder Coating";
|
||||
|
||||
try
|
||||
{
|
||||
var client = new SendGridClient(apiKey);
|
||||
var msg = MailHelper.CreateSingleEmail(
|
||||
new EmailAddress(fromEmail, fromName),
|
||||
new EmailAddress(toEmail, toName),
|
||||
subject, plainTextBody, htmlBody ?? plainTextBody);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(replyToEmail))
|
||||
msg.SetReplyTo(new EmailAddress(replyToEmail, replyToName));
|
||||
|
||||
if (attachmentData != null && attachmentData.Length > 0 && !string.IsNullOrWhiteSpace(attachmentFilename))
|
||||
{
|
||||
await msg.AddAttachmentAsync(
|
||||
attachmentFilename,
|
||||
new MemoryStream(attachmentData),
|
||||
attachmentContentType ?? "application/octet-stream");
|
||||
}
|
||||
|
||||
return await DispatchAsync(client, msg, toEmail, subject);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception sending email to {ToEmail}", toEmail);
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an email with one or more file attachments via SendGrid. Intended for scenarios
|
||||
/// such as attaching job photos to a ready-for-pickup notification. Callers are responsible
|
||||
/// for keeping total attachment size under SendGrid's 30 MB per-message limit; this method
|
||||
/// skips any attachment whose Data array is empty.
|
||||
/// </summary>
|
||||
public async Task<(bool Success, string? ErrorMessage)> SendEmailWithAttachmentsAsync(
|
||||
string toEmail,
|
||||
string toName,
|
||||
string subject,
|
||||
string plainTextBody,
|
||||
string? htmlBody = null,
|
||||
IList<EmailAttachment>? attachments = null,
|
||||
string? replyToEmail = null,
|
||||
string? replyToName = null)
|
||||
{
|
||||
var apiKey = _configuration["SendGrid:ApiKey"];
|
||||
if (string.IsNullOrWhiteSpace(apiKey) || apiKey.StartsWith("your-") || apiKey == "SG.placeholder")
|
||||
{
|
||||
_logger.LogWarning("SendGrid API key is not configured. Email to {ToEmail} skipped.", toEmail);
|
||||
return (false, "SendGrid not configured");
|
||||
}
|
||||
|
||||
if (!_hostEnvironment.IsProduction())
|
||||
subject = $"[{_hostEnvironment.EnvironmentName}] {subject}";
|
||||
|
||||
var fromEmail = _configuration["SendGrid:FromEmail"] ?? "noreply@example.com";
|
||||
var fromName = _configuration["SendGrid:FromName"] ?? "Powder Coating";
|
||||
|
||||
try
|
||||
{
|
||||
var client = new SendGridClient(apiKey);
|
||||
var msg = MailHelper.CreateSingleEmail(
|
||||
new EmailAddress(fromEmail, fromName),
|
||||
new EmailAddress(toEmail, toName),
|
||||
subject, plainTextBody, htmlBody ?? plainTextBody);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(replyToEmail))
|
||||
msg.SetReplyTo(new EmailAddress(replyToEmail, replyToName));
|
||||
|
||||
if (attachments != null)
|
||||
{
|
||||
foreach (var attachment in attachments.Where(a => a.Data.Length > 0))
|
||||
{
|
||||
await msg.AddAttachmentAsync(
|
||||
attachment.Filename,
|
||||
new MemoryStream(attachment.Data),
|
||||
attachment.ContentType);
|
||||
}
|
||||
}
|
||||
|
||||
return await DispatchAsync(client, msg, toEmail, subject);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception sending email with attachments to {ToEmail}", toEmail);
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the built SendGrid message and interprets the HTTP response. Extracted so both
|
||||
/// send methods share identical dispatch and logging logic.
|
||||
/// </summary>
|
||||
private async Task<(bool Success, string? ErrorMessage)> DispatchAsync(
|
||||
SendGridClient client, SendGridMessage msg, string toEmail, string subject)
|
||||
{
|
||||
var response = await client.SendEmailAsync(msg);
|
||||
|
||||
if ((int)response.StatusCode >= 200 && (int)response.StatusCode < 300)
|
||||
{
|
||||
_logger.LogInformation("Email sent to {ToEmail}: {Subject}", toEmail, subject);
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
var body = await response.Body.ReadAsStringAsync();
|
||||
_logger.LogWarning("SendGrid returned {StatusCode} for {ToEmail}: {Body}", response.StatusCode, toEmail, body);
|
||||
return (false, $"HTTP {(int)response.StatusCode}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user