Delphi Builder example

Date: 2020-10-16
Mail := MailBuilder
	.SetSender('no-reply@test.com')
	.AddRecipient('test@test.com')
	.AddRecipient('test@test.com')
	.AddCC('manager@test.com')
	.AddBCC('archive@test.com')
	.SetSubject('This is a subject')
	.SetContent('This is the content')
	.AddAttachment('Test.pdf', 'application/pdf')
	.Build();
	
Mail.Send();
unit Helpers.Email;

interface

uses
  SysUtils,
  Email.MailAttachment,
  Email.MailBuilder,
  Email.Mail,
  Email.MailServerBuilder,
  Email.MailPriority,
  Helpers.logging;

type
  TMailAttachment     = Email.MailAttachment.TMailAttachment;
  TMailBuilder        = Email.MailBuilder.TMailBuilder;
  TMailBuildResult    = Email.MailBuilder.TMailBuildResult;
  TMailServerSettings = Email.MailServerBuilder.TMailServerSettings;
  TSendMailResult     = Email.MailServerBuilder.TSendMailResult;
  TMailServerBuilder  = Email.MailServerBuilder.TMailServerBuilder;
  TMailPriority       = Email.MailPriority.TMailPriority;

function MailBuilder(AUseDefaults: Boolean = true): TMailBuilder;
var MailServer: TMailServerBuilder;
var MailLogger: TLogger;


implementation

function MailBuilder(AUseDefaults: Boolean = true): TMailBuilder;
begin
  Result := TMailBuilder.Create;
  if (AUseDefaults) then
    MailServer.AddDefaults(Result);
end;

var 
  DefaultMailLoggingConfig: TLoggerConfig;

initialization 
  MailServer:= TMailServerBuilder.Create;
  MailServer.LoadFromIniFile('.\SMTP.ini');

  DefaultMailLoggingConfig.FileName := '.\log\SMTP.log';
  DefaultMailLoggingConfig.LogLevel := ltTrace;  
  MailLogger := TLogger.Create(TLogger.ReadConfig('.\smtp-logging.ini', DefaultMailLoggingConfig));
  MailLogger.Info('SMTP log started.');

finalization
  FreeAndNil(MailServer);
  FreeAndNil(MailLogger)
end.



///////


unit Email.MailBuilder;

interface

uses
  variants, SysUtils, System.Classes, System.Generics.Collections, Email.Mail, Email.MailPriority;

type
  TMailBuildResult = record
    Valid: Boolean;
    ExecptionMessage: string;
    Mail: TMail;
  end;

  TMailBuilder = class
  private
    FMail: TMail;
    function _AddTo(AEmail: string): TMailBuilder;
    function _AddCC(AEmail: string): TMailBuilder;
    function _AddBCC(AEmail: string): TMailBuilder;
  public
    constructor Create;
    destructor Destroy; override;
    function SetFrom(AEmail: string): TMailBuilder;
    function AddTo(AEmail: string): TMailBuilder; overload;
    function AddTo(AEmails: TArray<string>): TMailBuilder; overload;
    function AddCC(AEmail: string): TMailBuilder; overload;
    function AddCC(AEmails: TArray<string>): TMailBuilder; overload;
    function AddBCC(AEmail: string): TMailBuilder; overload;
    function AddBCC(AEmails: TArray<string>): TMailBuilder; overload;
    function SetPriority(APriority: TMailPriority): TMailBuilder;

    function ClearTo(): TMailBuilder;
    function ClearCC(): TMailBuilder;
    function ClearBCC(): TMailBuilder;

    function SetSubject(AText: string): TMailBuilder;
    function ClearContent: TMailBuilder;    
    function AddContent(AText: string): TMailBuilder;

    function ClearHtmlContent: TMailBuilder;
    function AddHtmlContent(AText: string): TMailBuilder;
    function AddAttachment(AAttachmentName: string; AContentType: string; AContent: TStream; AFreeAfterSend: Boolean = false; AContentId: string = ''): TMailBuilder;

    function AddFileAttachment(AFileNames: TArray<string>): TMailBuilder; overload;
    function AddFileAttachment(AFileName: string): TMailBuilder; overload;
    function GetDebugContent: string;
    function Build: TMailBuildResult;
    class function IsEmailaddressValid(AEmail: String): Boolean;    
  end;

