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.
- 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.
- 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.
- 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.