Developer forum

Forum » Ecommerce - Standard features » Send email to anonymous users, 7 days after purchase

Send email to anonymous users, 7 days after purchase

Martin Ottesen
Martin Ottesen
Reply

Hi DW, 

Is there any way to send a delayed email to anonymous users after they have created an order? 

In this particular case, we would like to send an email asking the user to give us a review on pricerunner, using the email marketing module in DW.
And we would like to delay it by a week so that we are sure the customer has received their product.

But at the moment I can only set it up so that I get out users who have made orders.

Thanks
- Martin


Replies

 
Nicolai Pedersen Dynamicweb Employee
Nicolai Pedersen
Reply
This post has been marked as an answer

Hi Martin

You can setup email marketing to do that:

https://doc.dynamicweb.com/documentation-9/marketing/email-marketing/email-marketing#2251

By using a smart search for users as the recipient list, you can find users that bought things seven days ago:

https://doc.dynamicweb.com/documentation-9/users/user-management/smart-searches#2605

You can combine this with email flows to create a drip campaign:

https://doc.dynamicweb.com/documentation-9/marketing/email-marketing/flows

Votes for this answer: 1
 
Martin Ottesen
Martin Ottesen
Reply

Hi Nicolai, 

That is also what we've set up but as i can see it i can only get the customers who created an order to show in the smart search who actually have an account on the website and not anonymous users. 

This is my current smart search, which gets out 94 people, but all have an account, and all orders from non account customers does not show in my smart search.

 
Nicolai Pedersen Dynamicweb Employee
Nicolai Pedersen
Reply

Yeah, then you need a recipient provider that can look at orders.

Or make a setup like this:

  1. Create a group in user management for anonymous users in ecommerce
  2. Create a sceheduled task using SQL that will take all user info from orders and insert them into AccessUser table in the group from step 1
  3. Make logic to ensure users are updated depending on your logic if they place additional orders
  4. Use smart search to query that group

Alternative is to create a RecipientProvider - below our abandoned cart provider you can use as a starting point - it also takes a look at orders:

 

/// <summary>
/// Abandoned cart recipients provider
/// </summary>
[AddInName("Abandoned cart recipients")]
public class AbandonedCartRecipientProvider : EmailRecipientProvider, IDropDownOptions
{

    /// <summary>
    /// Gets or sets 'mark abandoned after' value
    /// </summary>
    /// <returns>Integer value of hours</returns>
    [AddInParameter("MarkAbandonedAfter"), AddInLabel("Mark abandoned after"), AddInParameterEditor(typeof(DropDownParameterEditor), "NewGUI=true;none=false;SortBy=Key")]
    public int MarkAbandonedAfter { get; set; } = 180; // Default 180 = 3 hours

    /// <summary>
    /// Gets or sets 'ignore carts older than' value
    /// </summary>
    /// <returns>Integer value of hours</returns>
    [AddInParameter("IgnoreAbandonedCartsOlderThan"), AddInLabel("Ignore carts older than"), AddInParameterEditor(typeof(DropDownParameterEditor), "NewGUI=true;none=false;SortBy=Key")]
    public int IgnoreAbandonedCartsOlderThan { get; set; } = 1440; // Default 1440 = 24 hours

