2

My project is hosted on Azure, and I would like to send an email every morning to users who have forgotten to complete certain tasks in my application.

I have my email built (sending using Postal). If I run the function itself, the emails send as expected.

I have configured the Azure scheduler to run an HTTPs action, get method, [https://www.example.com/Email/EmailReminder]. The scheduled job reporting as successful, but no emails are going out.

I haven't had to do this before, so I suspect I have a missing link between my function > scheduler job. I have searched for code samples on how to set this up, but I haven't found a solution yet. What is the scheduler expecting that I am not giving it?

public void EmailReminder()
{
     var remCheckOuts = // query code here                                
     into grouped
     select new Reminder
     {
          /// populate viewmodel 
     });    

    // send emails
    foreach (var i in remCheckOuts)
    {
                string Full = i.Full;
                string FirstName = i.FirstName;
                var CheckOutCt = i.CheckOutCt;                   

                dynamic email = new Email("emReminder");
                email.FromAdd = "test@test.com";
                email.To = "test2@test2.com";
                email.NPFirstName = NPFirstName;
                email.CheckOutCt = CheckOutCt;
                email.Send();
      } 
}
Daniela
  • 657
  • 1
  • 10
  • 25
  • Have you properly configured the outbound SMTP port in your web.config [as described here](http://aboutcode.net/postal/smtp-config.html)? Hint - Azure doesn't provide a pure SMTP server for you - you'll need a third-party like [SendGrid](https://azure.microsoft.com/en-us/documentation/articles/sendgrid-dotnet-how-to-send-email/), or you can spin up a VM if you want to roll your own. – Matt Johnson-Pint Jan 19 '16 at 19:28
  • Yes - The emails send correctly if just access run the function above. However, the scheduler that I set up to access this page runs successfully, but nothing sends. I know I am missing something in my code that the scheduler is looking for. If it is creating a cron job from the above, I don't know how to do that.... – Daniela Jan 19 '16 at 19:38
  • 1
    How are you determining that the email did not go out? You said the job shows that it ran in the job history, and when you hit the link manually the email goes out. Perhaps there's an authentication issue? If so, perhaps [these docs](https://azure.microsoft.com/en-us/documentation/articles/scheduler-outbound-authentication/) will help. – Matt Johnson-Pint Jan 19 '16 at 19:43
  • I have myself setup as the recipient for testing, and the Azure site is showing the job running successfully. I will look into this authentication issue, perhaps that is the problem. For testing, I turned off any authentication requirements on my end, but perhaps I am missing something! – Daniela Jan 19 '16 at 19:52
  • Can you clarify - If I use the Azure Scheduler method, do I need to set up a web job as well? – Daniela Jan 19 '16 at 19:58
  • The web job approach Matias describes is an *alternative* to using Scheduler. You don't need to do them both. I'll not try to persuade you one way or the other (as that requires deeper analysis of your overall solution), but either one should be able to get this task done. One differentiation (in favor of the web job) is *quantity* of emails to send, as you may hit the timeout Matias describes. – Matt Johnson-Pint Jan 19 '16 at 20:27
  • 1
    I was having an an authentication issue. My original script is sending now. Thank you. – Daniela Jan 19 '16 at 22:01

2 Answers2

7

I think your best bet is a Webjob. I assume you already have a Web App, so if you add a Webjob that uses the Webjob SDK, you can create a function with the signature:

public class Functions
{
    public static void ProcessTimer([TimerTrigger("0 0 9 1/1 * ? *", RunOnStartup = true)]
    TimerInfo info)
    {
        var remCheckOuts = // query code here                                
     into grouped
     select new Reminder
     {
          /// populate viewmodel 
     });    

    // send emails
    foreach (var i in remCheckOuts)
    {
                string Full = i.Full;
                string FirstName = i.FirstName;
                var CheckOutCt = i.CheckOutCt;                   

                dynamic email = new Email("emReminder");
                email.FromAdd = "test@test.com";
                email.To = "test2@test2.com";
                email.NPFirstName = NPFirstName;
                email.CheckOutCt = CheckOutCt;
                email.Send();
      } 
    }
}

It uses the TimerTrigger to fire at a given time (defined by the CRON expression), it's much simpler than the HTTP POST approach (in which you would need to take the HTTP Timeout into consideration).

If you have trouble with CRON expressions, check CronMaker.

For email sending and following the WebJobs SDK samples, you could use the SendGrid extension paired with a Queue for decoupling, this way you can have multiple TimerTrigger functions (for example, Morning mails, Evening Mails for X purpose, Night emails for Y purpose, Monthly reports) and one function that sends all the mails:

public class MailNotification{
    public string From {get;set;}
    public string To {get;set;}
    public string Subject {get;set;}
    public string Body {get;set;}
}

