unit uEMailUtils;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
  { Introducing a new Type of Event to get the Errorcode }
  TMapiErrEvent = procedure(Sender: TObject; ErrCode: Integer) of object;

  TMapiControl = class(TComponent)
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  private
    { Private-Deklarationen }
    FSubject: string;
    FMailtext: string;
    FFromName: string;
    FFromAdress: string;
    FTOAdr: TStrings;
    FCCAdr: TStrings;
    FBCCAdr: TStrings;
    FAttachedFileName: TStrings;
    FDisplayFileName: TStrings;
    FShowDialog: Boolean;
    FUseAppHandle: Boolean;
    { Error Events: }
{    FOnUserAbort: TNotifyEvent;
    FOnMapiError: TMapiErrEvent;
    FOnSuccess: TNotifyEvent;}
    { +> Changes by Eugene Mayevski [mailto:Mayevski@eldos.org]}
    procedure SetToAddr(newValue: TStrings);
    procedure SetCCAddr(newValue: TStrings);
    procedure SetBCCAddr(newValue: TStrings);
    procedure SetAttachedFileName(newValue: TStrings);
    { +< Changes }
  protected
    { Protected-Deklarationen }
  public
    { Public-Deklarationen }
    ApplicationHandle: THandle;
    function Sendmail: Boolean;
    procedure Reset;
  published
    { Published-Deklarationen }
    property Subject: string read FSubject write FSubject;
    property Body: string read FMailText write FMailText;
    property FromName: string read FFromName write FFromName;
    property FromAdress: string read FFromAdress write FFromAdress;
    property Recipients: TStrings read FTOAdr write SetTOAddr;
    property CopyTo: TStrings read FCCAdr write SetCCAddr;
    property BlindCopyTo: TStrings read FBCCAdr write SetBCCAddr;
    property AttachedFiles: TStrings read FAttachedFileName write SetAttachedFileName;
    property DisplayFileName: TStrings read FDisplayFileName;
    property ShowDialog: Boolean read FShowDialog write FShowDialog;
    property UseAppHandle: Boolean read FUseAppHandle write FUseAppHandle;

    { Events: }
    {property OnUserAbort: TNotifyEvent read FOnUserAbort write FOnUserAbort;
    property OnMapiError: TMapiErrEvent read FOnMapiError write FOnMapiError;
    property OnSuccess: TNotifyEvent read FOnSuccess write FOnSuccess;}
  end;

function EsDireccionEMailValida(const Value: string): boolean;
function EnviarEMailMAPI(const AAsunto, ACuerpo, AFicheroAdjunto, ANombreRemitente, AEMailRemitente,
  ANombreDestinatario, AEMailDestinatario: String; AEnviarDirectamente: Boolean = false) : boolean;

implementation

uses
  MAPI;

function EsDireccionEMailValida(const Value: string): boolean;
  function CheckAllowed(const s: string): boolean;
  var
    i: integer;
  begin
    Result:= false;
    for i:= 1 to Length(s) do
    begin
      // illegal char in s -> no valid address
      if not (s[i] in ['a'..'z','A'..'Z','0'..'9','_','-','.']) then
        Exit;
    end;
    Result:= true;
  end;
var
  i: integer;
  namePart, serverPart: string;
begin // of IsValidEmail
  Result:= false;
  i:= Pos('@', Value);
  if (i = 0) or (pos('..', Value) > 0) then
    Exit;
  namePart:= Copy(Value, 1, i - 1);
  serverPart:= Copy(Value, i + 1, Length(Value));
  if (Length(namePart) = 0)         // @ or name missing
    or ((Length(serverPart) < 4))   // name or server missing or
    then Exit;                      // too short
  i:= Pos('.', serverPart);
  // must have dot and at least 3 places from end
  if (i < 2) or (i > (Length(serverPart) - 2)) then
    Exit;
  Result:= CheckAllowed(namePart) and CheckAllowed(serverPart);
end;

function EnviarEMailMAPI(const AAsunto, ACuerpo, AFicheroAdjunto, ANombreRemitente, AEMailRemitente,
  ANombreDestinatario, AEMailDestinatario: String; AEnviarDirectamente: Boolean = false) : boolean;
var
  AMAPIControl : TMapiControl;
