IdSMTPBase.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  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. Classes,
  69. IdGlobal,
  70. IdEMailAddress,
  71. IdMessage,
  72. IdMessageClient,
  73. IdReply,
  74. IdTCPClient;
  75. //default property values
  76. const
  77. DEF_SMTP_PIPELINE = False;
  78. IdDEF_UseEhlo = True; //APR: default behavior
  79. IdDEF_UseVerp = False;
  80. const
  81. CAPAPIPELINE = 'PIPELINING'; {do not localize}
  82. CAPAVERP = 'VERP'; {do not localize}
  83. XMAILER_HEADER = 'X-Mailer'; {do not localize}
  84. const
  85. RCPTTO_ACCEPT : array [0..1] of Int16 = (250, 251);
  86. MAILFROM_ACCEPT : Int16 = 250;
  87. DATA_ACCEPT : Int16 = 354;
  88. DATA_PERIOD_ACCEPT : Int16 = 250;
  89. RSET_ACCEPT : Int16 = 250;
  90. const
  91. RSET_CMD = 'RSET'; {do not localize}
  92. MAILFROM_CMD = 'MAIL FROM:'; {do not localize}
  93. RCPTTO_CMD = 'RCPT TO:'; {do not localize}
  94. DATA_CMD = 'DATA'; {do not localize}
  95. type
  96. TIdSMTPFailedRecipient = procedure(Sender: TObject; const AAddress, ACode, AText: String;
  97. var VContinue: Boolean) of object;
  98. TIdSMTPFailedEHLO = procedure(Sender: TObject; const ACode, AText: String;
  99. var VContinue: Boolean) of object;
  100. TIdSMTPBase = class(TIdMessageClient)
  101. protected
  102. FMailAgent: string;
  103. FHeloName : String;
  104. FPipeline : Boolean;
  105. FUseEhlo : Boolean;
  106. FUseVerp : Boolean;
  107. FVerpDelims: string;
  108. FOnFailedRecipient: TIdSMTPFailedRecipient;
  109. FOnFailedEHLO: TIdSMTPFailedEHLO;
  110. //
  111. function GetSupportsTLS : Boolean; override;
  112. function GetReplyClass: TIdReplyClass; 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. constructor Create(AOwner: TComponent); override;
  130. procedure Send(AMsg: TIdMessage); overload; virtual;
  131. procedure Send(AMsg: TIdMessage; ARecipients: TIdEMailAddressList); overload; virtual;
  132. procedure Send(AMsg: TIdMessage; const AFrom: string); overload; virtual;
  133. procedure Send(AMsg: TIdMessage; ARecipients: TIdEMailAddressList; const AFrom: string); overload; virtual;
  134. published
  135. property MailAgent: string read FMailAgent write FMailAgent;
  136. property HeloName : string read FHeloName write FHeloName;
  137. property UseEhlo: Boolean read FUseEhlo write SetUseEhlo default IdDEF_UseEhlo;
  138. property PipeLine : Boolean read FPipeLine write SetPipeline default DEF_SMTP_PIPELINE;
  139. property UseVerp: Boolean read FUseVerp write FUseVerp default IdDEF_UseVerp;
  140. property VerpDelims: string read FVerpDelims write FVerpDelims;
  141. //
  142. property OnFailedRecipient: TIdSMTPFailedRecipient read FOnFailedRecipient write FOnFailedRecipient;
  143. property OnFailedEHLO: TIdSMTPFailedEHLO read FOnFailedEHLO write FOnFailedEHLO;
  144. end;
  145. implementation
  146. uses
  147. IdAssignedNumbers,
  148. IdExplicitTLSClientServerBase,
  149. IdGlobalProtocols, IdIOHandler, IdReplySMTP,
  150. IdSSL,
  151. IdStack, SysUtils; //required as we need to get the local computer DNS hostname.
  152. { TIdSMTPBase }
  153. constructor TIdSMTPBase.Create(AOwner: TComponent);
  154. begin
  155. inherited Create(AOwner);
  156. FRegularProtPort := IdPORT_SMTP;
  157. FImplicitTLSProtPort := IdPORT_ssmtp;
  158. FExplicitTLSProtPort := 587; // TODO: define a constant for this!
  159. FPipeLine := DEF_SMTP_PIPELINE;
  160. FUseEhlo := IdDEF_UseEhlo;
  161. FUseVerp := IdDEF_UseVerp;
  162. FMailAgent := '';
  163. FHeloName := '';
  164. Port := IdPORT_SMTP;
  165. end;
  166. function TIdSMTPBase.GetReplyClass:TIdReplyClass;
  167. begin
  168. Result := TIdReplySMTP;
  169. end;
  170. function TIdSMTPBase.GetSupportsTLS: Boolean;
  171. begin
  172. Result := ( FCapabilities.IndexOf('STARTTLS') > -1 ); //do not localize
  173. end;
  174. procedure TIdSMTPBase.SendGreeting;
  175. var
  176. LNameToSend : String;
  177. LContinue: Boolean;
  178. LError: TIdReply;
  179. begin
  180. Capabilities.Clear;
  181. if HeloName <> '' then begin
  182. LNameToSend := HeloName;
  183. end else begin
  184. //Note: IndyComputerName gets the computer name.
  185. //This is not always reliable in Indy because in Dot.NET,
  186. //it is done with System.Windows.Forms.SystemInformation.ComputerName
  187. //and that requires that we link to a problematic dependancy (System.Windows.Forms).
  188. //Besides, I think RFC 821 was refering to the computer's Internet
  189. //DNS name. We use the Computer name only if we can't get the DNS name.
  190. LNameToSend := GStack.HostName;
  191. if LNameToSend = '' then
  192. begin
  193. LNameToSend := IndyComputerName;
  194. end;
  195. end;
  196. if UseEhlo then begin //APR: user can prevent EHLO
  197. if SendCmd('EHLO ' + LNameToSend) = 250 then begin {Do not Localize}
  198. Capabilities.AddStrings(LastCmdResult.Text);
  199. if Capabilities.Count > 0 then begin
  200. //we drop the initial greeting. We only want the feature list
  201. Capabilities.Delete(0);
  202. end;
  203. Exit;
  204. end;
  205. // RLebeau: let the user decide whether to continue with HELO or QUIT...
  206. LContinue := True;
  207. if Assigned(FOnFailedEhlo) then begin
  208. FOnFailedEhlo(Self, LastCmdResult.Code, LastCmdResult.Text.Text, LContinue);
  209. end;
  210. if not LContinue then begin
  211. LError := FReplyClass.Create(nil);
  212. try
  213. LError.Assign(LastCmdResult);
  214. Disconnect(True);
  215. LError.RaiseReplyError;
  216. finally
  217. FreeAndNil(LError);
  218. end;
  219. Exit;
  220. end;
  221. end;
  222. SendCmd('HELO ' + LNameToSend, 250); {Do not Localize}
  223. end;
  224. procedure TIdSMTPBase.SetPipeline(const AValue: Boolean);
  225. begin
  226. FPipeLine := AValue;
  227. if AValue then begin
  228. FUseEhlo := True;
  229. end;
  230. end;
  231. procedure TIdSMTPBase.SetUseEhlo(const AValue: Boolean);
  232. begin
  233. FUseEhlo := AValue;
  234. if not AValue then
  235. begin
  236. FPipeLine := False;
  237. end;
  238. end;
  239. procedure TIdSMTPBase.SendNoPipelining(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList);
  240. var
  241. LCmd: string;
  242. begin
  243. LCmd := MAILFROM_CMD + '<' + AFrom + '>'; {Do not Localize}
  244. if FUseVerp then begin
  245. if Capabilities.IndexOf(CAPAVERP) > -1 then begin
  246. LCmd := LCmd + ' VERP'; {Do not Localize}
  247. end else begin
  248. LCmd := LCmd + ' XVERP'; {Do not Localize}
  249. end;
  250. if FVerpDelims <> '' then begin
  251. LCmd := LCmd + '=' + FVerpDelims; {Do not Localize}
  252. end;
  253. end;
  254. // RLebeau 4/29/2013: DO NOT send a RSET command before the MAIL FROM command!
  255. // Some servers are buggy and will reset the entire session, including any
  256. // previously accepted authentication, when they are supposed to reset only
  257. // their mail sending buffers and nothing else. Send a RSET only if the mail
  258. // transaction fails and needs to be cleaned up...
  259. // TODO: make this configurable?
  260. //SendCmd(RSET_CMD);
  261. SendCmd(LCmd, MAILFROM_ACCEPT);
  262. try
  263. WriteRecipientsNoPipelining(ARecipients);
  264. SendCmd(DATA_CMD, DATA_ACCEPT);
  265. // TODO: if the server supports the UTF8SMTP extension, force TIdMessage
  266. // to encode headers as raw 8bit UTF-8, even if the TIdMessage.OnInitializeISO
  267. // event has a handler assigned...
  268. SendMsg(AMsg);
  269. SendCmd('.', DATA_PERIOD_ACCEPT); {Do not Localize}
  270. except
  271. on E: EIdSMTPReplyError do begin
  272. SendCmd(RSET_CMD);
  273. raise;
  274. end;
  275. on E: Exception do begin
  276. // the state of the communication is indeterminate at this point, so the
  277. // only sane thing to do is just close the socket...
  278. Disconnect(False);
  279. raise;
  280. end;
  281. end;
  282. end;
  283. procedure TIdSMTPBase.SendPipelining(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList);
  284. var
  285. LError : TIdReply;
  286. I, LFailedRecips : Integer;
  287. LCmd: string;
  288. LBufferingStarted: Boolean;
  289. function SetupErrorReply: TIdReply;
  290. begin
  291. Result := FReplyClass.Create(nil);
  292. Result.Assign(LastCmdResult);
  293. end;
  294. begin
  295. LError := nil;
  296. LCmd := MAILFROM_CMD + '<' + AFrom + '>'; {Do not Localize}
  297. if FUseVerp then begin
  298. if Capabilities.IndexOf(CAPAVERP) > -1 then begin
  299. LCmd := LCmd + ' VERP'; {Do not Localize}
  300. end else begin
  301. LCmd := LCmd + ' XVERP'; {Do not Localize}
  302. end;
  303. if FVerpDelims <> '' then begin
  304. LCmd := LCmd + '=' + FVerpDelims; {Do not Localize}
  305. end;
  306. end;
  307. try
  308. LBufferingStarted := not IOHandler.WriteBufferingActive;
  309. if LBufferingStarted then begin
  310. IOHandler.WriteBufferOpen;
  311. end;
  312. // RLebeau 4/29/2013: DO NOT send a RSET command before the MAIL FROM command!
  313. // Some servers are buggy and will reset the entire session, including any
  314. // previously accepted authentication, when they are supposed to reset only
  315. // their mail sending buffers and nothing else. Send a RSET only if the mail
  316. // transaction fails and needs to be cleaned up...
  317. // TODO: make this configurable?
  318. try
  319. //IOHandler.WriteLn(RSET_CMD);
  320. IOHandler.WriteLn(LCmd);
  321. WriteRecipientsPipeLine(ARecipients);
  322. IOHandler.WriteLn(DATA_CMD);
  323. if LBufferingStarted then begin
  324. IOHandler.WriteBufferClose;
  325. end;
  326. except
  327. if LBufferingStarted then begin
  328. IOHandler.WriteBufferCancel;
  329. end;
  330. raise;
  331. end;
  332. {
  333. //RSET
  334. if PosInSmallIntArray(GetResponse, RSET_ACCEPT) = -1 then begin
  335. LError := SetupErrorReply;
  336. end;
  337. }
  338. //MAIL FROM:
  339. if PosInSmallIntArray(GetResponse, MAILFROM_ACCEPT) = -1 then begin
  340. if not Assigned(LError) then begin
  341. LError := SetupErrorReply;
  342. end;
  343. end;
  344. //RCPT TO:
  345. if ARecipients.Count > 0 then begin
  346. LFailedRecips := 0;
  347. for I := 0 to ARecipients.Count - 1 do begin
  348. if PosInSmallIntArray(GetResponse, RCPTTO_ACCEPT) = -1 then begin
  349. Inc(LFailedRecips);
  350. if not FailedRecipientCanContinue(ARecipients[I].Address) then begin
  351. if not Assigned(LError) then begin
  352. LError := SetupErrorReply;
  353. end;
  354. end;
  355. end;
  356. end;
  357. if not Assigned(LError) and (LFailedRecips = ARecipients.Count) then begin
  358. LError := SetupErrorReply;
  359. end;
  360. end;
  361. //DATA - last in the batch
  362. if PosInSmallIntArray(GetResponse, DATA_ACCEPT) <> -1 then begin
  363. // TODO: if the server supports the UTF8SMTP extension, force TIdMessage
  364. // to encode headers as raw 8bit UTF-8, even if the TIdMessage.OnInitializeISO
  365. // event has a handler assigned...
  366. try
  367. SendMsg(AMsg);
  368. except
  369. // the state of the communication is indeterminate at this point, so the
  370. // only sane thing to do is just close the socket...
  371. Disconnect(False);
  372. raise;
  373. end;
  374. if PosInSmallIntArray(SendCmd('.'), DATA_PERIOD_ACCEPT) = -1 then begin {Do not Localize}
  375. if not Assigned(LError) then begin
  376. LError := SetupErrorReply;
  377. end;
  378. end;
  379. end else begin
  380. if not Assigned(LError) then begin
  381. LError := SetupErrorReply;
  382. end;
  383. end;
  384. if Assigned(LError) then begin
  385. SendCmd(RSET_CMD);
  386. LError.RaiseReplyError;
  387. end;
  388. finally
  389. LError.Free;
  390. end;
  391. end;
  392. procedure TIdSMTPBase.StartTLS;
  393. var
  394. LIO : TIdSSLIOHandlerSocketBase;
  395. LSendQuitOnError: Boolean;
  396. begin
  397. LSendQuitOnError := True;
  398. try
  399. if (IOHandler is TIdSSLIOHandlerSocketBase) and (FUseTLS <> utNoTLSSupport) then
  400. begin
  401. LIO := TIdSSLIOHandlerSocketBase(IOHandler);
  402. //we check passthrough because we can either be using TLS currently with
  403. //implicit TLS support or because STARTLS was issued previously.
  404. if LIO.PassThrough then
  405. begin
  406. if SupportsTLS then
  407. begin
  408. if SendCmd('STARTTLS') = 220 then begin {do not localize}
  409. LSendQuitOnError := False;
  410. TLSHandshake;
  411. LSendQuitOnError := True;
  412. //send EHLO
  413. SendGreeting;
  414. end else begin
  415. ProcessTLSNegCmdFailed;
  416. end;
  417. end else begin
  418. ProcessTLSNotAvail;
  419. end;
  420. end;
  421. end;
  422. except
  423. Disconnect(LSendQuitOnError); // RLebeau: do not send the QUIT command during the TLS handshake
  424. Raise;
  425. end;
  426. end;
  427. function TIdSMTPBase.FailedRecipientCanContinue(const AAddress: string): Boolean;
  428. begin
  429. Result := Assigned(FOnFailedRecipient);
  430. if Result then begin
  431. FOnFailedRecipient(Self, AAddress, LastCmdResult.Code, LastCmdResult.Text.Text, Result);
  432. end;
  433. end;
  434. function TIdSMTPBase.WriteRecipientNoPipelining(const AEmailAddress: TIdEmailAddressItem): Boolean;
  435. var
  436. LReply: Int16;
  437. begin
  438. LReply := SendCmd(RCPTTO_CMD + '<' + AEMailAddress.Address + '>'); {do not localize}
  439. Result := PosInSmallIntArray(LReply, RCPTTO_ACCEPT) <> -1;
  440. end;
  441. procedure TIdSMTPBase.WriteRecipientPipeLine(const AEmailAddress: TIdEmailAddressItem);
  442. begin
  443. //we'll read the reply - LATER
  444. IOHandler.WriteLn(RCPTTO_CMD + '<' + AEMailAddress.Address + '>');
  445. end;
  446. procedure TIdSMTPBase.WriteRecipientsNoPipelining(AList: TIdEmailAddressList);
  447. var
  448. I, LFailedRecips: Integer;
  449. LContinue: Boolean;
  450. begin
  451. if AList.Count > 0 then begin
  452. LFailedRecips := 0;
  453. LContinue := True;
  454. for I := 0 to AList.Count - 1 do begin
  455. if not WriteRecipientNoPipelining(AList[I]) then begin
  456. Inc(LFailedRecips);
  457. if not FailedRecipientCanContinue(AList[I].Address) then begin
  458. LContinue := False;
  459. Break;
  460. end;
  461. end;
  462. end;
  463. if (not LContinue) or (LFailedRecips = AList.Count) then begin
  464. LastCmdResult.RaiseReplyError;
  465. end;
  466. end;
  467. end;
  468. procedure TIdSMTPBase.WriteRecipientsPipeLine(AList: TIdEmailAddressList);
  469. var
  470. I: integer;
  471. begin
  472. for I := 0 to AList.Count - 1 do begin
  473. WriteRecipientPipeLine(AList[I]);
  474. end;
  475. end;
  476. procedure TIdSMTPBase.InternalSend(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList);
  477. begin
  478. if Pipeline and (Capabilities.IndexOf(CAPAPIPELINE) > -1) then begin
  479. SendPipelining(AMsg, AFrom, ARecipients);
  480. end else begin
  481. SendNoPipelining(AMsg, AFrom, ARecipients);
  482. end;
  483. end;
  484. // this version of Send() uses the TIdMessage to determine both the
  485. // sender and the recipients...
  486. procedure TIdSMTPBase.Send(AMsg: TIdMessage);
  487. var
  488. LRecipients: TIdEMailAddressList;
  489. begin
  490. LRecipients := TIdEMailAddressList.Create(Self);
  491. try
  492. LRecipients.AddItems(AMsg.Recipients);
  493. LRecipients.AddItems(AMsg.CCList);
  494. LRecipients.AddItems(AMsg.BccList);
  495. Send(AMsg, LRecipients);
  496. finally
  497. LRecipients.Free;
  498. end;
  499. end;
  500. // this version of Send() uses the TIdMessage to determine the
  501. // sender, but sends to the caller's specified recipients
  502. procedure TIdSMTPBase.Send(AMsg: TIdMessage; ARecipients: TIdEMailAddressList);
  503. var
  504. LSender: string;
  505. begin
  506. LSender := Trim(AMsg.Sender.Address);
  507. if LSender = '' then begin
  508. LSender := Trim(AMsg.From.Address);
  509. end;
  510. InternalSend(AMsg, LSender, ARecipients);
  511. end;
  512. // this version of Send() uses the TIdMessage to determine the
  513. // recipients, but sends using the caller's specified sender.
  514. // The sender can be empty, which is useful for server-generated
  515. // error messages...
  516. procedure TIdSMTPBase.Send(AMsg: TIdMessage; const AFrom: string);
  517. var
  518. LRecipients: TIdEMailAddressList;
  519. begin
  520. LRecipients := TIdEMailAddressList.Create(Self);
  521. try
  522. LRecipients.AddItems(AMsg.Recipients);
  523. LRecipients.AddItems(AMsg.CCList);
  524. LRecipients.AddItems(AMsg.BccList);
  525. Send(AMsg, LRecipients, AFrom);
  526. finally
  527. LRecipients.Free;
  528. end;
  529. end;
  530. // this version of Send() uses the caller's specified sender and
  531. // recipients. The sender can be empty, which is useful for
  532. // server-generated error messages...
  533. procedure TIdSMTPBase.Send(AMsg: TIdMessage; ARecipients: TIdEMailAddressList;
  534. const AFrom: string);
  535. begin
  536. InternalSend(AMsg, AFrom, ARecipients);
  537. end;
  538. end.