public class Functions
{
    public static void MorningMail([TimerTrigger("0 0 9 1/1 * ? *", RunOnStartup = true)]
    TimerInfo info, [Queue]("mail") ICollector<MailNotification> mails)
    {
        var remCheckOuts = // query code here                                
     into grouped
     select new Reminder
     {
          /// populate viewmodel 
     });    

    // send emails
    foreach (var i in remCheckOuts)
    {
            mails.Add(new MailNotification(){
                To = "test2@test2.com",
                From = "test@test.com",
                Subject = "Whatever Subject you want",
                Body = "construct the body here"
            }); 

      } 
    }

    public static void EveningMail([TimerTrigger("0 0 18 1/1 * ? *", RunOnStartup = true)]
    TimerInfo info, [Queue]("mail") ICollector<MailNotification> mails)
    {
        var remCheckOuts = // query code here                                
     into grouped
     select new Reminder
     {
          /// populate viewmodel 
     });    

    // send emails
    foreach (var i in remCheckOuts)
    {
            mails.Add(new MailNotification(){
                To = "test2@test2.com",
                From = "test@test.com",
                Subject = "Whatever Subject you want",
                Body = "construct the body here"
            }); 

      } 
    }

    public static void SendMails([QueueTrigger(@"mails")] MailNotification order,
        [SendGrid(
            To = "{To}",
            From = "{From}",
            Subject = "{Subject}",
            Text = "{Body}")]
        SendGridMessage message)
    {
        ;
    }
}
Matias Quaranta
  • 13,907
  • 1
  • 22
  • 47
  • The downside with this approach is that the web job has to be "always on", whereas Scheduler can run the job only at the scheduled time. One should do the cost-benefit analysis before choosing. See also: https://azure.microsoft.com/en-us/documentation/articles/web-sites-create-web-jobs/#CreateScheduledCRON – Matt Johnson-Pint Jan 19 '16 at 19:24
  • 1
    It's true, if the Web App is already Always On it has no additional costs. The Scheduler has the technical problem of the HTTP Timeout handling though, it will try to rerun the Job unless you change the Retrying strategy which is no simple feat and can only be achieved by the Management API. – Matias Quaranta Jan 19 '16 at 19:29
  • Interesting. Any link with more details? Also I think the OP's question isn't so much about scheduler as it is about the email library being used. – Matt Johnson-Pint Jan 19 '16 at 19:32
  • I have a sense that I will be adding a few of these email reminders to my application. I want something that will run reliably. Am I correct in understanding that I would need to convert my email function to a CRON job? How do I convert my function above into a CRON job or file uploadable to the scheduler? This is new to me. – Daniela Jan 19 '16 at 19:33
  • I just tried the above code to run at 1145 PST and nothing went out. Is there something else I need to add to my project to trigger this? – Daniela Jan 19 '16 at 19:47
  • The sample code I showed is to be run in a [WebJob](http://www.hanselman.com/blog/IntroducingWindowsAzureWebJobs.aspx). A WebJob is like a background worker that "lives" along your Web App, it's basically a Console Application. You can deploy it uploading the .Exe file that you get by compiling the Console Application or through Continous Integration from the repository. Here is a full [Quick Start with Webjobs](https://azure.microsoft.com/en-us/documentation/articles/websites-dotnet-webjobs-sdk-get-started/). – Matias Quaranta Jan 19 '16 at 19:51
  • I am confused. Do I need to put this webjob in a separate project? I had seen this article, but it seems like the storage account, etc. are all more than I need to just send this email? – Daniela Jan 19 '16 at 19:57
  • 1
    As Matt suggested, Azure does not send mails. You need an external SMTP Service (Sendgrid, MailGun, AmazonSES). You have 2 possible options to schedule work: 1-Keep using the Azure Scheduler and sending mails using the external SMTP, but you have to keep in mind that the Scheduler will wait for 30 seconds, if you take longer than 30 seconds to do all your sending, the Scheduler will fail and try to run again the action, which might be not good. 2-Use Webjobs, it might look more daunting, but this is the type of scenarios for which they were made for. – Matias Quaranta Jan 19 '16 at 20:02
  • I did discover that I was having an authentication issue on my script above. But I think that your script will be my best solution in the long run. Points to both!?! :) – Daniela Jan 19 '16 at 22:02
  • @Daniela - Matias certainly earned the upvote points (thanks!), but if your authentication issue is easy to explain then please add it as an answer (and accept), so that others may benefit. – Matt Johnson-Pint Jan 19 '16 at 22:25
1

In regards to my question of why Azure Scheduler wasn't sending my emails, it was an issue with authentication that was solved in the Azure Portal.

Matias answer was also correct, and the direction I will move in in the future.

Daniela
  • 657
  • 1
  • 10
  • 25