    /// <summary>
    /// Gets or sets 'only abandoned carts from this shop' value
    /// </summary>
    /// <returns>String value</returns>
    [AddInParameter("AbandonedCartsShop"), AddInLabel("Only carts from this shop"), AddInParameterEditor(typeof(DropDownParameterEditor), "NewGUI=true;none=false;SortBy=Key")]
    public string AbandonedCartsShop { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets 'only abandoned carts in this language' value
    /// </summary>
    /// <returns>String value</returns>
    [AddInParameter("AbandonedCartsLanguage"), AddInLabel("Only carts in this language"), AddInParameterEditor(typeof(DropDownParameterEditor), "NewGUI=true;none=false;SortBy=Key")]
    public string AbandonedCartsLanguage { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets 'require customer was logged in' value
    /// </summary>
    /// <returns>True in case when customer was logged in</returns>
    [AddInParameter("RequireCustomerLoggedIn"), AddInLabel("Require customer was logged in"), AddInParameterEditor(typeof(YesNoParameterEditor), "")]
    public bool RequireCustomerLoggedIn { get; set; }

    /// <summary>
    /// Gets or sets 'require customer accepted sales terms' value
    /// </summary>
    /// <returns>Boolean value</returns>
    [AddInParameter("RequireCustomerAcceptedSalesTerms"), AddInLabel("Require customer accepted sales terms"), AddInParameterEditor(typeof(YesNoParameterEditor), "")]
    public bool RequireCustomerAcceptedSalesTerms { get; set; }

    /// <summary>
    /// Gets or sets 'require logged in or accepted sales terms' value
    /// </summary>
    /// <returns>Boolean value</returns>
    [AddInParameter("RequireCustomerLoggedInOrAcceptedTerms"), AddInLabel("Require logged in or accepted sales terms"), AddInParameterEditor(typeof(YesNoParameterEditor), "")]
    public bool RequireEitherCustomerLoggedInOrAcceptedTerms { get; set; }

    /// <summary>
    /// Get order recipients
    /// </summary>
    /// <returns>Recipient collection</returns>
    public override RecipientCollection GetRecipients()
    {
        RecipientCollection recipients = new RecipientCollection();

        CommandBuilder commandBuilder = new CommandBuilder();
        commandBuilder.Add("SELECT  OrderID, OrderCustomerEmail, OrderCustomerName, OrderCustomerAccessUserID");
        commandBuilder.Add("FROM EcomOrders");
        commandBuilder.Add("WHERE");
        commandBuilder.Add("      OrderComplete = 0");
        commandBuilder.Add("  AND OrderCart = 1");
        AddOrderModifiedWhereClause(commandBuilder, IgnoreAbandonedCartsOlderThan, MarkAbandonedAfter);
        commandBuilder.Add("  AND OrderCustomerEmail IS NOT NULL");
        commandBuilder.Add("  AND OrderCustomerEmail <> ''");
        commandBuilder.Add("  AND OrderCustomerEmail <> ''");
        //ensure that cart is not "lost"
        commandBuilder.Add("  AND (OrderCustomerAccessUserID IS NULL OR OrderCustomerAccessUserID < 1 OR OrderID = (SELECT TOP 1 AccessUserCartID FROM AccessUser WHERE AccessUserID = OrderCustomerAccessUserID)");
        //also check contexts
        commandBuilder.Add("  OR OrderID IN (SELECT OrderContextAccessUserOrderID FROM EcomOrderContextAccessUserRelation WHERE OrderContextAccessUserAccessUserID = OrderCustomerAccessUserID))");

        if (RequireEitherCustomerLoggedInOrAcceptedTerms || RequireCustomerLoggedIn || RequireCustomerAcceptedSalesTerms)
        {
            commandBuilder.Add("  AND (");
            if (RequireEitherCustomerLoggedInOrAcceptedTerms || RequireCustomerLoggedIn)
            {
                commandBuilder.Add("    OrderCustomerAccessUserID > 0");
            }
            if (RequireEitherCustomerLoggedInOrAcceptedTerms)
            {
                commandBuilder.Add("    OR");
            }
            else if (RequireCustomerLoggedIn && RequireCustomerAcceptedSalesTerms)
            {
                commandBuilder.Add("    AND");
            }
            if (RequireEitherCustomerLoggedInOrAcceptedTerms || RequireCustomerAcceptedSalesTerms)
            {
                commandBuilder.Add("    OrderCustomerAccepted = 1");
            }
            commandBuilder.Add("  )");
        }

        if (!string.IsNullOrWhiteSpace(AbandonedCartsShop))
        {
            commandBuilder.Add("  AND OrderShopID = '" + AbandonedCartsShop + "'");
        }
        if (!string.IsNullOrWhiteSpace(AbandonedCartsLanguage))
        {
            commandBuilder.Add("  AND OrderLanguageID = '" + AbandonedCartsLanguage + "'");
        }

        using (IDataReader dataReader = Database.CreateDataReader(commandBuilder))
        {
            while (dataReader.Read())
            {
                string orderId = Converter.ToString(dataReader["OrderID"]);
                string email = Converter.ToString(dataReader["OrderCustomerEmail"]);
                string name = Converter.ToString(dataReader["OrderCustomerName"]);
                int userId = Converter.ToInt32(dataReader["OrderCustomerAccessUserID"]);

                if (!StringHelper.IsValidEmailAddress(email))
                {
                    continue;
                }

                Recipient recipient = new Recipient();
                recipient.EmailAddress = email;
                recipient.Name = name;
                recipient.RecipientKey = orderId;

                if (userId > 0)
                {
                    User user = User.GetUserByID(userId);
                    AccessUserRecipientProvider.SetTags(recipient, user);
                }

                recipients.Add(recipient);
            }
        }

        return recipients;
    }

    /// <summary>
    /// Gets context of recipient content
    /// </summary>
    /// <param name="recipient">Recipient object instance</param>
    /// <returns>Page ciew context with order data</returns>
    public override PageViewContext GetRecipientContentContext(Recipient recipient)
    {
        PageViewContext pageViewContext = new PageViewContext();
        Order order = Ecommerce.Services.Orders.GetById(recipient.RecipientKey);

        if (order != null && !string.IsNullOrEmpty(order.Id))
        {
            pageViewContext.SetValue("OrderID", recipient.RecipientKey);
            pageViewContext.SetValue("Order", order);
        }

        return pageViewContext;
    }

    private static void AddOrderModifiedWhereClause(CommandBuilder builder, int lowerTimeBound, int upperTimeBound)
    {
        DateTime now = DateTime.Now;
        var lower = now.AddMinutes(-lowerTimeBound);
        var upper = now.AddMinutes(-upperTimeBound);

        if (lowerTimeBound > 0 && upperTimeBound > 0)
        {
            builder.Add(" AND OrderModified BETWEEN {0} AND {1}", lower, upper);
        }
        else if (lowerTimeBound > 0)
        {
            builder.Add(" AND OrderModified >= {0}", lower);
        }
        else if (upperTimeBound > 0)
        {
            builder.Add(" AND OrderModified <= {0}", upper);
        }
        else
        {
            builder.Add(" AND OrderModified <= {0}", DateTime.Now);
        }
    }

    /// <summary>
    /// Implements <see cref="M:Dynamicweb.Extensibility.Editors.IDropDownOptions.GetOptions">IDropDownOptions.GetOptions</see> method,
    /// and used for DropDownLists control
    /// </summary>
    /// <param name="name">Specified current control name</param>
    /// <returns>Hashtable with specified control options</returns>
    public Hashtable GetOptions(string name)
    {
        Hashtable result = new Hashtable();

        if (name == "MarkAbandonedAfter")
        {
            TimeSpan[] timePeriods = new TimeSpan[]
            {
                new TimeSpan(0, 1, 0),
                new TimeSpan(0, 30, 0),
                new TimeSpan(1, 0, 0),
                new TimeSpan(2, 0, 0),
                new TimeSpan(3, 0, 0),
                new TimeSpan(4, 0, 0),
                new TimeSpan(5, 0, 0),
                new TimeSpan(6, 0, 0),
                new TimeSpan(12, 0, 0),
                new TimeSpan(18, 0, 0),
                new TimeSpan(24, 0, 0),
                new TimeSpan(48, 0, 0),
                new TimeSpan(72, 0, 0),
                new TimeSpan(96, 0, 0),
                new TimeSpan(120, 0, 0),
                new TimeSpan(144, 0, 0),
                new TimeSpan(7, 0, 0, 0),
                new TimeSpan(14, 0, 0, 0, 0),
                new TimeSpan(30, 0, 0, 0, 0)
            };

            foreach (TimeSpan span in timePeriods)
            {
                result.Add(Convert.ToInt32(span.TotalMinutes), GetTimeSpanInText(span));
            }
        }

        else if (name == "IgnoreAbandonedCartsOlderThan")
        {
            result.Add(0, Translate.Translate("No limit"));
            for (var i = 1; i <= 48; i++)
            {
                var span = new TimeSpan(i, 0, 0);
                result.Add(Convert.ToInt32(span.TotalMinutes), GetTimeSpanInText(span));
            }

            TimeSpan[] additionalTimePeriods = new TimeSpan[]
            {
                    new TimeSpan(60, 0, 0),
                    new TimeSpan(72, 0, 0),
                    new TimeSpan(7, 0, 0, 0),
                    new TimeSpan(14, 0, 0, 0, 0),
                    new TimeSpan(30, 0, 0, 0, 0)
            };

            foreach (TimeSpan span in additionalTimePeriods)
            {
                result.Add(Convert.ToInt32(span.TotalMinutes), GetTimeSpanInText(span));
            }
        }

        else if (name == "AbandonedCartsShop")
        {
            result.Add("", Translate.Translate("All"));
            foreach (Shop shop in Ecommerce.Services.Shops.GetShops())
            {
                result.Add(shop.Id, shop.Name);
            }
        }

        else if (name == "AbandonedCartsLanguage")
        {
            result.Add("", Translate.Translate("All"));
            foreach (Language language in Ecommerce.Services.Languages.GetLanguages())
            {
                result.Add(language.LanguageId, language.Name);
            }
        }            

        return result;
    }

    private string GetTimeSpanInText(TimeSpan span)
    {
        string timeText = "";
        int timeValue = 0;

        if (span.TotalDays >= 30)
        {
            timeText = "month" + (span.TotalDays > 30 ? "s" : string.Empty);
            timeValue = Convert.ToInt32(span.TotalDays) / 30;
        }
        else if (span.TotalDays >= 7)
        {
            timeText = "week" + (span.TotalDays > 7 ? "s" : string.Empty);
            timeValue = Convert.ToInt32(span.TotalDays) / 7;
        }
        else if (span.TotalHours >= 1)
        {
            timeText = "hour" + (span.TotalHours > 1 ? "s" : string.Empty);
            timeValue = Convert.ToInt32(span.TotalHours);
        }
        else if (span.TotalMinutes >= 1)
        {
            timeText = "minute" + (span.TotalMinutes > 1 ? "s" : string.Empty);
            timeValue = Convert.ToInt32(span.TotalMinutes);
        }
        else
        {
            throw new ArgumentException("TimeSpan 'span' is outside the excepted range.");
        }

        return string.Format("{0} {1}", timeValue, Translate.Translate(timeText));
    }

}

 

You must be logged in to post in the forum