Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
TIdSMTPServer - Questions (differences between v9 and v10)
#11
(07-17-2024, 08:15 PM)Justin Case Wrote: Well after mulling this over, despite your advice to me it makes sense to actually call RCPTTo event again

Not really...

(07-17-2024, 08:15 PM)Justin Case Wrote: That event filters out the ok addresses.. but then in the MsgReceive event I still need to know if incoming mail is for a local account or external.

You do that by simply looking at the domain portion of each recipient. If the domain belongs to the local system, that recipient is a local account. Otherwise, relay the email to that domain's own SMTP server.

(07-17-2024, 08:15 PM)Justin Case Wrote: The LAction variable parameter of the call to the RCPTTo is rather useful here and despite essentially duplicating the previous behaviour that variable then makes it easier to determine what to do with the incoming mail

Then you should refactor your code so you can share logic in both events, eg:

Code:
function IsAddressInLocalDomain(const AAddress: String): Boolean;
begin
  Query1.Close;
  Query1.SQL.Text := SQL('CheckAddressDomainIsLocal');
  Query1.Prepare;
  Query1.ParamByName('address').AsString := Utils.Parse('@', AAddress, 1);
  Query1.ParamByName('domain').AsString := Utils.Parse('@', AAddress, 2);
  Query1.Open;
  try
    Query1.First;
    Result := not Query1.EOF;
  finally
    Query1.Close;
  end;
end;

procedure TDataModule1.IdSMTPServer1RcptTo(ASender: TIdSMTPServerContext;
  const AAddress: String; AParams: TStrings; var VAction: TIdRCPToReply;
  var VForward: String);
begin
  if IsAddressInLocalDomain(AAddress) then begin
    VAction := rAddressOk;
  end
  else if ASender.LoggedIn then begin
    VAction := rWillForward;
  end
  else begin
    VAction := rRelayDenied;
  end;
end;

procedure TDataModule1.IdSMTPServer1MsgReceive(
  ASender: TIdSMTPServerContext; AMsg: TStream;
  var VAction: TIdDataReply);
var
  I: Integer;
  ...
begin
  for I := 0 to ASender.RCPTList.Count -1 do
  begin
    if IsAddressInLocalDomain(ASender.RCPTList[I].Address) then
    begin
      {TODO : Save to database}
      ...
    end
    else
    begin
      {TODO : Send onwards elsewhere}
    end;
  end;
  VAction := dOk;
end;

That being said, you are only looking at the entire address as a whole. You should be looking just at the domain portion of the address to determine if it is targeting a local account or not. If the domain is local but the account is not found locally, your logic would try to relay that address, when it could just fail the address instead, eg:

Code:
function IsLocalDomain(const ADomain: String): Boolean;
begin
  Result := ADomain = 'my.domain'; // or whatever you need to do to check this...
end;

type
  TUserDomainResult = (UserInLocalDomain, UserNotInLocalDomain, ExternalUser);

function CheckUserInLocalDomain(const AUser, ADomain: String): TUserDomainResult;
begin
  if IsLocalDomain(ADomain) then
  begin
    Query1.Close;
    Query1.SQL.Text := SQL('CheckAddressDomainIsLocal');
    Query1.Prepare;
    Query1.ParamByName('address').AsString := AUser;
    Query1.ParamByName('domain').AsString := ADomain;
    Query1.Open;
    try
      Query1.First;
      if not Query1.EOF then
        Result := UserInLocalDomain
      else
        Result := UserNotInLocalDomain;
    finally
      Query1.Close;
    end;
  end
  else begin
    Result := ExternalUser;
  end;
end;

procedure TDataModule1.IdSMTPServer1RcptTo(ASender: TIdSMTPServerContext;
  const AAddress: String; AParams: TStrings; var VAction: TIdRCPToReply;
  var VForward: String);
var
  user, domain: string;
begin
  user := Utils.Parse('@', AAddress, 1);
  domain := Utils.Parse('@', AAddress, 2);
  case CheckUserInLocalDomain(user, domain) of
    UserInLocalDomain: begin
      VAction := rAddressOk;
    end;
    UserNotInLocalDomain: begin
      VAction := rInvalid; // or rRelayDenied or rNoForward
    end;
    ExternalUser: begin
      if ASender.LoggedIn then
        VAction := rWillForward
      else
        VAction := rRelayDenied;
    end;
  end;
end;

procedure TDataModule1.IdSMTPServer1MsgReceive(
  ASender: TIdSMTPServerContext; AMsg: TStream;
  var VAction: TIdDataReply);
var
  I: Integer;
  ...
begin
  for I := 0 to ASender.RCPTList.Count -1 do
  begin
    if IsLocalDomain(ASender.RCPTList[I].Domain) then
    begin
      {TODO : Save to database}
      ...
    end
    else
    begin
      {TODO : Send onwards elsewhere}
    end;
  end;
  VAction := dOk;
end;

Otherwise, you can have the OnRcptTo event accept all accounts in the local domain, even if they don't exist, and then have OnMsgReceive send a failure email back to the client if a local account is not found, eg:

Code:
procedure TDataModule1.IdSMTPServer1RcptTo(ASender: TIdSMTPServerContext;
  const AAddress: String; AParams: TStrings; var VAction: TIdRCPToReply;
  var VForward: String);
