IdSMTPBase.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.19 10/01/2005 16:31:20 ANeillans
  18. Minor bug fix for Exim compatibility.
  19. Rev 1.18 11/27/04 3:03:22 AM RLebeau
  20. Bug fix for 'STARTTLS' response handling
  21. Rev 1.17 6/11/2004 9:38:44 AM DSiders
  22. Added "Do not Localize" comments.
  23. Rev 1.16 2004.02.03 5:45:46 PM czhower
  24. Name changes
  25. Rev 1.15 2004.02.03 2:12:18 PM czhower
  26. $I path change
  27. Rev 1.14 1/28/2004 8:08:10 PM JPMugaas
  28. Fixed a bug in SendGreeting. It was sending an invalid EHLO and probably an
  29. invalid HELO command. The problem is that it was getting the computer name.
  30. There's an issue in NET with that which Kudzu commented on in IdGlobal.
  31. Thus, "EHLO<space>" and probably "HELO<space>" were being sent causing a
  32. failure. The fix is to to try to get the local computer's DNS name with
  33. GStack.HostName. We only use the computer name if GStack.HostName fails.
  34. Rev 1.13 1/25/2004 3:11:48 PM JPMugaas
  35. SASL Interface reworked to make it easier for developers to use.
  36. SSL and SASL reenabled components.
  37. Rev 1.12 2004.01.22 10:29:58 PM czhower
  38. Now supports default login mechanism with just username and pw.
  39. Rev 1.11 1/21/2004 4:03:24 PM JPMugaas
  40. InitComponent
  41. Rev 1.10 22/12/2003 00:46:36 CCostelloe
  42. .NET fixes
  43. Rev 1.9 4/12/2003 10:24:08 PM GGrieve
  44. Fix to Compile
  45. Rev 1.8 25/11/2003 12:24:22 PM SGrobety
  46. various IdStream fixes with ReadLn/D6
  47. Rev 1.7 10/17/2003 1:02:56 AM DSiders
  48. Added localization comments.
  49. Rev 1.6 2003.10.14 1:31:16 PM czhower
  50. DotNet
  51. Rev 1.5 2003.10.12 6:36:42 PM czhower
  52. Now compiles.
  53. Rev 1.4 10/11/2003 7:14:36 PM BGooijen
  54. Changed IdCompilerDefines.inc path
  55. Rev 1.3 10/10/2003 10:45:12 PM BGooijen
  56. DotNet
  57. Rev 1.2 2003.10.02 9:27:54 PM czhower
  58. DotNet Excludes
  59. Rev 1.1 6/15/2003 03:28:24 PM JPMugaas
  60. Minor class change.
  61. Rev 1.0 6/15/2003 01:06:48 PM JPMugaas
  62. TIdSMTP and TIdDirectSMTP now share common code in this base class.
  63. }
  64. unit IdSMTPBase;
  65. interface
  66. {$i IdCompilerDefines.inc}
  67. uses
  68. IdGlobal,
  69. IdEMailAddress,
  70. IdMessage,
  71. IdMessageClient,
  72. IdReply,
  73. IdTCPClient;
  74. //default property values
  75. const
  76. DEF_SMTP_PIPELINE = False;
  77. IdDEF_UseEhlo = True; //APR: default behavior
  78. IdDEF_UseVerp = False;
  79. const
  80. CAPAPIPELINE = 'PIPELINING'; {do not localize}
  81. CAPAVERP = 'VERP'; {do not localize}
  82. XMAILER_HEADER = 'X-Mailer'; {do not localize}
  83. const
  84. RCPTTO_ACCEPT : array [0..1] of Int16 = (250, 251);
  85. MAILFROM_ACCEPT : Int16 = 250;
  86. DATA_ACCEPT : Int16 = 354;
  87. DATA_PERIOD_ACCEPT : Int16 = 250;
  88. RSET_ACCEPT : Int16 = 250;
  89. const
  90. RSET_CMD = 'RSET'; {do not localize}
  91. MAILFROM_CMD = 'MAIL FROM:'; {do not localize}
  92. RCPTTO_CMD = 'RCPT TO:'; {do not localize}
  93. DATA_CMD = 'DATA'; {do not localize}
  94. type
  95. TIdSMTPFailedRecipient = procedure(Sender: TObject; const AAddress, ACode, AText: String;
  96. var VContinue: Boolean) of object;
  97. TIdSMTPFailedEHLO = procedure(Sender: TObject; const ACode, AText: String;
  98. var VContinue: Boolean) of object;
  99. TIdSMTPBase = class(TIdMessageClient)
  100. protected
  101. FMailAgent: string;
  102. FHeloName : String;
  103. FPipeline : Boolean;
  104. FUseEhlo : Boolean;
  105. FUseVerp : Boolean;
  106. FVerpDelims: string;
  107. FOnFailedRecipient: TIdSMTPFailedRecipient;
  108. FOnFailedEHLO: TIdSMTPFailedEHLO;
  109. //
  110. function GetSupportsTLS : Boolean; override;
  111. function GetReplyClass: TIdReplyClass; override;
  112. procedure InitComponent; override;
  113. procedure SendGreeting;
  114. procedure SetUseEhlo(const AValue: Boolean); virtual;
  115. procedure SetPipeline(const AValue: Boolean);
  116. procedure StartTLS;
  117. function FailedRecipientCanContinue(const AAddress: string): Boolean;
  118. //No pipeline send methods
  119. function WriteRecipientNoPipelining(const AEmailAddress: TIdEmailAddressItem): Boolean;
  120. procedure WriteRecipientsNoPipelining(AList: TIdEmailAddressList);
  121. procedure SendNoPipelining(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList);
  122. //pipeline send methods
  123. procedure WriteRecipientPipeLine(const AEmailAddress: TIdEmailAddressItem);
  124. procedure WriteRecipientsPipeLine(AList: TIdEmailAddressList);
  125. procedure SendPipelining(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList);
  126. //
  127. procedure InternalSend(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList); virtual;
  128. public
  129. procedure Send(AMsg: TIdMessage); overload; virtual;
  130. procedure Send(AMsg: TIdMessage; ARecipients: TIdEMailAddressList); overload; virtual;
  131. procedure Send(AMsg: TIdMessage; const AFrom: string); overload; virtual;
  132. procedure Send(AMsg: TIdMessage; ARecipients: TIdEMailAddressList; const AFrom: string); overload; virtual;
  133. published
  134. property MailAgent: string read FMailAgent write FMailAgent;
  135. property HeloName : string read FHeloName write FHeloName;
  136. property UseEhlo: Boolean read FUseEhlo write SetUseEhlo default IdDEF_UseEhlo;
  137. property PipeLine : Boolean read FPipeLine write SetPipeline default DEF_SMTP_PIPELINE;
  138. property UseVerp: Boolean read FUseVerp write FUseVerp default IdDEF_UseVerp;
  139. property VerpDelims: string read FVerpDelims write FVerpDelims;
  140. //
  141. property OnFailedRecipient: TIdSMTPFailedRecipient read FOnFailedRecipient write FOnFailedRecipient;
  142. property OnFailedEHLO: TIdSMTPFailedEHLO read FOnFailedEHLO write FOnFailedEHLO;
  143. end;
  144. implementation
  145. uses
  146. {$IFDEF VCL_XE3_OR_ABOVE}
  147. System.Classes,
  148. {$ENDIF}
  149. IdAssignedNumbers,
  150. IdExplicitTLSClientServerBase,
  151. IdGlobalProtocols, IdIOHandler, IdReplySMTP,
  152. IdSSL,
  153. IdStack, SysUtils; //required as we need to get the local computer DNS hostname.
  154. { TIdSMTPBase }
  155. function TIdSMTPBase.GetReplyClass:TIdReplyClass;
  156. begin
  157. Result := TIdReplySMTP;
  158. end;
  159. procedure TIdSMTPBase.InitComponent;
  160. begin
  161. inherited InitComponent;
  162. FRegularProtPort := IdPORT_SMTP;
  163. FImplicitTLSProtPort := IdPORT_ssmtp;
  164. FExplicitTLSProtPort := 587; // TODO: define a constant for this!
  165. FPipeLine := DEF_SMTP_PIPELINE;
  166. FUseEhlo := IdDEF_UseEhlo;
  167. FUseVerp := IdDEF_UseVerp;
  168. FMailAgent := '';
  169. FHeloName := '';
  170. Port := IdPORT_SMTP;
  171. end;
  172. function TIdSMTPBase.GetSupportsTLS: Boolean;
  173. begin
  174. Result := ( FCapabilities.IndexOf('STARTTLS') > -1 ); //do not localize
  175. end;
  176. procedure TIdSMTPBase.SendGreeting;
  177. var
  178. LNameToSend : String;
  179. LContinue: Boolean;
  180. LError: TIdReply;
  181. begin
  182. Capabilities.Clear;
  183. if HeloName <> '' then begin
  184. LNameToSend := HeloName;
  185. end else begin
  186. //Note: IndyComputerName gets the computer name.
  187. //This is not always reliable in Indy because in Dot.NET,
  188. //it is done with System.Windows.Forms.SystemInformation.ComputerName
  189. //and that requires that we link to a problematic dependancy (System.Windows.Forms).
  190. //Besides, I think RFC 821 was refering to the computer's Internet
  191. //DNS name. We use the Computer name only if we can't get the DNS name.
  192. LNameToSend := GStack.HostName;
  193. if LNameToSend = '' then
  194. begin
  195. LNameToSend := IndyComputerName;
  196. end;
  197. end;
  198. if UseEhlo then begin //APR: user can prevent EHLO
  199. if SendCmd('EHLO ' + LNameToSend) = 250 then begin {Do not Localize}
  200. Capabilities.AddStrings(LastCmdResult.Text);
  201. if Capabilities.Count > 0 then begin
  202. //we drop the initial greeting. We only want the feature list
  203. Capabilities.Delete(0);
  204. end;
  205. Exit;
  206. end;
  207. // RLebeau: let the user decide whether to continue with HELO or QUIT...
  208. LContinue := True;
  209. if Assigned(FOnFailedEhlo) then begin
  210. FOnFailedEhlo(Self, LastCmdResult.Code, LastCmdResult.Text.Text, LContinue);
  211. end;
  212. if not LContinue then begin
  213. LError := FReplyClass.Create(nil);
  214. try
  215. LError.Assign(LastCmdResult);
  216. Disconnect(True);
  217. LError.RaiseReplyError;
  218. finally
  219. FreeAndNil(LError);
  220. end;
  221. Exit;
  222. end;
  223. end;
  224. SendCmd('HELO ' + LNameToSend, 250); {Do not Localize}
  225. end;
  226. procedure TIdSMTPBase.SetPipeline(const AValue: Boolean);
  227. begin
  228. FPipeLine := AValue;
  229. if AValue then begin
  230. FUseEhlo := True;
  231. end;
  232. end;
  233. procedure TIdSMTPBase.SetUseEhlo(const AValue: Boolean);
  234. begin
  235. FUseEhlo := AValue;
  236. if not AValue then
  237. begin
  238. FPipeLine := False;
  239. end;
  240. end;
  241. procedure TIdSMTPBase.SendNoPipelining(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList);
  242. var
  243. LCmd: string;
  244. begin
  245. LCmd := MAILFROM_CMD + '<' + AFrom + '>'; {Do not Localize}
  246. if FUseVerp then begin
  247. if Capabilities.IndexOf(CAPAVERP) > -1 then begin
  248. LCmd := LCmd + ' VERP'; {Do not Localize}
  249. end else begin
  250. LCmd := LCmd + ' XVERP'; {Do not Localize}
  251. end;
  252. if FVerpDelims <> '' then begin
  253. LCmd := LCmd + '=' + FVerpDelims; {Do not Localize}
  254. end;
  255. end;
  256. // RLebeau 4/29/2013: DO NOT send a RSET command before the MAIL FROM command!
  257. // Some servers are buggy and will reset the entire session, including any
  258. // previously accepted authentication, when they are supposed to reset only
  259. // their mail sending buffers and nothing else. Send a RSET only if the mail
  260. // transaction fails and needs to be cleaned up...
  261. // TODO: make this configurable?
  262. //SendCmd(RSET_CMD);
  263. SendCmd(LCmd, MAILFROM_ACCEPT);
  264. try
  265. WriteRecipientsNoPipelining(ARecipients);
  266. SendCmd(DATA_CMD, DATA_ACCEPT);
  267. // TODO: if the server supports the UTF8SMTP extension, force TIdMessage
  268. // to encode headers as raw 8bit UTF-8, even if the TIdMessage.OnInitializeISO
  269. // event has a handler assigned...
  270. SendMsg(AMsg);
  271. SendCmd('.', DATA_PERIOD_ACCEPT); {Do not Localize}
  272. except
  273. on E: EIdSMTPReplyError do begin
  274. SendCmd(RSET_CMD);
  275. raise;
  276. end;
  277. on E: Exception do begin
  278. // the state of the communication is indeterminate at this point, so the
  279. // only sane thing to do is just close the socket...
  280. Disconnect(False);
  281. raise;
  282. end;
  283. end;
  284. end;
  285. procedure TIdSMTPBase.SendPipelining(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList);
  286. var
  287. LError : TIdReply;
  288. I, LFailedRecips : Integer;
  289. LCmd: string;
  290. LBufferingStarted: Boolean;
  291. function SetupErrorReply: TIdReply;
  292. begin
  293. Result := FReplyClass.Create(nil);
  294. Result.Assign(LastCmdResult);
  295. end;
  296. begin
  297. LError := nil;
  298. LCmd := MAILFROM_CMD + '<' + AFrom + '>'; {Do not Localize}
  299. if FUseVerp then begin
  300. if Capabilities.IndexOf(CAPAVERP) > -1 then begin
  301. LCmd := LCmd + ' VERP'; {Do not Localize}
  302. end else begin
  303. LCmd := LCmd + ' XVERP'; {Do not Localize}
  304. end;
  305. if FVerpDelims <> '' then begin
  306. LCmd := LCmd + '=' + FVerpDelims; {Do not Localize}
  307. end;
  308. end;
  309. try
  310. LBufferingStarted := not IOHandler.WriteBufferingActive;
  311. if LBufferingStarted then begin
  312. IOHandler.WriteBufferOpen;
  313. end;
  314. // RLebeau 4/29/2013: DO NOT send a RSET command before the MAIL FROM command!
  315. // Some servers are buggy and will reset the entire session, including any
  316. // previously accepted authentication, when they are supposed to reset only
  317. // their mail sending buffers and nothing else. Send a RSET only if the mail
  318. // transaction fails and needs to be cleaned up...
  319. // TODO: make this configurable?
  320. try
  321. //IOHandler.WriteLn(RSET_CMD);
  322. IOHandler.WriteLn(LCmd);
  323. WriteRecipientsPipeLine(ARecipients);
  324. IOHandler.WriteLn(DATA_CMD);
  325. if LBufferingStarted then begin
  326. IOHandler.WriteBufferClose;
  327. end;
  328. except
  329. if LBufferingStarted then begin
  330. IOHandler.WriteBufferCancel;
  331. end;
  332. raise;
  333. end;
  334. {
  335. //RSET
  336. if PosInSmallIntArray(GetResponse, RSET_ACCEPT) = -1 then begin
  337. LError := SetupErrorReply;
  338. end;
  339. }
  340. //MAIL FROM:
  341. if PosInSmallIntArray(GetResponse, MAILFROM_ACCEPT) = -1 then begin
  342. if not Assigned(LError) then begin
  343. LError := SetupErrorReply;
  344. end;
  345. end;
  346. //RCPT TO:
  347. if ARecipients.Count > 0 then begin
  348. LFailedRecips := 0;
  349. for I := 0 to ARecipients.Count - 1 do begin
  350. if PosInSmallIntArray(GetResponse, RCPTTO_ACCEPT) = -1 then begin
  351. Inc(LFailedRecips);
  352. if not FailedRecipientCanContinue(ARecipients[I].Address) then begin
  353. if not Assigned(LError) then begin
  354. LError := SetupErrorReply;
  355. end;
  356. end;
  357. end;
  358. end;
  359. if not Assigned(LError) and (LFailedRecips = ARecipients.Count) then begin
  360. LError := SetupErrorReply;
  361. end;
  362. end;
  363. //DATA - last in the batch
  364. if PosInSmallIntArray(GetResponse, DATA_ACCEPT) <> -1 then begin
  365. // TODO: if the server supports the UTF8SMTP extension, force TIdMessage
  366. // to encode headers as raw 8bit UTF-8, even if the TIdMessage.OnInitializeISO
  367. // event has a handler assigned...
  368. try
  369. SendMsg(AMsg);
  370. except
  371. // the state of the communication is indeterminate at this point, so the
  372. // only sane thing to do is just close the socket...
  373. Disconnect(False);
  374. raise;
  375. end;
  376. if PosInSmallIntArray(SendCmd('.'), DATA_PERIOD_ACCEPT) = -1 then begin {Do not Localize}
  377. if not Assigned(LError) then begin
  378. LError := SetupErrorReply;
  379. end;
  380. end;
  381. end else begin
  382. if not Assigned(LError) then begin
  383. LError := SetupErrorReply;
  384. end;
  385. end;
  386. if Assigned(LError) then begin
  387. SendCmd(RSET_CMD);
  388. LError.RaiseReplyError;
  389. end;
  390. finally
  391. FreeAndNil(LError);
  392. end;
  393. end;
  394. procedure TIdSMTPBase.StartTLS;
  395. var
  396. LIO : TIdSSLIOHandlerSocketBase;
  397. LSendQuitOnError: Boolean;
  398. begin
  399. LSendQuitOnError := True;
  400. try
  401. if (IOHandler is TIdSSLIOHandlerSocketBase) and (FUseTLS <> utNoTLSSupport) then
  402. begin
  403. LIO := TIdSSLIOHandlerSocketBase(IOHandler);
  404. //we check passthrough because we can either be using TLS currently with
  405. //implicit TLS support or because STARTLS was issued previously.
  406. if LIO.PassThrough then
  407. begin
  408. if SupportsTLS then
  409. begin
  410. if SendCmd('STARTTLS') = 220 then begin {do not localize}
  411. LSendQuitOnError := False;
  412. TLSHandshake;
  413. LSendQuitOnError := True;
  414. //send EHLO
  415. SendGreeting;
  416. end else begin
  417. ProcessTLSNegCmdFailed;
  418. end;
  419. end else begin
  420. ProcessTLSNotAvail;
  421. end;
  422. end;
  423. end;
  424. except
  425. Disconnect(LSendQuitOnError); // RLebeau: do not send the QUIT command during the TLS handshake
  426. Raise;
  427. end;
  428. end;
  429. function TIdSMTPBase.FailedRecipientCanContinue(const AAddress: string): Boolean;
  430. begin
  431. Result := Assigned(FOnFailedRecipient);
  432. if Result then begin
  433. FOnFailedRecipient(Self, AAddress, LastCmdResult.Code, LastCmdResult.Text.Text, Result);
  434. end;
  435. end;
  436. function TIdSMTPBase.WriteRecipientNoPipelining(const AEmailAddress: TIdEmailAddressItem): Boolean;
  437. var
  438. LReply: Int16;
  439. begin
  440. LReply := SendCmd(RCPTTO_CMD + '<' + AEMailAddress.Address + '>'); {do not localize}
  441. Result := PosInSmallIntArray(LReply, RCPTTO_ACCEPT) <> -1;
  442. end;
  443. procedure TIdSMTPBase.WriteRecipientPipeLine(const AEmailAddress: TIdEmailAddressItem);
  444. begin
  445. //we'll read the reply - LATER
  446. IOHandler.WriteLn(RCPTTO_CMD + '<' + AEMailAddress.Address + '>');
  447. end;
  448. procedure TIdSMTPBase.WriteRecipientsNoPipelining(AList: TIdEmailAddressList);
  449. var
  450. I, LFailedRecips: Integer;
  451. LContinue: Boolean;
  452. begin
  453. if AList.Count > 0 then begin
  454. LFailedRecips := 0;
  455. LContinue := True;
  456. for I := 0 to AList.Count - 1 do begin
  457. if not WriteRecipientNoPipelining(AList[I]) then begin
  458. Inc(LFailedRecips);
  459. if not FailedRecipientCanContinue(AList[I].Address) then begin
  460. LContinue := False;
  461. Break;
  462. end;
  463. end;
  464. end;
  465. if (not LContinue) or (LFailedRecips = AList.Count) then begin
  466. LastCmdResult.RaiseReplyError;
  467. end;
  468. end;
  469. end;
  470. procedure TIdSMTPBase.WriteRecipientsPipeLine(AList: TIdEmailAddressList);
  471. var
  472. I: integer;
  473. begin
  474. for I := 0 to AList.Count - 1 do begin
  475. WriteRecipientPipeLine(AList[I]);
  476. end;
  477. end;
  478. procedure TIdSMTPBase.InternalSend(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList);
  479. begin
  480. if Pipeline and (Capabilities.IndexOf(CAPAPIPELINE) > -1) then begin
  481. SendPipelining(AMsg, AFrom, ARecipients);
  482. end else begin
  483. SendNoPipelining(AMsg, AFrom, ARecipients);
  484. end;
  485. end;
  486. // this version of Send() uses the TIdMessage to determine both the
  487. // sender and the recipients...
  488. procedure TIdSMTPBase.Send(AMsg: TIdMessage);
  489. var
  490. LRecipients: TIdEMailAddressList;
  491. begin
  492. LRecipients := TIdEMailAddressList.Create(Self);
  493. try
  494. LRecipients.AddItems(AMsg.Recipients);
  495. LRecipients.AddItems(AMsg.CCList);
  496. LRecipients.AddItems(AMsg.BccList);
  497. Send(AMsg, LRecipients);
  498. finally
  499. FreeAndNil(LRecipients);
  500. end;
  501. end;
  502. // this version of Send() uses the TIdMessage to determine the
  503. // sender, but sends to the caller's specified recipients
  504. procedure TIdSMTPBase.Send(AMsg: TIdMessage; ARecipients: TIdEMailAddressList);
  505. var
  506. LSender: string;
  507. begin
  508. LSender := Trim(AMsg.Sender.Address);
  509. if LSender = '' then begin
  510. LSender := Trim(AMsg.From.Address);
  511. end;
  512. InternalSend(AMsg, LSender, ARecipients);
  513. end;
  514. // this version of Send() uses the TIdMessage to determine the
  515. // recipients, but sends using the caller's specified sender.
  516. // The sender can be empty, which is useful for server-generated
  517. // error messages...
  518. procedure TIdSMTPBase.Send(AMsg: TIdMessage; const AFrom: string);
  519. var
  520. LRecipients: TIdEMailAddressList;
  521. begin
  522. LRecipients := TIdEMailAddressList.Create(Self);
  523. try
  524. LRecipients.AddItems(AMsg.Recipients);
  525. LRecipients.AddItems(AMsg.CCList);
  526. LRecipients.AddItems(AMsg.BccList);
  527. Send(AMsg, LRecipients, AFrom);
  528. finally
  529. FreeAndNil(LRecipients);
  530. end;
  531. end;
  532. // this version of Send() uses the caller's specified sender and
  533. // recipients. The sender can be empty, which is useful for
  534. // server-generated error messages...
  535. procedure TIdSMTPBase.Send(AMsg: TIdMessage; ARecipients: TIdEMailAddressList;
  536. const AFrom: string);
  537. begin
  538. InternalSend(AMsg, AFrom, ARecipients);
  539. end;
  540. end.