lonelydev101
lonelydev101

Reputation: 1901

How to send email properly - asynchronously? Mail never arrives

I created a new MVC5 project in C#.

I have some model:

public class EmailFormModel
{
    [Required, Display(Name = "Your name")]
    public string FromName { get; set; }
    [Required, Display(Name = "Your email"), EmailAddress]
    public string FromEmail { get; set; }
    [Required]
    public string Message { get; set; }
}

and I have a Conctact.cshtml:

@model EMailSenderWebApp.Models.EmailFormModel
@{
    ViewBag.Title = "Contact";
}
<h2>@ViewBag.Title.</h2>

@using (Html.BeginForm("SendEmailAsync", "Home", FormMethod.Post, new { role = "form contact-form", @id = "form-div" }))
{
    @Html.AntiForgeryToken()
    <h4>Send your comments.</h4>
    <hr />
    <div class="form-group">
        @Html.LabelFor(m => m.FromName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.FromName, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.FromName)
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.FromEmail, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.FromEmail, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.FromEmail)
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Message, new { @class = "col-md-2 control-label"  })
        <div class="col-md-10">
            @Html.TextAreaFor(m => m.Message, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.Message)
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Send" />
        </div>
    </div>
}

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

So, as you can see, my form calls HttpPost method "SendEmailAsync" which is described in my Home controller:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SendEmailAsync(EmailFormModel model)
{
    if (ModelState.IsValid)
    {
        SmtpClient client = new SmtpClient();

        MailMessage message = new MailMessage();
        var body = "<p>Email From: {0} ({1})</p><p>Message:</p><p>{2}</p>";
        message.To.Add(new MailAddress("[email protected]"));  // replace with valid value 
        message.From = new MailAddress("[email protected]");  // replace with valid value
        message.Subject = "Your email subject";
        message.Body = string.Format(body, model.FromName, model.FromEmail, model.Message);
        message.IsBodyHtml = true;

        client.Credentials = new NetworkCredential("", "");
        client.Host = "smtp.gmail.com";
        client.Port = 587;
        client.EnableSsl = false;

        client.SendCompleted += (s, e) =>
        {
            client.Dispose();
            message.Dispose();

        };
        ThreadPool.QueueUserWorkItem(o =>
            client.SendAsync(message, Tuple.Create(client, message)));
    }
    return View(model);
}

I've tried with some example as

await client.SendMailSync() but as you probably know, async call gone and never returns (some more info about that here)

So, I've implemented like this, guided with this stack-overflow article

This executes all the way the method (returns the View at the end) but my email never gets into my inbox?

Maybe google has some firewall?

Upvotes: 1

Views: 408

Answers (1)

Neil
Neil

Reputation: 1633

What's happening here is you are never connecting your smtp client to the server. Normally this would throw an exception, however you are also attempting to send your mail message inside a thread pool worker without proper try/catch. There is no code anywhere to catch your exception, so it seems like nothing is happening.

Also see my edits below to put SmtpClient and MailMessage inside using blocks so you don't have to manually call .Dispose(). This ensures resources are disposed even if an exception occurs while connecting or sending your message.

As others have mentioned the built in System.Net.Mail library isn't maintained any more. I recommend using MailKit (nuget link) as it is extremely well maintained and the owner is very responsive.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SendEmailAsync(EmailFormModel model)
{
    if (ModelState.IsValid)
    {
        // Create IDisposable inside `using` block so you aren't on the hook for calling Dispose()
        // The same has been done with your MailMessage
        using(SmtpClient client = new SmtpClient())
        {
            client.Credentials = new NetworkCredential("", "");
            client.Host = "smtp.gmail.com";
            client.Port = 587;
            client.EnableSsl = false;

            // You were missing this before which
            // was causing the exception. But due
            // to the thread your email was sent from
            // the exception was not thrown from a context
            // where you could know about it at all.
            await client.ConnectAsync();

            using(MailMessage message = new MailMessage())
            {
                var body = "<p>Email From: {0} ({1})</p><p>Message:</p><p>{2}</p>";
                message.To.Add(new MailAddress("[email protected]"));  // replace with valid value 
                message.From = new MailAddress("[email protected]");  // replace with valid value
                message.Subject = "Your email subject";
                message.Body = string.Format(body, model.FromName, model.FromEmail, model.Message);
                message.IsBodyHtml = true;

                await client.SendAsync(message);
            }
        }
    }

    return View(model);
}

Upvotes: 2

Related Questions