Sending E-Mail from TMS Web Core via XData/Indy

A question came up in the webinar today about sending email from TMS Web Core. I think this is one of those topcis that has many qutie different potential implementations depending on the rest of your infrastructure. If you're a big user of Amazon's services, then it is likely trivial to use those, for example. If you're looking for something quick and simple, there are examples of sending mail using a JS library directly within the TMS Web Core app. In this post I'll show an impementation where I send via XData through my own mail server using Indy.

Why XData? Well, there are some possible issues that may come up when sending directly from the client. Now these may not be relevant to some or may have been resolved by other means, but are considerations I'm concerned about.

  1. Outbound emails may be blocked by the client's network. ISP's where I live are notorious for blocking outbound mail to any server that is not their own.
  2. Outbound emails may be viewed by others on the client network. Depending on how you connect to the SMTP server, this activity may be tracked in varying levels of detail by firewalls or other tools on the client PC/network that can sometimes interfere with mail being sent reliably.
  3. Relying on the client network to do more than just 'client' work. Might be splitting hairs here, but the idea is that the client should just be communicating with the server, nothing more.

So on the XData server, I just define a service endpoint that is used for sending emails. It is configured under the same security umbrella (JWT, SSL, etc.) and as many unique details of the email can be passed as needed as part of the XData call. For example, for a "login notification" email, the server might not need any details. If the client is composing an email, maybe it needs all of the details. The XData service then might look like the following. This is all traditiional VCL-type stuff when using the Indy library.

The initial part is just validating that the client is authorized to send an email. Lots of ways to do this and lots of reasons to be particularly picky about it, so how you might do this is entirely up to you. Important that it isn't "wide open" though as that would be equivalent to an open relay..... very bad.

function TAuthService.SendEMail(Secret, Project, EMailAddress, EMailSubject, EMailMessage: String): String;
var
  SMTP1: TIdSMTP;
  Msg1: TIdMessage;
  Addr1: TIdEmailAddressItem;
  Html1: TIdMessageBuilderHtml;
begin
  // Make sure the secret is valid
  if not(ValidSecret(Secret)) then raise EXDataHttpUnauthorized.Create('Invalid secret');
  // Make sure the Project is valid
  if not(ValidProject(Project)) then raise EXDataHttpUnauthorized.Create('Invalid project');
  // If the message doesn't contain an email address that we're familiar with in some way, then it is denied.
  if ((ValidateEMail(Secret, Project, EMailAddress)) <> 'Valid E-Mail') then raise EXDataHttpUnauthorized.Create('Invalid Email Address');

  // Finally, let's send the email
  Msg1  := nil;
  Addr1 := nil;

  // Here we're using an authenticated connection to our own mail server.
  // The authentication is configured in such a way as to allow outbound emails to others
  // from this account only so as to not be an open relay.
  SMTP1 := TIdSMTP.Create(nil);
  SMTP1.Host     := MainForm.MailServerHost;
  SMTP1.Port     := MainForm.MailServerPort;
  SMTP1.Username := MainForm.MailServerUser;
  SMTP1.Password := MainForm.MailServerPass;

  try
    Html1 := TIdMessageBuilderHtml.Create;
    try
      Html1.Html.Add('<html>');
      Html1.Html.Add('<head>');
      Html1.Html.Add('</head>');
      Html1.Html.Add('<body>');
      Html1.Html.Add(EMailMessage);
      Html1.Html.Add('</body>');
      Html1.Html.Add('</html>');
      Html1.HtmlCharSet := 'utf-8';

      Msg1 := Html1.NewMessage(nil);
      Msg1.Subject := EMailSubject;
      // The 'from' in this case is fixed, but could pass as parameters as well if desired
      Msg1.From.Text := MainForm.AppEmailFrom;
      Msg1.From.Name := MainForm.AppEMailName;

      Addr1 := Msg1.Recipients.Add;
      Addr1.Address := EMailAddress;

      SMTP1.Connect;
      try
        SMTP1.Send(Msg1);
      finally
        SMTP1.Disconnect();
      end;
    finally
      Addr1.Free;  
      Msg1.Free;   
      Html1.Free;  
    end;
  except
    on E: Exception do
      begin
      end;
  end;
  SMTP1.Free; // Should have to free this!

  Result := 'E-Mail sent';