var
  domain: string;
begin
  domain := Utils.Parse('@', AAddress, 2);
  if IsLocalDomain(domain) then begin
    VAction := rAddressOk;
  end
  else if ASender.LoggedIn then begin
    VAction := rWillForward;
  end
  else begin
    VAction := rRelayDenied;
  end;
end;

procedure TDataModule1.IdSMTPServer1MsgReceive(
  ASender: TIdSMTPServerContext; AMsg: TStream;
  var VAction: TIdDataReply);
var
  I: Integer;
  ...
begin
  for I := 0 to ASender.RCPTList.Count -1 do
  begin
    if IsLocalDomain(ASender.RCPTList[I].Domain) then
    begin
      if IsUserInLocalDomain(ASender.RCPTList[I].Username, ASender.RCPTList[I].Domain) then
      begin
        {TODO : Save to database}
        ...
      end
      else
      begin
        {TODO : Send failure email to ASender.From if not blank }
      end;
    end
    else
    begin
      {TODO : Send onwards elsewhere}
    end;
  end;
  VAction := dOk;
end;

Now, THAT being said - another option would be to do the DB check in the OnRcptTo event and cache the results where the OnMsgReceive event can use them without having to hit the DB again. You could use the ASender.Data property for that. Or you could derive a new class from TIdSMTPServerContext to add caching logic, and then assign that class type to the TIdSMTPServer.ContextClass property.

Reply
#12
ASender.Data was going to be my next question!

So with what you said about the data and thread models being seperated, can I assume that ASender.Data is the equivalent of the old AThread.Data?

As for checking the domain, I thought best to check for the account and domain via the one query - that way if the mailbox isn't local my server can just reject it. I'm not too keen on the idea of wasting extra CPU cycles to generate a bounce email. However there will be some domains where I would like a catch-all ability so the code will still have to check a setting and adapt accordingly.

Remy, as always, thanks for your help.
Reply
#13
(07-18-2024, 08:47 PM)Justin Case Wrote: So with what you said about the data and thread models being seperated, can I assume that ASender.Data is the equivalent of the old AThread.Data?

Yes. TIdContext is basically what TIdPeerThread was before, except TIdContext is merely linked to the owning TThread (via the TIdContext.Yarn property) instead of being directly derived from it (if you need direct access to the TThread, there is a way to do it).

(07-18-2024, 08:47 PM)Justin Case Wrote: As for checking the domain, I thought best to check for the account and domain via the one query - that way if the mailbox isn't local my server can just reject it.

It is your server, your rules.

(07-18-2024, 08:47 PM)Justin Case Wrote: I'm not too keen on the idea of wasting extra CPU cycles to generate a bounce email.

Keep in mind that you do need to implement such emails in situations where delivery still fails after you already told the client you would deliver. Once you reach the OnMsgReceive event, the mail transaction is complete and you are solely responsible for the email, and must either deliver it or report failures to the client's mailbox (they can't be reported using the current SMTP connection at this point).

Reply
#14
(07-09-2024, 11:50 PM)rlebeau Wrote: TIdSMTPRelay is an SMTP client similar to TIdSMTP, except that instead of connecting to a single SMTP server, it can connect to several servers.

TIdSMTP connects to 1 specified server and sends 1 email at a time, specifying all of the recipients of the email to that server.  That server will receive the email and deliver it or relay it to other servers as needed.

TIdSMTPRelay, on the other hand, groups an email's recipients together by their domains, then does a DNS lookup on each domain to discover its registered SMTP server, and then connects to each server and delivers the email to only the recipients of that server.

Basically, TIdSMTP is what an end client would use, and TIdSMTPRelay is what a relaying server would use to forward emails to another server.  Of course, nothing stops an end user from trying to use TIdSMTPRelay directly to bypss their own ISP, but they will likely be blocked by the receiving servers since they are not themselves a validated relay server.

Can you give me any pointers (or even code) as to where I start with this component?
Reply
#15
(07-19-2024, 08:34 PM)Justin Case Wrote: Can you give me any pointers (or even code) as to where I start with this component?

If you mean TIdSMTPRelay, then you use it similarly to TIdSMTP, in that you create a TIdMessage and give it to the TIdSMTPRelay.Send() method.

However, it is different then TIdSMTP in that:
  • you don't call Connect() on it, as it manages its own connections internally.
  • you have to assign it a DNSServer so it can lookup the SMTP server of each recipient domain.
  • when sending the TIdMessage, you can pass in the ASender.RCPTList as a parameter to Send() so it knows who to send to.  It CAN use the TIdMessage to determine the recipients (To, CCList, and BCCList), but use the ASender.RCPTList when you have it handy.

Reply
#16
Ok, that's very useful, thanks.

What i have realised this evening is that my server seems to get blocked by a lot of the larger players (yahoo, gmail etc) and my other emails have been routed via my ISPs SMTP server so I think it's going to be best if i carry on doing that.

Going by what you've said above, that means that TIdSMTPRelay won't work for me and I'll be best sticking with TIdSMTP instead but I shall still use it (and your advice) so that the relay method can be enabled via settings.

Again, thanks for your time and dedication Remy!
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)