implementation

uses Email.MailAttachment, Email.MailServerBuilder;



constructor TMailBuilder.Create;
begin
  FMail := TMail.Create;
end;

destructor TMailBuilder.Destroy;
begin
  FreeAndNil(FMail);
  inherited;
end;

function TMailBuilder.GetDebugContent: string;
var
  stringList: TStringList;
begin
  stringList := TStringList.Create;
  try

    stringList.Add('From: ');
    stringList.Add(Self.FMail.From);        
    stringList.Add('-------------------------------------------------------------');        
    stringList.Add('To: ');
    stringList.Add(string.Join('; ' ,Self.FMail.ToList.ToArray));    
    stringList.Add('-------------------------------------------------------------');        
    stringList.Add('Cc: ');
    stringList.Add(string.Join('; ' ,Self.FMail.CCList.ToArray));    
    stringList.Add('-------------------------------------------------------------');     
    stringList.Add('Bcc: ');
    stringList.Add(string.Join('; ' ,Self.FMail.BCCList.ToArray));    
    stringList.Add('-------------------------------------------------------------');     
    stringList.Add('Subject: ');
    stringList.Add(Self.FMail.Subject);    
    stringList.Add('-------------------------------------------------------------');         
    stringList.Add('Content: ');
    stringList.Add(Self.FMail.Content);    
    stringList.Add('-------------------------------------------------------------');        
    Result := stringList.Text;
  finally
    stringList.Free;
  end;
end;

class function TMailBuilder.IsEmailaddressValid(AEmail: string): Boolean;
begin
  Result := AEmail.Trim() <> '';
end;

function TMailBuilder.Build: TMailBuildResult;
begin
  Result.Valid := false;
  if not IsEmailaddressValid(FMail.From) then
  begin
    Result.ExecptionMessage := 'Invalid mail "From" address';
  end
  else if FMail.ToList.Count = 0 then
  begin
    Result.ExecptionMessage := 'No valid mail "To" address';
  end
  else
  begin
    Result.Valid := true;
    Result.Mail := FMail;
  end;
end;

function TMailBuilder.SetFrom(AEmail: string): TMailBuilder;
begin
  if IsEmailaddressValid(AEmail) then
    FMail.From := Trim(AEmail);
  Result := self;
end;

function TMailBuilder.SetPriority(APriority: TMailPriority): TMailBuilder;
begin
  FMail.Priority := APriority;
  result := self;
end;

function TMailBuilder.AddTo(AEmails: TArray<string>): TMailBuilder;
var
  str: string;
begin
  for str in AEmails do
    _AddTo(str);
  Result := self;
end;

function TMailBuilder.AddTo(AEmail: string): TMailBuilder;
begin
  result := AddTo(AEmail.Split([';']));
end;

function TMailBuilder._AddTo(AEmail: string): TMailBuilder;
begin
  if IsEmailaddressValid(AEmail) then
    FMail.ToList.Add(Trim(AEmail));
  Result := self;
end;

function TMailBuilder.AddCC(AEmails: TArray<string>): TMailBuilder;
var
  str: string;
begin
  for str in AEmails do
    _AddCC(str);
  Result := self;
end;

function TMailBuilder.AddCC(AEmail: string): TMailBuilder;
begin
  result := AddCC(AEmail.Split([';']));
end;

function TMailBuilder._AddCC(AEmail: string): TMailBuilder;
begin
  if IsEmailaddressValid(AEmail) then
    FMail.CCList.Add(Trim(AEmail));
  Result := self;
end;


function TMailBuilder.ClearContent(): TMailBuilder;
begin
  FMail.Content := '';
  Result := self;
end;


function TMailBuilder.ClearHtmlContent(): TMailBuilder;
begin
  FMail.HtmlContent := '';
  Result := self;
end;


function TMailBuilder.AddContent(AText: string): TMailBuilder;
begin
  FMail.Content := FMail.Content + AText;
  Result := self;
end;

function TMailBuilder.AddBCC(AEmails: TArray<string>): TMailBuilder;
var
  str: string;
begin
  for str in AEmails do
    _AddBCC(str);
  Result := self;
end;

function TMailBuilder.AddBCC(AEmail: string): TMailBuilder;
begin
  Result := AddBCC(AEmail.Split([';']));
end;

function TMailBuilder._AddBCC(AEmail: string): TMailBuilder;
begin
  if IsEmailaddressValid(AEmail) then
    FMail.BCCList.Add(Trim(AEmail));
  Result := self;
end;

function TMailBuilder.SetSubject(AText: string): TMailBuilder;
begin
  FMail.Subject := AText;
  Result := self;
end;

function TMailBuilder.AddAttachment(AAttachmentName, AContentType: string; AContent: TStream; AFreeAfterSend: Boolean = false; AContentId: string = '')
  : TMailBuilder;
var
  attachment: TMailAttachment;
begin
  if not Assigned(AContent) then
    Exit(self);

  attachment.Name := AAttachmentName;
  attachment.ContentType := AContentType;
  attachment.Data := AContent;
  attachment.ContentId := AContentId;
  attachment.FreeAfterSend := AFreeAfterSend;
  FMail.Attachments.Add(attachment);
  Result := self;
end;

function TMailBuilder.AddFileAttachment(AFileNames: TArray<string>): TMailBuilder;
var
  str: string;
begin
  for str in AFileNames do
    AddFileAttachment(str);
  Result := self;
end;

function TMailBuilder.AddFileAttachment(AFileName: string): TMailBuilder;
var
  fileStream : TFileStream;
  mimetable  : TIdThreadSafeMimeTable;
  ContentType: string;
begin
  if not TFile.Exists(AFileName) then
    Exit(self);

  fileStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone); // TFile.OpenRead(AFileName);
  mimetable := TIdThreadSafeMimeTable.Create(true);
  try
    ContentType := mimetable.GetFileMIMEType(AFileName);
  finally
    mimetable.Free;
  end;
  Result := AddAttachment(TPath.GetFileName(fileStream.FileName), ContentType, fileStream, true);
end;

function TMailBuilder.AddHtmlContent(AText: string): TMailBuilder;
begin
  FMail.HtmlContent := FMail.HtmlContent + AText;
  Result := self;
end;

function TMailBuilder.ClearTo: TMailBuilder;
begin
  FMail.ToList.Clear;
  Result := self;
end;

function TMailBuilder.ClearCC: TMailBuilder;
begin
  FMail.CCList.Clear;
  Result := self;
end;

function TMailBuilder.ClearBCC: TMailBuilder;
begin
  FMail.BCCList.Clear;
  Result := self;
end;


end.

/////////

unit Email.MailServerBuilder;

interface

uses
 variants, SysUtils, System.Classes, System.Generics.Collections, Email.Mail, Email.MailBuilder, Email.MailAttachment;

type

  TMailServerSettings = class
  public
    Host: string;
    Port: Integer;
    UserName: string;
    Password: string;
    UseTLS: Boolean;
    DefaultFrom: string;
    DefaultTo: string;
    DefaultCC: string;
    DefaultBCC: string;
    DebugOverride: string;
    OverrideEmail: string;
    ReferencePrefix: string;
  end;

  TMailServerBuilder = class;

  TSendMailResult = record
  public
    Success: Boolean;
    ExceptionMessage: string;
  end;

  TMailServerBuilder = class
    FServerSettings: TMailServerSettings;
    function LoadFromIniFile(ACompleteFilename: string): TMailServerBuilder;
    function SetHost(AHost: string; APort: integer; AUseTLS: boolean = false): TMailServerBuilder;
    function SetCredentials(AUserName: string; APassword: string): TMailServerBuilder;
    function SendMail(AMailBuilder: TMailBuilder) : TSendMailResult;
    function GetReferenceText(AText: string): string;
    procedure AddDefaults(AMailBuilder: TMailBuilder);
    constructor Create;
    destructor Destroy; override;
  end;


  const EmailConfigSalt: word = 5326;



implementation

uses Helpers.Email;


constructor TMailServerBuilder.Create;
begin
  FServerSettings := TMailServerSettings.Create;
end;

destructor TMailServerBuilder.Destroy;
begin
  FreeAndNil(FServerSettings);
  inherited;
end;

function TMailServerBuilder.GetReferenceText(AText: string): string;
begin
  Result := #13#10+FServerSettings.ReferencePrefix+' '+AText;