begin
  AMAPIControl := TMapiControl.Create(NIL);
  try
    with AMAPIControl do
    begin
      Subject := AAsunto;
      Body := ACuerpo;
      FromName := ANombreRemitente;
      FromAdress := AEMailRemitente;

      Recipients.Add(AEMailDestinatario);
      AttachedFiles.Add(AFicheroAdjunto);

      ShowDialog := not AEnviarDirectamente;
    end;
    Result := AMAPIControl.Sendmail;
  finally
    FreeANDNIL(AMAPIControl);
  end;
end;


{ TMapiControl }

constructor TMapiControl.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  {FOnUserAbort := nil;
  FOnMapiError := nil;
  FOnSuccess := nil;}
  FSubject := '';
  FMailtext := '';
  FFromName := '';
  FFromAdress := '';
  FTOAdr := TStringList.Create;
  FCCAdr := TStringList.Create;
  FBCCAdr := TStringList.Create;
  FAttachedFileName := TStringList.Create;
  FDisplayFileName := TStringList.Create;
  FShowDialog := False;
  ApplicationHandle := Application.Handle;
end;

{ +> Changes by Eugene Mayevski [mailto:Mayevski@eldos.org]}

procedure TMapiControl.SetToAddr(newValue: TStrings);
begin
  FToAdr.Assign(newValue);
end;

procedure TMapiControl.SetCCAddr(newValue: TStrings);
begin
  FCCAdr.Assign(newValue);
end;

procedure TMapiControl.SetBCCAddr(newValue: TStrings);
begin
  FBCCAdr.Assign(newValue);
end;

procedure TMapiControl.SetAttachedFileName(newValue: TStrings);
begin
  FAttachedFileName.Assign(newValue);
end;
{ +< Changes }

destructor TMapiControl.Destroy;
begin
  FTOAdr.Free;
  FCCAdr.Free;
  FBCCAdr.Free;
  FAttachedFileName.Free;
  FDisplayFileName.Free;
  inherited destroy;
end;

{ Reset the fields for re-use}

procedure TMapiControl.Reset;
begin
  FSubject := '';
  FMailtext := '';
  FFromName := '';
  FFromAdress := '';
  FTOAdr.Clear;
  FCCAdr.Clear;
  FBCCAdr.Clear;
  FAttachedFileName.Clear;
  FDisplayFileName.Clear;
end;

{ Send the Mail via the API, this procedure composes and sends
  the Email }

function TMapiControl.Sendmail: Boolean;
var
  MapiMessage: TMapiMessage;
  MError: Cardinal;
  Sender: TMapiRecipDesc;
  PRecip, Recipients: PMapiRecipDesc;
  PFiles, Attachments: PMapiFileDesc;
  i: Integer;
  AppHandle: THandle;
