/// <summary>
        /// Gets the queued communications
        /// </summary>
        /// <param name="expirationDays">The expiration days.</param>
        /// <param name="delayMinutes">The delay minutes.</param>
        /// <param name="includeFuture">if set to <c>true</c> [include future].</param>
        /// <param name="includePending">if set to <c>true</c> [include pending].</param>
        /// <returns></returns>
        public IQueryable <Communication> GetQueued(int expirationDays, int delayMinutes, bool includeFuture, bool includePending)
        {
            var beginWindow = RockDateTime.Now.AddDays(0 - expirationDays);
            var endWindow   = RockDateTime.Now.AddMinutes(0 - delayMinutes);
            var nowDate     = RockDateTime.Now;

            var qryPendingRecipients = new CommunicationRecipientService((RockContext)Context)
                                       .Queryable()
                                       .Where(a => a.Status == CommunicationRecipientStatus.Pending);

            return(Queryable()
                   .Where(c =>
                          (c.Status == CommunicationStatus.Approved || (includePending && c.Status == CommunicationStatus.PendingApproval)) &&
                          qryPendingRecipients.Where(r => r.CommunicationId == c.Id).Any() &&
                          (
                              (!c.FutureSendDateTime.HasValue && c.CreatedDateTime.HasValue && c.CreatedDateTime.Value.CompareTo(beginWindow) >= 0 && c.CreatedDateTime.Value.CompareTo(endWindow) <= 0) ||
                              (c.FutureSendDateTime.HasValue && c.FutureSendDateTime.Value.CompareTo(beginWindow) >= 0 && (includeFuture || c.FutureSendDateTime.Value.CompareTo(nowDate) <= 0))
                          )));
        }
        /// <summary>
        /// Gets the communications and response recipients.
        /// </summary>
        /// <param name="relatedSmsFromDefinedValueId">The related SMS from defined value identifier.</param>
        /// <param name="startDateTime">The start date time.</param>
        /// <param name="showReadMessages">if set to <c>true</c> [show read messages].</param>
        /// <param name="maxCount">The maximum count.</param>
        /// <param name="personId">The person identifier.</param>
        /// <returns></returns>
        public List <CommunicationRecipientResponse> GetCommunicationResponseRecipients(int relatedSmsFromDefinedValueId, DateTime startDateTime, bool showReadMessages, int maxCount, int?personId)
        {
            var smsMediumEntityTypeId = EntityTypeCache.GetId(SystemGuid.EntityType.COMMUNICATION_MEDIUM_SMS).Value;

            IQueryable <CommunicationResponse> communicationResponseQuery = this.Queryable()
                                                                            .Where(r => r.RelatedMediumEntityTypeId == smsMediumEntityTypeId && r.RelatedSmsFromDefinedValueId == relatedSmsFromDefinedValueId && r.CreatedDateTime >= startDateTime && r.FromPersonAliasId.HasValue);

            if (!showReadMessages)
            {
                communicationResponseQuery = communicationResponseQuery.Where(r => r.IsRead == false);
            }

            var personAliasQuery = personId == null
                ? new PersonAliasService(this.Context as RockContext).Queryable()
                : new PersonAliasService(this.Context as RockContext).Queryable().Where(p => p.PersonId == personId);

            // do an explicit LINQ inner join on PersonAlias to avoid performance issue where it would do an outer join instead
            var communicationResponseJoinQuery =
                from cr in communicationResponseQuery
                join pa in personAliasQuery on cr.FromPersonAliasId equals pa.Id
                select new { cr, pa };

            IQueryable <CommunicationResponse> mostRecentCommunicationResponseQuery = communicationResponseJoinQuery
                                                                                      .GroupBy(r => r.pa.PersonId)
                                                                                      .Select(a => a.OrderByDescending(x => x.cr.CreatedDateTime).FirstOrDefault())
                                                                                      .OrderByDescending(a => a.cr.CreatedDateTime).Select(a => a.cr);

            IQueryable <CommunicationRecipient> communicationRecipientQuery = new CommunicationRecipientService(this.Context as RockContext).Queryable()
                                                                              .Where(r =>
                                                                                     r.MediumEntityTypeId == smsMediumEntityTypeId &&
                                                                                     r.Communication.SMSFromDefinedValueId == relatedSmsFromDefinedValueId &&
                                                                                     r.CreatedDateTime >= startDateTime &&
                                                                                     r.Status == CommunicationRecipientStatus.Delivered);

            // do an explicit LINQ inner join on PersonAlias to avoid performance issue where it would do an outer join instead
            var communicationRecipientJoinQuery =
                from cr in communicationRecipientQuery.Where(a => a.PersonAliasId.HasValue)
                join pa in personAliasQuery on cr.PersonAliasId.Value equals pa.Id
                select new
            {
                PersonId        = pa.PersonId,
                Person          = pa.Person,
                CreatedDateTime = cr.CreatedDateTime,
                cr.Communication,
                cr.CommunicationId,
                CommunicationSMSMessage = cr.Communication.SMSMessage,
                SentMessage             = cr.SentMessage,
                PersonAliasId           = cr.PersonAliasId
            };

            var mostRecentCommunicationRecipientQuery = communicationRecipientJoinQuery
                                                        .GroupBy(r => r.PersonId)
                                                        .Select(a =>
                                                                a.Select(s => new
            {
                s.Person,
                s.CreatedDateTime,
                s.CommunicationSMSMessage,
                s.CommunicationId,
                s.Communication,
                s.SentMessage,
                s.PersonAliasId
            }).OrderByDescending(s => s.CreatedDateTime).FirstOrDefault()
                                                                );

            var mostRecentCommunicationResponseList = mostRecentCommunicationResponseQuery.Include(a => a.FromPersonAlias.Person).AsNoTracking().Take(maxCount).ToList();

            List <CommunicationRecipientResponse> communicationRecipientResponseList = new List <CommunicationRecipientResponse>();

            foreach (var mostRecentCommunicationResponse in mostRecentCommunicationResponseList)
            {
                var communicationRecipientResponse = new CommunicationRecipientResponse
                {
                    CreatedDateTime         = mostRecentCommunicationResponse.CreatedDateTime,
                    PersonId                = mostRecentCommunicationResponse?.FromPersonAlias.PersonId,
                    RecordTypeValueId       = mostRecentCommunicationResponse?.FromPersonAlias.Person.RecordTypeValueId,
                    FullName                = mostRecentCommunicationResponse?.FromPersonAlias.Person.FullName,
                    IsRead                  = mostRecentCommunicationResponse.IsRead,
                    MessageKey              = mostRecentCommunicationResponse.MessageKey,
                    IsOutbound              = false,
                    RecipientPersonAliasId  = mostRecentCommunicationResponse.FromPersonAliasId,
                    SMSMessage              = mostRecentCommunicationResponse.Response,
                    CommunicationResponseId = mostRecentCommunicationResponse.Id
                };

                communicationRecipientResponseList.Add(communicationRecipientResponse);
            }

            var mostRecentCommunicationRecipientList = mostRecentCommunicationRecipientQuery.Take(maxCount).ToList();

            foreach (var mostRecentCommunicationRecipient in mostRecentCommunicationRecipientList)
            {
                var communicationRecipientResponse = new CommunicationRecipientResponse
                {
                    CreatedDateTime        = mostRecentCommunicationRecipient.CreatedDateTime,
                    PersonId               = mostRecentCommunicationRecipient.Person.Id,
                    RecordTypeValueId      = mostRecentCommunicationRecipient.Person.RecordTypeValueId,
                    FullName               = mostRecentCommunicationRecipient.Person.FullName,
                    IsOutbound             = true,
                    IsRead                 = true,
                    MessageKey             = null, // communication recipients just need to show their name, not their number
                    RecipientPersonAliasId = mostRecentCommunicationRecipient.PersonAliasId,
                    SMSMessage             = mostRecentCommunicationRecipient.SentMessage.IsNullOrWhiteSpace() ? mostRecentCommunicationRecipient.CommunicationSMSMessage : mostRecentCommunicationRecipient.SentMessage,
                    CommunicationId        = mostRecentCommunicationRecipient.CommunicationId
                };

                if (mostRecentCommunicationRecipient?.Person.IsNameless() == true)
                {
                    // if the person is nameless, we'll need to know their number since we don't know their name
                    communicationRecipientResponse.MessageKey = mostRecentCommunicationRecipient.Person.PhoneNumbers.FirstOrDefault()?.Number;
                }
                else
                {
                    // If the Person is not nameless, we just need to show their name, not their number
                    communicationRecipientResponse.MessageKey = null;
                }

                communicationRecipientResponseList.Add(communicationRecipientResponse);
            }

            // NOTE: We actually have up to twice the max count at this point, because we are combining results from
            // CommunicationRecipient and CommunicationResponse, and we took the maxCount of each of those.
            // Now, we see what that combination ends up looking like when we sort it by CreatedDateTime
            communicationRecipientResponseList = communicationRecipientResponseList
                                                 .GroupBy(r => r.PersonId)
                                                 .Select(a => a.OrderByDescending(x => x.CreatedDateTime).FirstOrDefault())
                                                 .OrderByDescending(a => a.CreatedDateTime).Take(maxCount).ToList();

            return(communicationRecipientResponseList);
        }
        private void BindRecipients()
        {
            if ( CommunicationId.HasValue )
            {
                var rockContext = new RockContext();
                var recipients = new CommunicationRecipientService( rockContext )
                    .Queryable( "PersonAlias.Person,Activities" )
                    .Where( r => r.CommunicationId == CommunicationId.Value );

                SetRecipients( pnlPending, aPending, lPending, gPending,
                    recipients.Where( r => r.Status == CommunicationRecipientStatus.Pending ) );
                SetRecipients( pnlDelivered, aDelivered, lDelivered, gDelivered,
                    recipients.Where( r => r.Status == CommunicationRecipientStatus.Delivered || r.Status == CommunicationRecipientStatus.Opened ) );
                SetRecipients( pnlFailed, aFailed, lFailed, gFailed,
                    recipients.Where( r => r.Status == CommunicationRecipientStatus.Failed ) );
                SetRecipients( pnlCancelled, aCancelled, lCancelled, gCancelled,
                    recipients.Where( r => r.Status == CommunicationRecipientStatus.Cancelled ) );

                if ( pnlOpened.Visible )
                {
                    SetRecipients( pnlOpened, aOpened, lOpened, gOpened,
                        recipients.Where( r => r.Status == CommunicationRecipientStatus.Opened ) );
                }
            }
        }
        /// <summary>
        /// Binds the grid.
        /// </summary>
        private void BindGrid()
        {
            var rockContext = new RockContext();

            var communications = new CommunicationService( rockContext )
                    .Queryable( "ChannelEntityType,Sender,Reviewer" )
                    .Where( c => c.Status != CommunicationStatus.Transient );

            string subject = rFilter.GetUserPreference( "Subject" );
            if ( !string.IsNullOrWhiteSpace( subject ) )
            {
                communications = communications.Where( c => c.Subject.Contains( subject ) );
            }

            Guid entityTypeGuid = Guid.Empty;
            if ( Guid.TryParse( rFilter.GetUserPreference( "Channel" ), out entityTypeGuid ) )
            {
                communications = communications.Where( c => c.ChannelEntityType != null && c.ChannelEntityType.Guid.Equals( entityTypeGuid ) );
            }

            string status = rFilter.GetUserPreference( "Status" );
            if ( !string.IsNullOrWhiteSpace( status ) )
            {
                var communicationStatus = (CommunicationStatus)System.Enum.Parse( typeof( CommunicationStatus ), status );
                communications = communications.Where( c => c.Status == communicationStatus );
            }

            if ( canApprove )
            {
                int personId = 0;
                if ( int.TryParse( rFilter.GetUserPreference( "Created By" ), out personId ) && personId != 0 )
                {
                    communications = communications.Where( c => c.SenderPersonId.HasValue && c.SenderPersonId.Value == personId );
                }
            }
            else
            {
                communications = communications.Where( c => c.SenderPersonId.HasValue && c.SenderPersonId.Value == CurrentPersonId );
            }

            string content = rFilter.GetUserPreference( "Content" );
            if ( !string.IsNullOrWhiteSpace( content ) )
            {
                communications = communications.Where( c => c.ChannelDataJson.Contains( content ) );
            }

            var drp = new DateRangePicker();
            drp.DelimitedValues = rFilter.GetUserPreference( "Date Range" );
            if ( drp.LowerValue.HasValue )
            {
                communications = communications.Where( a => a.ReviewedDateTime >= drp.LowerValue.Value );
            }

            if ( drp.UpperValue.HasValue )
            {
                DateTime upperDate = drp.UpperValue.Value.Date.AddDays( 1 );
                communications = communications.Where( a => a.ReviewedDateTime < upperDate );
            }

            var recipients = new CommunicationRecipientService( rockContext ).Queryable();

            var queryable = communications
                .Select( c => new CommunicationItem
                {
                    Id = c.Id,
                    Communication = c,
                    Recipients = recipients
                        .Where( r => r.CommunicationId == c.Id)
                        .Count(),
                    PendingRecipients = recipients
                        .Where( r => r.CommunicationId == c.Id && r.Status == CommunicationRecipientStatus.Pending)
                        .Count(),
                    CancelledRecipients = recipients
                        .Where( r => r.CommunicationId == c.Id && r.Status == CommunicationRecipientStatus.Cancelled)
                        .Count(),
                    FailedRecipients = recipients
                        .Where( r => r.CommunicationId == c.Id && r.Status == CommunicationRecipientStatus.Failed)
                        .Count(),
                    DeliveredRecipients = recipients
                        .Where( r => r.CommunicationId == c.Id &&
                            (r.Status == CommunicationRecipientStatus.Delivered || r.Status == CommunicationRecipientStatus.Opened))
                        .Count(),
                    OpenedRecipients = recipients
                        .Where( r => r.CommunicationId == c.Id && r.Status == CommunicationRecipientStatus.Opened)
                        .Count()
                } );

            var sortProperty = gCommunication.SortProperty;
            if ( sortProperty != null )
            {
                queryable = queryable.Sort( sortProperty );
            }
            else
            {
                queryable = queryable.OrderByDescending( c => c.Communication.Id );
            }

            // Get the channel names
            var channels = new Dictionary<int, string>();
            foreach ( var item in Rock.Communication.ChannelContainer.Instance.Components.Values )
            {
                var entityType = item.Value.EntityType;
                channels.Add( entityType.Id, item.Metadata.ComponentName );
            }

            var communicationItems = queryable.ToList();
            foreach( var c in communicationItems)
            {
                c.ChannelName = channels.ContainsKey( c.Communication.ChannelEntityTypeId ?? 0 ) ?
                    channels[c.Communication.ChannelEntityTypeId ?? 0] :
                    c.Communication.ChannelEntityType.FriendlyName;
            }

            gCommunication.DataSource = communicationItems;
            gCommunication.DataBind();
        }
        /// <summary>
        /// Binds the grid.
        /// </summary>
        private void BindGrid()
        {
            var rockContext = new RockContext();

            var communications = new CommunicationService( rockContext )
                    .Queryable().AsNoTracking()
                    .Where( c => c.Status != CommunicationStatus.Transient );

            string subject = tbSubject.Text;
            if ( !string.IsNullOrWhiteSpace( subject ) )
            {
                communications = communications.Where( c => c.Subject.Contains( subject ) );
            }

            Guid? entityTypeGuid = cpMedium.SelectedValue.AsGuidOrNull();
            if ( entityTypeGuid.HasValue )
            {
                communications = communications.Where( c => c.MediumEntityType != null && c.MediumEntityType.Guid.Equals( entityTypeGuid.Value ) );
            }

            string status = ddlStatus.SelectedValue;
            if ( !string.IsNullOrWhiteSpace( status ) )
            {
                var communicationStatus = (CommunicationStatus)System.Enum.Parse( typeof( CommunicationStatus ), status );
                communications = communications.Where( c => c.Status == communicationStatus );
            }

            if ( canApprove )
            {
                if ( ppSender.PersonId.HasValue )
                {
                    communications = communications
                        .Where( c =>
                            c.SenderPersonAlias != null &&
                            c.SenderPersonAlias.PersonId == ppSender.PersonId.Value );
                }
            }
            else
            {
                // If can't approve, only show current person's communications
                communications = communications
                    .Where( c =>
                        c.SenderPersonAlias != null &&
                        c.SenderPersonAlias.PersonId == CurrentPersonId );
            }

            if ( drpDates.LowerValue.HasValue )
            {
                communications = communications.Where( a => a.CreatedDateTime >= drpDates.LowerValue.Value );
            }

            if ( drpDates.UpperValue.HasValue )
            {
                DateTime upperDate = drpDates.UpperValue.Value.Date.AddDays( 1 );
                communications = communications.Where( a => a.CreatedDateTime < upperDate );
            }

            string content = tbContent.Text;
            if ( !string.IsNullOrWhiteSpace( content ) )
            {
                communications = communications.Where( c => c.MediumDataJson.Contains( content ) );
            }

            var recipients = new CommunicationRecipientService( rockContext ).Queryable();

            var queryable = communications
                .Select( c => new CommunicationItem {
                    Id = c.Id,
                    MediumEntityTypeId = c.MediumEntityTypeId,
                    MediumName = c.MediumEntityTypeId.HasValue ? c.MediumEntityType.FriendlyName : null,
                    Subject = c.Subject,
                    CreatedDateTime = c.CreatedDateTime,
                    Sender = c.SenderPersonAlias != null ? c.SenderPersonAlias.Person : null,
                    ReviewedDateTime = c.ReviewedDateTime,
                    Reviewer = c.ReviewerPersonAlias != null ? c.ReviewerPersonAlias.Person : null,
                    Status = c.Status,
                    Recipients = recipients.Where( r => r.CommunicationId == c.Id ).Count(),
                    PendingRecipients = recipients.Where( r => r.CommunicationId == c.Id && r.Status == CommunicationRecipientStatus.Pending ).Count(),
                    CancelledRecipients = recipients.Where( r => r.CommunicationId == c.Id && r.Status == CommunicationRecipientStatus.Cancelled ).Count(),
                    FailedRecipients = recipients.Where( r => r.CommunicationId == c.Id && r.Status == CommunicationRecipientStatus.Failed ).Count(),
                    DeliveredRecipients = recipients.Where( r => r.CommunicationId == c.Id && ( r.Status == CommunicationRecipientStatus.Delivered || r.Status == CommunicationRecipientStatus.Opened ) ).Count(),
                    OpenedRecipients = recipients.Where( r => r.CommunicationId == c.Id && r.Status == CommunicationRecipientStatus.Opened ).Count()
                });

            var sortProperty = gCommunication.SortProperty;
            if ( sortProperty != null )
            {
                queryable = queryable.Sort( sortProperty );
            }
            else
            {
                queryable = queryable.OrderByDescending( c => c.CreatedDateTime );
            }

            gCommunication.EntityTypeId = EntityTypeCache.Read<Rock.Model.Communication>().Id;
            gCommunication.SetLinqDataSource( queryable );
            gCommunication.DataBind();
        }
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public virtual void Execute( IJobExecutionContext context )
        {
            JobDataMap dataMap = context.JobDetail.JobDataMap;
            var beginWindow = RockDateTime.Now.AddDays( 0 - dataMap.GetInt( "ExpirationPeriod" ) );
            var endWindow = RockDateTime.Now.AddMinutes( 0 - dataMap.GetInt( "DelayPeriod" ) );
            var nowDate = RockDateTime.Now;

            var rockContext = new RockContext();
            var qryPendingRecipients = new CommunicationRecipientService( rockContext ).Queryable().Where( a => a.Status == CommunicationRecipientStatus.Pending );

            var qry = new CommunicationService( rockContext ).Queryable()
                .Where( c =>
                    c.Status == CommunicationStatus.Approved &&
                    qryPendingRecipients.Where( r => r.CommunicationId == c.Id ).Any() &&
                    (
                        ( !c.FutureSendDateTime.HasValue && c.CreatedDateTime.HasValue && c.CreatedDateTime.Value.CompareTo( beginWindow ) >= 0 && c.CreatedDateTime.Value.CompareTo( endWindow ) <= 0 ) ||
                        ( c.FutureSendDateTime.HasValue && c.FutureSendDateTime.Value.CompareTo( beginWindow ) >= 0 && c.FutureSendDateTime.Value.CompareTo( nowDate ) <= 0 )
                    ) );

            var exceptionMsgs = new List<string>();
            int communicationsSent = 0;

            foreach ( var comm in qry.AsNoTracking().ToList() )
            {
                try
                {
                    var medium = comm.Medium;
                    if ( medium != null && medium.IsActive )
                    {
                        medium.Send( comm );
                        communicationsSent++;
                    }
                }

                catch ( Exception ex )
                {
                    exceptionMsgs.Add( string.Format( "Exception occurred sending communication ID:{0}:{1}    {2}", comm.Id, Environment.NewLine, ex.Messages().AsDelimited( Environment.NewLine + "   " ) ) );
                    ExceptionLogService.LogException( ex, System.Web.HttpContext.Current );
                }
            }

            if ( communicationsSent > 0 )
            {
                context.Result = string.Format( "Sent {0} {1}", communicationsSent, "communication".PluralizeIf( communicationsSent > 1 ) );
            }
            else
            {
                context.Result = "No communications to send";
            }

            if ( exceptionMsgs.Any() )
            {
                throw new Exception( "One or more exceptions occurred sending communications..." + Environment.NewLine + exceptionMsgs.AsDelimited( Environment.NewLine ) );
            }

            // check for communications that have not been sent but are past the expire date. Mark them as failed and set a warning.
            var qryExpired = new CommunicationService( rockContext ).Queryable()
                .Where( c =>
                    c.Status == CommunicationStatus.Approved &&
                    qryPendingRecipients.Where( r => r.CommunicationId == c.Id ).Any() &&
                    (
                        (!c.FutureSendDateTime.HasValue && c.CreatedDateTime.HasValue && c.CreatedDateTime.Value.CompareTo( beginWindow ) < 0 ) ||
                        (c.FutureSendDateTime.HasValue && c.FutureSendDateTime.Value.CompareTo( beginWindow ) < 0 )
                    ) );

            foreach ( var comm in qryExpired.ToList() )
            {
                foreach ( var recipient in comm.Recipients.Where( r => r.Status == CommunicationRecipientStatus.Pending ) )
                {
                    recipient.Status = CommunicationRecipientStatus.Failed;
                    recipient.StatusNote = "Communication was not sent before the expire window (possibly due to delayed approval).";
                    rockContext.SaveChanges();
                }
            }
        }
Exemple #7
0
        /// <summary>
        /// Sends the specified communication.
        /// </summary>
        /// <param name="communication">The communication.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        public override void Send( Rock.Model.Communication communication )
        {
            using ( var communicationRockContext = new RockContext() )
            {
                // Requery the Communication object in case we need to load any properties from the database
                communication = new CommunicationService( communicationRockContext )
                    .Queryable( "CreatedByPersonAlias.Person" )
                    .FirstOrDefault( c => c.Id == communication.Id );

                bool hasPendingRecipients;
                if ( communication != null
                    && communication.Status == Model.CommunicationStatus.Approved
                    && ( !communication.FutureSendDateTime.HasValue || communication.FutureSendDateTime.Value.CompareTo( RockDateTime.Now ) <= 0 ))
                {
                    var qryRecipients = new CommunicationRecipientService( communicationRockContext ).Queryable();
                    hasPendingRecipients = qryRecipients.Where( a => a.CommunicationId == communication.Id ).Where( r => r.Status == Model.CommunicationRecipientStatus.Pending ).Any();
                }
                else
                {
                    hasPendingRecipients = false;
                }

                if ( hasPendingRecipients )
                {
                    var currentPerson = communication.CreatedByPersonAlias.Person;
                    var globalAttributes = Rock.Web.Cache.GlobalAttributesCache.Read();
                    var mergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields( null, currentPerson );

                    // From - if none is set, use the one in the Organization's GlobalAttributes.
                    string fromAddress = communication.GetMediumDataValue( "FromAddress" );
                    if ( string.IsNullOrWhiteSpace( fromAddress ) )
                    {
                        fromAddress = globalAttributes.GetValue( "OrganizationEmail" );
                    }

                    string fromName = communication.GetMediumDataValue( "FromName" );
                    if ( string.IsNullOrWhiteSpace( fromName ) )
                    {
                        fromName = globalAttributes.GetValue( "OrganizationName" );
                    }

                    // Resolve any possible merge fields in the from address
                    fromAddress = fromAddress.ResolveMergeFields( mergeFields, currentPerson, communication.EnabledLavaCommands );
                    fromName = fromName.ResolveMergeFields( mergeFields, currentPerson, communication.EnabledLavaCommands );

                    MailMessage message = new MailMessage();
                    message.From = new MailAddress( fromAddress, fromName );

                    // Reply To
                    try
                    {
                        string replyTo = communication.GetMediumDataValue( "ReplyTo" );
                        if ( !string.IsNullOrWhiteSpace( replyTo ) )
                        {
                            message.ReplyToList.Add( new MailAddress( replyTo ) );
                        }
                    }
                    catch { }

                    CheckSafeSender( message, globalAttributes );

                    // CC
                    string cc = communication.GetMediumDataValue( "CC" );
                    if ( !string.IsNullOrWhiteSpace( cc ) )
                    {
                        foreach ( string ccRecipient in cc.SplitDelimitedValues() )
                        {
                            message.CC.Add( new MailAddress( ccRecipient ) );
                        }
                    }

                    // BCC
                    string bcc = communication.GetMediumDataValue( "BCC" );
                    if ( !string.IsNullOrWhiteSpace( bcc ) )
                    {
                        foreach ( string bccRecipient in bcc.SplitDelimitedValues() )
                        {
                            message.Bcc.Add( new MailAddress( bccRecipient ) );
                        }
                    }

                    message.IsBodyHtml = true;
                    message.Priority = MailPriority.Normal;

                    using ( var smtpClient = GetSmtpClient() )
                    {
                        // Add Attachments
                        var attachments = new List<BinaryFile>();
                        string attachmentIds = communication.GetMediumDataValue( "Attachments" );
                        if ( !string.IsNullOrWhiteSpace( attachmentIds ) )
                        {
                            var binaryFileService = new BinaryFileService( communicationRockContext );

                            foreach ( int binaryFileId in attachmentIds.SplitDelimitedValues().AsIntegerList() )
                            {
                                var binaryFile = binaryFileService.Get( binaryFileId );
                                if ( binaryFile != null )
                                {
                                    attachments.Add( binaryFile );
                                }
                            }
                        }

                        var personEntityTypeId = EntityTypeCache.Read( "Rock.Model.Person" ).Id;
                        var communicationEntityTypeId = EntityTypeCache.Read( "Rock.Model.Communication" ).Id;
                        var communicationCategoryId = CategoryCache.Read( Rock.SystemGuid.Category.HISTORY_PERSON_COMMUNICATIONS.AsGuid(), communicationRockContext ).Id;

                        bool recipientFound = true;
                        while ( recipientFound )
                        {
                            // make a new rockContext per recipient so that DbChangeTracker doesn't get gummed up on large communications
                            var recipientRockContext = new RockContext();
                            var recipient = Rock.Model.Communication.GetNextPending( communication.Id, recipientRockContext );
                            if ( recipient != null )
                            {
                                if ( string.IsNullOrWhiteSpace( recipient.PersonAlias.Person.Email ) )
                                {
                                    recipient.Status = CommunicationRecipientStatus.Failed;
                                    recipient.StatusNote = "No Email Address";
                                }
                                else
                                {
                                    try
                                    {
                                        message.To.Clear();
                                        message.Headers.Clear();
                                        message.AlternateViews.Clear();

                                        message.To.Add( new MailAddress( recipient.PersonAlias.Person.Email, recipient.PersonAlias.Person.FullName ) );

                                        // Create merge field dictionary
                                        var mergeObjects = recipient.CommunicationMergeValues( mergeFields );

                                        // Subject
                                        message.Subject = communication.Subject.ResolveMergeFields( mergeObjects, currentPerson, communication.EnabledLavaCommands );

                                        // convert any special microsoft word characters to normal chars so they don't look funny (for example "Hey “double-quotes” from ‘single quote’")
                                        message.Subject = message.Subject.ReplaceWordChars();

                                        // Add text view first as last view is usually treated as the preferred view by email readers (gmail)
                                        string plainTextBody = Rock.Communication.Medium.Email.ProcessTextBody( communication, globalAttributes, mergeObjects, currentPerson );

                                        // convert any special microsoft word characters to normal chars so they don't look funny
                                        plainTextBody = plainTextBody.ReplaceWordChars();

                                        if ( !string.IsNullOrWhiteSpace( plainTextBody ) )
                                        {
                                            AlternateView plainTextView = AlternateView.CreateAlternateViewFromString( plainTextBody, new System.Net.Mime.ContentType( MediaTypeNames.Text.Plain ) );
                                            message.AlternateViews.Add( plainTextView );
                                        }

                                        // Add Html view
                                        string htmlBody = Rock.Communication.Medium.Email.ProcessHtmlBody( communication, globalAttributes, mergeObjects, currentPerson );

                                        // convert any special microsoft word characters to normal chars so they don't look funny
                                        htmlBody = htmlBody.ReplaceWordChars();

                                        if ( !string.IsNullOrWhiteSpace( htmlBody ) )
                                        {
                                            AlternateView htmlView = AlternateView.CreateAlternateViewFromString( htmlBody, new System.Net.Mime.ContentType( MediaTypeNames.Text.Html ) );
                                            message.AlternateViews.Add( htmlView );
                                        }

                                        // Add any additional headers that specific SMTP provider needs
                                        var metaData = new Dictionary<string, string>();
                                        metaData.Add( "communication_recipient_guid", recipient.Guid.ToString() );
                                        AddAdditionalHeaders( message, metaData );

                                        // Recreate the attachments
                                        message.Attachments.Clear();
                                        if ( attachments.Any() )
                                        {
                                            foreach( var attachment in attachments )
                                            {
                                                message.Attachments.Add( new Attachment( attachment.ContentStream, attachment.FileName ) );
                                            }
                                        }

                                        smtpClient.Send( message );
                                        recipient.Status = CommunicationRecipientStatus.Delivered;

                                        string statusNote = StatusNote;
                                        if ( !string.IsNullOrWhiteSpace( statusNote ) )
                                        {
                                            recipient.StatusNote = statusNote;
                                        }

                                        recipient.TransportEntityTypeName = this.GetType().FullName;

                                        var historyService = new HistoryService( recipientRockContext );
                                        historyService.Add( new History
                                        {
                                            CreatedByPersonAliasId = communication.SenderPersonAliasId,
                                            EntityTypeId = personEntityTypeId,
                                            CategoryId = communicationCategoryId,
                                            EntityId = recipient.PersonAlias.PersonId,
                                            Summary = string.Format( "Sent communication from <span class='field-value'>{0}</span>.", message.From.DisplayName ),
                                            Caption = message.Subject,
                                            RelatedEntityTypeId = communicationEntityTypeId,
                                            RelatedEntityId = communication.Id
                                        } );
                                    }

                                    catch ( Exception ex )
                                    {
                                        recipient.Status = CommunicationRecipientStatus.Failed;
                                        recipient.StatusNote = "Exception: " + ex.Message;
                                    }
                                }

                                recipientRockContext.SaveChanges();
                            }
                            else
                            {
                                recipientFound = false;
                            }
                        }
                    }
                }
            }
        }