end;

{
  [SMTP]
  host=
  port=
  useTLS=0
  Username=
  Password=
  Override=Email@email.com (keep mail the same, but clean To,Cc, Bcc)
  DebugOverride=test@test.com (Set everything in body of mail)
  ReferencePrefix= referentienummer:

  [DEFAULTS]
  From=test@test.com
  To=
  CC=
  BCC=test@test.com
}

function TMailServerBuilder.LoadFromIniFile(ACompleteFilename: string): TMailServerBuilder;
var
  iniFile: TMemIniFile;
  path: string;
  Section: string;
  password: string;
  EncriptedPassword: string;
begin

  path := ACompleteFilename;
  iniFile := TMemIniFile.Create(path, TEncoding.ANSI, false);
  try
    Section:= 'SMTP';
    FServerSettings.Host := iniFile.ReadString(Section, 'host', '');
    FServerSettings.Port := iniFile.ReadInteger(Section, 'port', 0);
    FServerSettings.UserName := iniFile.ReadString(Section, 'Username', '');
    password := iniFile.ReadString(Section, 'Password', '');
    EncriptedPassword := iniFile.ReadString(Section, 'PasswordEncrypted', '');
    if (password.Trim() <> '') then
    begin
      EncriptedPassword := TEncryptionHelper.Encrypt(password, EmailConfigSalt);
      iniFile.WriteString(Section, 'PasswordEncrypted', EncriptedPassword);
      iniFile.WriteString(Section, 'Password', '');
      iniFile.UpdateFile;
    end;
    FServerSettings.Password := TEncryptionHelper.Decrypt(EncriptedPassword, EmailConfigSalt);
    FServerSettings.UseTLS := iniFile.ReadBool(Section, 'UseTLS', false);
    FServerSettings.DebugOverride := iniFile.ReadString(Section, 'DebugOverride', '');
    FServerSettings.OverrideEmail := iniFile.ReadString(Section, 'Override', '');
    FServerSettings.ReferencePrefix := iniFile.ReadString(Section, 'ReferencePrefix', '');

    Section:= 'DEFAULTS';
    FServerSettings.DefaultFrom := iniFile.ReadString(Section, 'From', '');
    FServerSettings.DefaultTo := iniFile.ReadString(Section, 'To', '');
    FServerSettings.DefaultCC := iniFile.ReadString(Section, 'CC', '');
    FServerSettings.DefaultBCC := iniFile.ReadString(Section, 'BCC', '');
  finally
    iniFile.Free;
  end;
  Result := Self;
end;

function TMailServerBuilder.SendMail(AMailBuilder: TMailBuilder): TSendMailResult;
var
  smtpClient : TidSmtp;
  mailMessage : TIdMessage;
  email: string;
  attachment: TMailAttachment;
  idAttachment : TIdAttachmentMemory;
  SendMailResult : TSendMailResult;
  textPart : TIdText;
  SSL: TIdSSLIOHandlerSocketOpenSSL;
  mail : TMail;
  mailBuildResult : TMailBuildResult;
  debugContent: string;
begin
  try
    debugContent := AMailBuilder.GetDebugContent;
    if TMailBuilder.IsEmailaddressValid(FServerSettings.DebugOverride) then
    begin

      AMailBuilder
      .ClearTo()
      .ClearCC()
      .ClearBCC()
      .AddTo(FServerSettings.DebugOverride)
      .ClearContent()
      .AddContent(debugContent);
    end else if TMailBuilder.IsEmailaddressValid(FServerSettings.OverrideEmail) then
    begin
      AMailBuilder
      .ClearTo()
      .ClearCC()
      .ClearBCC()
      .AddTo(FServerSettings.OverrideEmail);
    end;

    mailBuildResult := AMailBuilder.Build;

    if (mailBuildResult.Valid) then
    begin
      mail := mailBuildResult.Mail;
    end else
    begin
      MailLogger.Warning('Failed to build valid Email, Err:' + mailBuildResult.ExecptionMessage);
      Result.Success := false;
      Result.ExceptionMessage := mailBuildResult.ExecptionMessage;
      Exit;
    end;

    smtpClient := TIdSMTP.Create;
    if FServerSettings.UseTLS then
    begin
      SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
      SSL.SSLOptions.Method := sslvTLSv1;