end;

Could probably do with a little more error checking on the SMTP end of things but the user part can be done on the client, just waiting for the "E-Mail sent" reply with a timeout or something.

3 Likes

Very helpful information, @AndrewSimard. I'm sure this is a very common scenario for many users. E-mail handling in general is a very complex mechanism and a nightmare to make it work properly.

Besides the communication itself, there are many other variables (outside of XData/Web Core scope), that developers might need to pay attention to, like rate limits imposed by e-mail servers, limits on the number of e-mails that could be sent, e-mail deliverability, and so on.

I use Indy and it works perfectly but I send emails in a thread with a delay between each email.

Yes indeed, so much to consider and so very many things that can get in the way. Here are a few more that are typically on the list of considerations.

  • Might want the sender or "from" address to be consistent and familiar so that it doesn't end up in users' spam folders.
  • Be sure the recipient addresses are ones that have been provided to you, presumably by the recipient. There might even be local laws that relate to sending unsolicited emails or the requirement to provide "unsubcribe" mechanisms that are as accessible as the original "subscribe" mechanism. One of my "checks" to send an email is that it is an email that is validated for this situation.
  • Might want to consider whether you'll be accepting replies to the email and whether there is a "reply to" value that can be set if appropriate in addition to the "from" address.
  • Whether the email should be formatted as HTML or Plain Text. My example is for an HTML email so you can incorporate themes and so on.
  • How to compose useful and well-themed emails is a book in itself I'm sure. Inline-CSS is the way to go here, but HTML in email is very different than HTML rendered in a modern browser.
  • Consider having emails delivered from a static IP address or domain so that a proper reverse DNS lookup on the mail server can be done. This is needed to help with various anti-spam-checking sites. Sending email from your home IP address (where I live) is likely not workable as it is almost certainly blocked in this fashion.
  • Attachments are entirely possible. I can post an extended example of that with Indy (basically the same as the original example with a few extra bits) if anyone is interested. But the files sent should probably be compressed (typically zip) unless it is just a PDF or common image file format that a browser can understand. Sending an EXE is a good way to get mail blocked and the sender black-listed :expressionless:
  • This all works pretty well at a small scale, but different issues come up as the number of emails grows, like the rate limiting problem. Less of an issue if you have your own SMTP server and send to many different email domains. More of an issue if you have to send a lot of emails to one domain that you don't control.

Despite all this, it is a super-useful feature to have and not all that hard to get up and running. I'd certainly be interested in hearing about what others have done in this situation. Sending SMS messages is a different task altogether but it would be interesting to hear what others have done there as well.

Also remember that if you want to send bcc's that TIdMessage.SaveToStream removes them.

1 Like

Still adding to the topic:

  • You don't necessarily need to choose between HTML and plain text. Actually, to reduce SPAM ratio, it's sometimes recommended that you provide both options in the same message, as a multipart content, so your HTML e-mail can also be read in clients not supporting HTML (or that have such option disabled).
  • For better deliverability, also proper setup SPF, DKIM and, at a more advanced level, enable and monitor DMARC report.
  • For SMS messages, Amazon SNS is a cheap and good candidate. You can also use it for push notifications.
1 Like

I would suggest push notifications as being a third option (Email, SMS, Push). Would defintiely be interested in options for that. Will check out Amazon SNS. Integrating with other projects like Slack also come to mind but just spewing out candidates for other threads (originally typed 'threats' which is perhaps more accurate!) at this point :grin:

1 Like