begin
  { First we store the Application Handle, if not
    the Component might fail to send the Email or
    your calling Program gets locked up. }
  AppHandle := Application.Handle;

  { Initialize the Attachment Pointer, to keep Delphi quiet }
  PFiles := nil;

  { We need all recipients to alloc the memory }
  MapiMessage.nRecipCount := FTOAdr.Count + FCCAdr.Count + FBCCAdr.Count;
  GetMem(Recipients, MapiMessage.nRecipCount * sizeof(TMapiRecipDesc));

  try
    with MapiMessage do
      begin
        ulReserved := 0;
        { Setting the Subject: }
        lpszSubject := PChar(Self.FSubject);

        { ... the Body: }
        lpszNoteText := PChar(FMailText);

        lpszMessageType := nil;
        lpszDateReceived := nil;
        lpszConversationID := nil;
        flFlags := 0;

        { and the sender: (MAPI_ORIG) }
        Sender.ulReserved := 0;
        Sender.ulRecipClass := MAPI_ORIG;
        Sender.lpszName := PChar(FromName);
        Sender.lpszAddress := PChar(FromAdress);
        Sender.ulEIDSize := 0;
        Sender.lpEntryID := nil;
        lpOriginator := @Sender;

        PRecip := Recipients;

        { We have multiple recipients: (MAPI_TO)
          and setting up each: }
        if nRecipCount > 0 then
          begin
            for i := 1 to FTOAdr.Count do
              begin
                PRecip^.ulReserved := 0;
                PRecip^.ulRecipClass := MAPI_TO;
                { lpszName should carry the Name like in the
                  contacts or the adress book, I will take the
                  email adress to keep it short: }
                PRecip^.lpszName := PChar(FTOAdr.Strings[i - 1]);
                { If you use this component with Outlook97 or 2000
                  and not some of Express versions you will have to set
                  'SMTP:' in front of each (email-) adress. Otherwise
                  Outlook/Mapi will try to handle the Email on itself.
                  Sounds strange, just erease the 'SMTP:', compile, compose
                  a mail and take a look at the resulting email adresses
                  (right click).
                }
                { +> Changes by Andreas Hoerig [mailto:andreas.hoerig@sillner.com] }
                PRecip^.lpszAddress := StrNew(PChar('SMTP:' + FTOAdr.Strings[i - 1]));
                { +< Changes  }
                PRecip^.ulEIDSize := 0;
                PRecip^.lpEntryID := nil;
                Inc(PRecip);
              end;

            { Same with the carbon copy recipients: (CC, MAPI_CC) }
            for i := 1 to FCCAdr.Count do
              begin
                PRecip^.ulReserved := 0;
                PRecip^.ulRecipClass := MAPI_CC;
                PRecip^.lpszName := PChar(FCCAdr.Strings[i - 1]);
                { +> Changes by Andreas Hoerig [mailto:andreas.hoerig@sillner.com] }
                PRecip^.lpszAddress := StrNew(PChar('SMTP:' + FCCAdr.Strings[i - 1]));
                { +< Changes  }
                PRecip^.ulEIDSize := 0;
                PRecip^.lpEntryID := nil;
                Inc(PRecip);
              end;

            { ... and the blind copy recipients: (BCC, MAPI_BCC) }
            for i := 1 to FBCCAdr.Count do
              begin
                PRecip^.ulReserved := 0;
                PRecip^.ulRecipClass := MAPI_BCC;
                PRecip^.lpszName := PChar(FBCCAdr.Strings[i - 1]);
                { +> Changes by Andreas Hoerig [mailto:andreas.hoerig@sillner.com] }
                PRecip^.lpszAddress := StrNew(PChar('SMTP:' + FBCCAdr.Strings[i - 1]));
                { +< Changes  }
                PRecip^.ulEIDSize := 0;
                PRecip^.lpEntryID := nil;
                Inc(PRecip);
              end;
          end;
        lpRecips := Recipients;

        { Now we process the attachments: }
        nFileCount := FAttachedFileName.Count;
        if nFileCount > 0 then
          begin
            GetMem(Attachments, nFileCount * sizeof(TMapiFileDesc));
            PFiles := Attachments;

            { Fist setting up the display names (without path): }
            FDisplayFileName.Clear;
            for i := 1 to FAttachedFileName.Count do
              FDisplayFileName.Add(ExtractFileName(FAttachedFileName[i - 1]));

            if nFileCount > 0 then
              begin
                { Now we pass the attached file (their paths) to the
                  structure: }
                for i := 1 to FAttachedFileName.Count do
                  begin
                    { Setting the complete Path }
                    Attachments^.lpszPathName := PChar(FAttachedFileName.Strings[i - 1]);
                    { ... and the displayname: }
                    Attachments^.lpszFileName := PChar(FDisplayFileName.Strings[i - 1]);
                    Attachments^.ulReserved := 0;
                    Attachments^.flFlags := 0;
                    { Position has to be -1, please see the WinApi Help
                      for details. }
                    Attachments^.nPosition := Cardinal(-1);
                    Attachments^.lpFileType := nil;
                    Inc(Attachments);
                  end;
              end;
            lpFiles := PFiles;
          end
        else
          begin
            nFileCount := 0;
            lpFiles := nil;
          end;
      end;

    { Send the Mail, silent or verbose:
      Verbose means in Express a Mail is composed and shown as setup.
      In non-Express versions we show the Login-Dialog for a new
      session and after we have choosen the profile to use, the
      composed email is shown before sending

      Silent does currently not work for non-Express version. We have
      no Session, no Login Dialog so the system refuses to compose a
      new email. In Express Versions the email is sent in the
      background.

      Please Note: It seems that your success on the delivery depends
      on a combination of MAPI-Flags (MAPI_DIALOG, MAPI_LOGON_UI, ...)
      and your used OS and Office Version. I am currently using
      Win2K SP1 and Office 2K SP2 with no problems at all.
      If you experience problems on another versions, please try
      a different combination of flags for each purpose (Dialog or not).
      I would be glad to setup a table with working flags on
      each OS/Office combination, just drop me a line.

      Possible combinations are also (with Dialog):
      1. MAPI_DIALOG or MAPI_LOGON_UI MAPI_NEW_SESSION or MAPI_USE_DEFAULT
      2. MAPI_SIMPLE_DEFAULT

      See MAPI.PAS or MAPI.H (SDK) for more...
     }
    if FShowDialog then
      MError := MapiSendMail(0, AppHandle, MapiMessage, MAPI_DIALOG or MAPI_LOGON_UI or MAPI_NEW_SESSION, 0)
    else
      MError := MapiSendMail(0, AppHandle, MapiMessage, 0, 0);

    { Now we have to process the error messages. There are some
      defined in the MAPI unit please take a look at the unit to get
      familiar with it.
      I decided to handle USER_ABORT and SUCCESS as special and leave
      the rest to fire the "new" error event defined at the top (as
      generic error)

      Not treated as special (constants from mapi.pas):

      MAPI_E_FAILURE = 2;
      MAPI_E_LOGON_FAILURE = 3;
      MAPI_E_LOGIN_FAILURE = MAPI_E_LOGON_FAILURE;
      MAPI_E_DISK_FULL = 4;
      MAPI_E_INSUFFICIENT_MEMORY = 5;
      MAPI_E_ACCESS_DENIED = 6;
      MAPI_E_TOO_MANY_SESSIONS = 8;
      MAPI_E_TOO_MANY_FILES = 9;
      MAPI_E_TOO_MANY_RECIPIENTS = 10;
      MAPI_E_ATTACHMENT_NOT_FOUND = 11;
      MAPI_E_ATTACHMENT_OPEN_FAILURE = 12;
      MAPI_E_ATTACHMENT_WRITE_FAILURE = 13;
      MAPI_E_UNKNOWN_RECIPIENT = 14;
      MAPI_E_BAD_RECIPTYPE = 15;
      MAPI_E_NO_MESSAGES = 16;
      MAPI_E_INVALID_MESSAGE = 17;
      MAPI_E_TEXT_TOO_LARGE = 18;
      MAPI_E_INVALID_SESSION = 19;
      MAPI_E_TYPE_NOT_SUPPORTED = 20;
      MAPI_E_AMBIGUOUS_RECIPIENT = 21;
      MAPI_E_AMBIG_RECIP = MAPI_E_AMBIGUOUS_RECIPIENT;
      MAPI_E_MESSAGE_IN_USE = 22;
      MAPI_E_NETWORK_FAILURE = 23;
      MAPI_E_INVALID_EDITFIELDS = 24;
      MAPI_E_INVALID_RECIPS = 25;
      MAPI_E_NOT_SUPPORTED = 26;
    }

{    case MError of
      MAPI_E_USER_ABORT:
        begin
          if Assigned(FOnUserAbort) then
            FOnUserAbort(Self);
        end;
      SUCCESS_SUCCESS:
        begin
          if Assigned(FOnSuccess) then
            FOnSuccess(Self);
        end
      else
        begin
          if Assigned(FOnMapiError) then
            FOnMapiError(Self, MError);
        end;
    end;}
    Result := (MError = 0);

  finally
    { Finally we do the cleanups, the message should be on its way }
    { +> Changes by Andreas Hoerig [mailto:andreas.hoerig@sillner.com] }
    PRecip := Recipients;
    for i := 1 to MapiMessage.nRecipCount do
      begin
        StrDispose(PRecip^.lpszAddress);
        Inc(PRecip)
      end;
    { +< Changes }
    FreeMem(Recipients, MapiMessage.nRecipCount * sizeof(TMapiRecipDesc));
    { +> Changes due to Ken Halliwell [mailto:kjhalliwell@aol.com] }
    if Assigned(PFiles) then
      FreeMem(PFiles, MapiMessage.nFileCount * sizeof(TMapiFileDesc));
    { +< Changes }
  end;
end; 

end.