//      SSL.SSLOptions.SSLVersions := [sslvSSLv2, sslvSSLv23, sslvSSLv3, sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
      SSL.SSLOptions.Mode := sslmClient;     // or sslmUnassigned ?
      SSL.SSLOptions.VerifyMode := [];
      SSL.SSLOptions.VerifyDepth := 0;
      smtpClient.IOHandler := SSL;
      smtpClient.UseTLS := utUseExplicitTLS;
    end;

    mailMessage:= TIdMessage.Create(nil);
    try
      smtpClient.Host := FServerSettings.Host;
      smtpClient.Port := FServerSettings.Port;
      smtpClient.UserName := FServerSettings.UserName;
      smtpClient.Password := FServerSettings.Password;

      mailMessage.Body.Clear;
      mailMessage.ContentType := 'multipart/related';
      mailMessage.Priority := TIdMessagePriority(mail.Priority);

      mailMessage.From.Text := mail.From;
      mailMessage.Subject := mail.Subject;

      for email in mail.ToList do
        mailMessage.Recipients.Add().Text := email;

      for email in mail.CCList do
        mailMessage.CCList.Add().Text := email;

      for email in mail.BCCList do
        mailMessage.BCCList.Add().Text := email;

      mailMessage.ContentType := 'multipart/related';
      if (mail.HtmlContent.Length > 0) then
      begin
        textPart := TIdText.Create(mailMessage.MessageParts);
        textPart.Body.Text := mail.HtmlContent;
        textPart.ContentType := 'text/html';
        textPart.ContentDisposition := 'inline';
      end;

      textPart := TIdText.Create(mailMessage.MessageParts);
      textPart.Body.Text := mail.Content;
      textPart.ContentType := 'text/plain';
      textPart.ContentDisposition := 'inline';

      for attachment in mail.Attachments do
      begin
        attachment.Data.Position := 0;
        idAttachment := TIdAttachmentMemory.Create(mailMessage.MessageParts, attachment.Data);
        idAttachment.ContentType := attachment.ContentType;
        idAttachment.ContentID := attachment.ContentId;
        idAttachment.FileName := attachment.Name;
      end;

      MailLogger.Trace(FServerSettings.Host +':'+ IntToStr(FServerSettings.Port) + '; Username:' + FServerSettings.UserName);
      smtpClient.Connect;
      smtpClient.Send(mailMessage);
      smtpClient.Disconnect(true);



     finally
      for attachment in mail.Attachments do
      begin
        if (attachment.FreeAfterSend) then
        begin
          attachment.Data.Free;
        end;
      end;


      FreeAndNil(mailMessage);
      FreeAndNil(smtpClient);
      FreeAndNil(AMailBuilder);
    end;
    SendMailResult.Success := true;
    MailLogger.Info('Mail successfully sent.');
  Except
    on E: Exception do
    begin
      SendMailResult.Success := false;
      SendMailResult.ExceptionMessage := E.Message;
      MailLogger.Error('Failed to send mail.', E);
      MailLogger.Trace('FAILED EMAIL CONTENT: ' + #13#10 + debugContent);
    end;
  end;
  Result := SendMailResult;
end;

function TMailServerBuilder.SetCredentials(AUserName: string; APassword: string): TMailServerBuilder;
begin
  FServerSettings.UserName := AUserName;
  FServerSettings.Password := APassword;
  result := Self;
end;

procedure TMailServerBuilder.AddDefaults(AMailBuilder: TMailBuilder);
begin
  AMailBuilder.SetFrom(FServerSettings.DefaultFrom);
  AMailBuilder.AddTo(FServerSettings.DefaultTo.Split([';', ',']) );
  AMailBuilder.AddCC(FServerSettings.DefaultCC.Split([';', ',']));
  AMailBuilder.AddBCC(FServerSettings.DefaultBCC.Split([';', ',']));
end;

function TMailServerBuilder.SetHost(AHost: string; APort: integer; AUseTLS: boolean): TMailServerBuilder;
begin
  FServerSettings.Host := AHost;
  FServerSettings.Port := APort;
  FServerSettings.UseTLS := AUseTLS;
  result := Self;
end;



end.

41550cookie-checkDelphi Builder example