Skip to content

Instantly share code, notes, and snippets.

@Al-Muhandis
Last active October 26, 2025 18:30
Show Gist options
  • Select an option

  • Save Al-Muhandis/6c29bd118963604716955f2a88831038 to your computer and use it in GitHub Desktop.

Select an option

Save Al-Muhandis/6c29bd118963604716955f2a88831038 to your computer and use it in GitHub Desktop.
CLI wrapper to send email via msmtp/sendemail (Linux specific)
unit unix_sendmail;
{ smtp mailer via standard Unix sendmail support }
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils
;
type
TSendEmailResult = (serUnknown, serOk, serErrCldNtSndEml);
{ TSendMail }
TSendMail = class
private
FAccount: String;
FBody: String;
FContentTransferEncoding: String;
FContentType: String;
FExecutable: String;
FFrom: String;
FOutputData: TStringList;
FReplyToAddress: String;
FToAddress: String;
FSendResult: TSendEmailResult;
FSubject: String;
procedure ParseOutput;
function EncodeHeader(const AValue: string): string;
public
constructor Create;
destructor Destroy; override;
function Send: Boolean;
property From: String read FFrom write FFrom;
property Account: String read FAccount write FAccount;
property ToAddress: String read FToAddress write FToAddress;
property ReplyToAddress: String read FReplyToAddress write FReplyToAddress;
property Subject: String read FSubject write FSubject;
property ContentType: String read FContentType write FContentType;
property Body: String read FBody write FBody;
property Executable: String read FExecutable write FExecutable;
property SendResult: TSendEmailResult read FSendResult write FSendResult;
property RawOutput: TStringList read FOutputData;
end;
procedure SendMail(const aToAddress, aSubject, aBody: string);
implementation
uses
process
;
const
_CldNtSndEml='could not send mail ';
procedure SendMail(const aToAddress, aSubject, aBody: string);
var
aSendMail: TSendMail;
begin
aSendMail:=TSendMail.Create;
try
aSendMail.ToAddress:=aToAddress;
aSendMail.Subject:=aSubject;
aSendMail.Body:=aBody;
aSendMail.Send;
finally
aSendMail.Free;
end;
end;
function ParseMSMTPOutput(const aOutput: String): TSendEmailResult;
begin
if aOutput.Contains(_CldNtSndEml) then
Exit(serErrCldNtSndEml);
Result:=serOk;
end;
{ TSendMail }
procedure TSendMail.ParseOutput;
begin
FSendResult:=ParseMSMTPOutput(FOutputData.Text);
end;
function TSendMail.EncodeHeader(const AValue: string): string;
begin
Result:=AValue; { #todo : May be encoding as an option? }
end;
constructor TSendMail.Create;
begin
FExecutable:='msmtp'; //'/usr/sbin/sendmail';
FOutputData:=TStringList.Create;
FContentType:='text/plain; charset=UTF-8';
FContentTransferEncoding:='8bit';
end;
destructor TSendMail.Destroy;
begin
FOutputData.Free;
inherited Destroy;
end;
function TSendMail.Send: Boolean;
var
aMailProcess: TProcess;
aMailData: TStringList;
begin
aMailData:=TStringList.Create;
try
aMailData.Add('MIME-Version: 1.0');
aMailData.Add('To: ' + FToAddress);
aMailData.Add('Subject: ' + EncodeHeader(FSubject));
if not FReplyToAddress.IsEmpty then
aMailData.Add('Reply-To: ' + FReplyToAddress);
if not FFrom.IsEmpty then
aMailData.Add('From: ' + FFrom);
if not FContentType.IsEmpty then
aMailData.Add('Content-Type: '+FContentType);
if not FContentTransferEncoding.IsEmpty then
aMailData.Add('Content-Transfer-Encoding: '+FContentTransferEncoding);
aMailData.Add(EmptyStr);
aMailData.Add(FBody);
aMailProcess := TProcess.Create(nil);
try
aMailProcess.Executable := FExecutable;
if not Account.IsEmpty then
begin
aMailProcess.Parameters.Add('-a');
aMailProcess.Parameters.Add(FAccount);
end;
aMailProcess.Parameters.Add('-t');
aMailProcess.Options := [poUsePipes, poStderrToOutPut];
aMailProcess.Execute;
aMailData.SaveToStream(aMailProcess.Input);
aMailProcess.CloseInput;
FOutputData.LoadFromStream(aMailProcess.Output);
ParseOutput;
Result:=FSendResult=serOk;
finally
aMailProcess.Free;
end;
finally
aMailData.Free;
end;
end;
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment