GithubHelp home page GithubHelp logo

fluent-crm's Introduction

FluentCRM

Email Marketing Automation for WordPress

FluentCRM is a Self Hosted Email Marketing Automation Plugin. Our goal is to let you manage your lead and customers and email campaigns, automation, track activity, and many more. We are currently on beta.

Features

Manage Email Campaigns

FluentCRM lets you compose, filter audiences, and send emails to thousands of subscribers. You can track the email open rate, click rate, and see which links are clicked.

360 Degree Contact View

FluentCRM will let you view the full overview of your contact. It will show your contact overview, Email history, Purchase History from EDD/WooCommerce, Form Submissions, Support Tickets. You can even add files, notes for references.

Contact Segmentation

FluentCRM will let you segment your contacts by lists, tags or you can dynamically segment your contacts based on different dynamic parameters.

Automatic Sequence Emails

Want to send sequence emails / Drip Emails to your customers? That’s easy with fluentCRM. You can trigger automatic sequence emails based on different events like Product Purchase, User Registration, Your LMS course purchase, etc. Run your whole business on auto-pilot with fluentCRM.

Automation Funnels

fluentCRM let you create unlimited automation Funnels and based on your different event you can run pre-defined actions. fluentCRM already has integrations with most of the popular WP Plugins. We are going to add lots of integration.

In Details Reporting

Generate and view your campaign reports, Find which emails or subjects performed better, and tune your marketing campaigns and increase your revenue.

More details about fluentCRM

Q: Are there any limitations about how many emails I can send? No, there are no limitations. You can send as many emails as you want and also no limitations on contact numbers.

Q: How emails are sent? You can use any SMTP service like MailGun, SendGrid, Amazon SES. We recommend using Amazon SES because it’s a reliable and cost-effective solution.

Q: Is it 100% self-hosted?* Yes, FluentCRM is 100% self-hosted. I will not connect with any of our SAAS servers. You own the data and your data should be hosted in your hosting server.

Q: Is it GDPR complaint?* Yes, your data never goes to a different server like MailChimp, ActiveCampaign, or 100s of other CRM. All the data will be saved and managed into WordPress.

Q: Will it be a performance issue for WordPress? Absolutely not! From the very first, We were careful about this. It stores all the Campaign and Contact data in custom database tables so it will not affect your WordPress database. We built the application with VueJS and it’s only run when you go to the admin dashboard of Fluent CRM. Also, The Admin UI is super fast as It’s a SPA and communicates over ajax.

Developer Documentation

Please Check the WIKI section of this repository

fluent-crm's People

Contributors

almaz avatar pullsharkbadge avatar techjewel avatar verygoodplugins avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

fluent-crm's Issues

[CRITICAL] Email success is messured fundamentaly wrong by using wp_mail incorrectly

Inside Handler.php you have the sendEmails() method.

This has the following main for each loop.

 foreach ($campaignEmails as $email) {
            if ($this->reachedEmailLimitPerSecond()) {
                $this->restartWhenOneSecondExceeds();
            } else {
                $response = Mailer::send($email->data());
                $this->dispatchedWithinOneSecond++;
                if (is_wp_error($response)) {
                    $failedIds[] = $email->id;
                } else {
                    CampaignEmail::where('id', $email->id)->whereNot('status', 'failed')->update([
                        'status'     => 'sent',
                        'updated_at' => current_time('mysql')
                    ]);
                    $sentIds[] = $email->id;
                }
            }
        }

Now lets look what Mailer::send() returns.

 public static function send($data)
    {
        $headers = static::buildHeaders($data);

        if (apply_filters('fluentcrm_is_simulated_mail', false, $data, $headers)) {
            return true;
        }

        return wp_mail(
            $data['to']['email'],
            $data['subject'],
            $data['body'],
            $headers
        );
    }

wp_mail() by contract RETURNS A BOOLEAN

That means that:

 $response = Mailer::send($email->data());
                $this->dispatchedWithinOneSecond++;
                if (is_wp_error($response)) {
                    $failedIds[] = $email->id;
                } 

WILL ALWAYS MARK THE EMAIL AS SUCCESSFULL

Its not possible that this function returns a WP_ERROR class. It returns booleans.

	/**
	 * Sends an email, similar to PHP's mail function.
	 *
	 * A true return value does not automatically mean that the user received the
	 * email successfully. It just only means that the method used was able to
	 * process the request without any errors.
	 *
	 * The default content type is `text/plain` which does not allow using HTML.
	 * However, you can set the content type of the email by using the
	 * {@see 'wp_mail_content_type'} filter.
	 *
	 * The default charset is based on the charset used on the blog. The charset can
	 * be set using the {@see 'wp_mail_charset'} filter.
	 *
	 * @since 1.2.1
	 * @since 5.5.0 is_email() is used for email validation,
	 *              instead of PHPMailer's default validator.
	 *
	 * @global PHPMailer\PHPMailer\PHPMailer $phpmailer
	 *
	 * @param string|string[] $to          Array or comma-separated list of email addresses to send message.
	 * @param string          $subject     Email subject.
	 * @param string          $message     Message contents.
	 * @param string|string[] $headers     Optional. Additional headers.
	 * @param string|string[] $attachments Optional. Paths to files to attach.
	 * @return bool Whether the email was sent successfully.
	 */
	function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
	
	// code
	
	}

unsubscription link not working

Hello, how are you guys? I have a problem here getting the unsubscription link working. In the preview it works but when I trigger the email the link is not applied and so the subscriber is unable to unsubscribe. How can I solve?

Fetch a new WordPress Nonce if the current one is expired.

The Gutenberg editor always fetches a new WordPress nonce when the current one is expired, but the user is still logged in.

WordPress/gutenberg#16683

FluentCRM should do the same.

The WP-Rest API will always return this error for expired nonces for logged-in users.

return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie check failed' ), array( 'status' => 403 ) );

From rest_cookie_check_errors.

I think this is even handled automatically if you use the official client JS for the rest API.

	// Send a refreshed nonce in header.
	rest_get_server()->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) );

Open Rates (I know they're always flawed but this is different)

Hey there,

I'm well aware open rates are flawed but I see something happening with FluentCRM that I haven't seen with any other email marketing service (and I've tried many such as: aweber, convertkit, mailchimp, kartra, activecampaign, mailerlite, etc...)

FluentCRM seems to be the only one that will automatically mark a random percentage of subscribers as having opened the email immediately after it is sent.

Here's a video:
https://www.loom.com/share/c6e08909aacf47ee89a85edcd7bcbf39

[BUG] missing 'created_by' column in fluentcrm_update_subscriber_meta

function fluentcrm_update_subscriber_meta($subscriberId, $key, $value)
{

 // more code
 
    return SubscriberMeta::create([
        'key'           => $key,
        'value'         => $value,
        'subscriber_id' => $subscriberId,
        'created_at'    => fluentCrmTimestamp()
    ]);
}

You are missing the created_by column here which is a non-nullable column without a default value. The only reason this does not throw a fatal mysqli error is because WordPress is so permissive with its sql mode.

But the user id is not recorded anyway and always set to 0 even if logged in.

It should be

 return SubscriberMeta::create([
        'key'           => $key,
        'value'         => $value,
        'created_by'    => get_current_user_id(),
        'subscriber_id' => $subscriberId,
        'created_at'    => fluentCrmTimestamp()
    ]);

[BUG] Sending custom emails via the dashboard totally disregards subscriber status and gives false positive success message.

When you visit a subscribers contact page and send him a custom email. The SubscriberController totally disregards the subscriber status.

Also if sending the mail fails no feedback is given. The response is always 200 success. A CustomEmailCampaign is created every single time. You are missing return statements.

Inside SubscriberController::sendCustomEmail()

  public function sendCustomEmail(Request $request, $contactId)
    {
        $contact = Subscriber::findOrFail($contactId);
        if ($contact->status != 'subscribed') {
        
          // YOU HAVE TO RETURN HERE.
        
            $this->sendError([
                'message' => __('Subscriber\'s status need to be subscribed.', 'fluent-crm')
            ]);
        }

        add_action('wp_mail_failed', function ($wpError) {
        
        // THIS DOES NOTHING, WE ARE INSIDE THE CLOSURE SCOPE.
        
            $this->sendError([
                'message' => $wpError->get_error_message()
            ]);
        }, 10, 1);

        $newCampaign = $request->get('campaign');
        unset($newCampaign['id']);

        $campaign = CustomEmailCampaign::create($newCampaign);

        $campaign->subscribe([$contactId], [
            'status'       => 'scheduled',
            'scheduled_at' => current_time('mysql')
        ]);

        do_action('fluentcrm_process_contact_jobs', $contact);

        return [
            'message' => __('Custom Email has been successfully sent', 'fluent-crm')
        ];
    }

[BUG] User Login trigger will run an indefinate amount of times for the same user regardless of settings

The issue is the following: It has to be $user->user_email not $user->email
This is an instance of WP_USER. The email property does not exists and thus there will never be a subscriber for this funnel.

private function isProcessable($funnel, $user)
    {
        $conditions = $funnel->conditions;
        // check update_type
        $updateType = Arr::get($conditions, 'update_type');

// subscriber will always be null.
        $subscriber = FunnelHelper::getSubscriber($user->email);
        if ($updateType == 'skip_all_if_exist' && $subscriber) {
            return false;
        }
        
        //...
    }    

Apply List action is not working.

Hello,

The action "Apply List" is not working in latest version 1.1.80. It was working in version 1.1.5. I have checked the plugin code and see code for add user to the particular list is no more available in the latest version.

You can see the previous plugin code for add user to the list here:

$subscriber->lists()->sync($lists, false);

This is no more available in the latest version.

[BUG] A trigger has to have at least one default value for conditions or no conditions will show in the UI.

This is a javascript related issue. But since JS is minified I have no way to debug.

To replicate the issue do the following.

  1. Comment out all the default values of the UserLoginTrigger (or any other trigger).
 public function getFunnelConditionDefaults($funnel)
    {
        return [
            //'update_type'  => 'update', // skip_all_actions, skip_update_if_exist
            //'user_roles'   => [],
            //'run_multiple'       => 'yes'
        ];
    }
  1. Create a funnel the trigger.
  2. See that all conditional fields are gone.
  3. Add the following dummy data:
 public function getFunnelConditionDefaults($funnel)
    {
        return [
            'foo' => 'bar'
            //'update_type'  => 'update', // skip_all_actions, skip_update_if_exist
            //'user_roles'   => [],
            //'run_multiple'       => 'yes'
        ];
    }
  1. Create the funnel again and now everything is working.

The same issue probably happens with settings fields and the reason most probably is that inside BaseTrigger.php

the default values are always serialized with wp_parse_args which does not give the desired result for an empty array.

 public function prepareEditorDetails($funnel)
    {
        $funnel->settings = wp_parse_args($funnel->settings, $this->getFunnelSettingsDefaults());
        $funnel->settingsFields = $this->getSettingsFields($funnel);
        $funnel->conditions = wp_parse_args($funnel->conditions, $this->getFunnelConditionDefaults($funnel));
        $funnel->conditionFields = $this->getConditionFields($funnel);
        return $funnel;
    }

Stop using the development autoloader.

Currently, FluentCRM comes with the composer autoloader that searches files and directories at runtime.

This will cause significant performance hits on sites with many custom autoloader classes.

FluentCRM's composer autoloader must only handle it's only classes.

For that, you need to run:

composer dump-autoload --classmap-authoritative

during the release process.

If you need to update the autoloader because of new classes in a classmap package for example, you can use dump-autoload to do that without having to go through an install or update.

Additionally, it can dump an optimized autoloader that converts PSR-0/4 packages into classmap ones for performance reasons. In large applications with many classes, the autoloader can take up a substantial portion of every request's time. Using classmaps for everything is less convenient in development, but using this option you can still use PSR-0/4 for convenience and classmaps for performance.

https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload

[BUG] Bug in WPOrm causes overwritting of model events.

Right now its only possible to register one callback per model event.

Ever successive call to Mode::registerModelEvent() will overwritte all previous callbacks.

Current code:

  public static function registerModelEvent($event, $callback)
    {
        $calledClass = static::getCalledClassName();
        static::$events["model.{$calledClass}.{$event}"] = $callback;
    }

What it should be:

public static function registerModelEvent($event, $callback)
    {
        $calledClass = static::getCalledClassName();
        static::$events["model.{$calledClass}.{$event}"][] = $callback;
    }

And then also something like this:

  public function fireModelEvent($event)
    {
        $calledClass = static::getCalledClassName();
        $eventName = "model.{$calledClass}.{$event}";

        $return = null;
        if (isset(static::$events[$eventName])) {
    
            foreach (static::$events[$eventName] as $callback) {
                
                $return = call_user_func($callback, $this);
                
                if( $return === false ) {
                    return false;
                }
            }
            
            return $return;
        }
    }

I assume you did not intend it to work this way to only be able to register one event per model action. Its kinda limiting and the fix is easy and non-breaking

Sugestions

Sorry if I'm posting this in the wrong place but could find it anywhere...

I have a few suggestions that probably can be implemented quickly and bring more flexibility to FluentCMS.
I'm a frequent user of other CRM systems and I've noticed almost immediatly a few important things:

  • Email a subscriber directly (One2One emails). Maybe using the templates and logging the sent email also...
  • In the Contact profile, in the field "Prefix" there are also some default values Mr., Mrs.. How can we change this? Such a small thing like this has a negative impact to the user...
  • Add items on the fly in the multy-select list (custom field) is not possible.
  • Strange chars appear when using accents in the name of the custom fields (áàãõéêç). Also no ability to change the "fieldName". Conditional fields is also a must but that's why there is a pro version. My suggestion is between fields themselves (District, State, Province). Maybe put some time on custom fields to setup max lenght, validation rules, Etc.

Please don't get me wrong. I've been trying to use FluentCRM and learn about it before I'm putting any money on it... Some of this small things have a huge impact to the user experience. Just keep the good work and keep improving.

Submission Form+Automation

Hi,I'm not sure it's the right place to report a issue here,I started to test fluentcrm a few days ago,first thing is setting up a submission form,and enable double opt-in email conformation,the vistiors submit their emails throught the form and captured in fluentcrm contacts,showing submisson status "pending" before the vistiors click the confirmation button in email,once they confirm the the submission,the stauts will change to "subscribed",everything is good so far.But after setting up an automation for sending a marketing email to the subscribers whos status are"subscribed",the form will ingore the pending status, subscribers don't need to click confirmation even they can get the double opt-in confirmation email.their submission status are"subscribed" already after they submit the email through that form.

Grammer and spelling

I love this product!
Where is the best place to post grammar and spelling in the UI?

Edit: My timezone was off an hour which was causing this confusion:
I see this grammatical error popping up in several places. "23 minutes from now" references the future. "23 minutes ago" references the past.

[BUG] When using wp user account sync and updating the email adress in FluentCRM the user_id column will be lost.

When you enable the option "Auto Sync User Data and Contact Data" and then update a contacts email address in fluent crm the new user record will loose the user_id column and thus will no longer be linked to the actual WordPress user account.
This does not happen without that option enabled.

The reason is the following code inside Subscriber.php where you have an updating event registered on the model.

 static::updating(function ($model) {
            if ($model->user_id && Helper::isUserSyncEnabled()) {
            
                // Email was updated so $user will be bool(false)
                $user = get_user_by('email', $model->email);
                if ($user) {
                    if ($model->first_name) {
                        update_user_meta($user->ID, 'first_name', $model->first_name);
                    }
                    if ($model->last_name) {
                        update_user_meta($user->ID, 'last_name', $model->last_name);
                    }
                }
                
                // $user->ID will be null. We lost the account connection. 
                $model->user_id = $user->ID; // in case user id mismatch
            }
        });

Since we already know that we have a user_id on the model we should do the following instead:

 $user = get_user_by('email', $model->email);
    
if ( ! $user ) {
  $user = get_user_by('id', $model->user_id);
 }

Thus we will at least not loose the connection to the user account. Wheter we want to update the actual user account email is another question. This should probably be configurable as well.

The updated version could look like this:

 if ($model->user_id && Helper::isUserSyncEnabled()) {
                $user = get_user_by('email', $model->email);
                $email_mismatch = false;
                if ( ! $user ) {
                    $email_mismatch = true;
                    $user = get_user_by('id', $model->user_id);
                }
                
                if ($user) {
                    if ($model->first_name) {
                        update_user_meta($user->ID, 'first_name', $model->first_name);
                    }
                    if ($model->last_name) {
                        update_user_meta($user->ID, 'last_name', $model->last_name);
                    }
                }
                
                if ( $user && $email_mismatch && apply_filters('fluentcrm_update_wp_user_email_on_change', false ) ) {
                    $user->user_email = $model->email;
                    wp_update_user($user);
                }
                
                $model->user_id = $user->ID; // in case user id mismatch
            }

Problem with downloading localization files

After installing the plugin from wp.org repository to /wp-content/languages/plugins directory should download localization files corresponding to non-English site language. But with FluentCRM, this does not seem to happen.

[BUG] Unable to create new lists while creating a funnel trigger.

ListsController::storeBulk will throw an error because you are trying to do a foreach loop on a non array.

You accounted for this in the TagsController::storeBulk method which works fine an allows creation in the frontend.

This slight change below will allow to create lists while creating a new ListApplied trigger.

 $lists = $request->get('lists', []);
    
        if(empty($lists)) {
            $lists = $this->request->get('items', []);
        }
        
        foreach ($lists as $list) {
            if (!$list['title'] || empty($list['title'])) {
                continue;
            }
    
            if(empty($list['slug'])) {
                $list['slug'] = $list['title'];
            }
            
            $list = Lists::updateOrCreate(
                ['slug' => sanitize_title($list['slug'], 'display')],
                ['title' => sanitize_text_field($list['title'])]
            );
            do_action('fluentcrm_list_created', $list->id);
        }

[Enhancment] Add filter to allow adding of custom icons for funnel categories

It would be very nice if there were a filter to allow customizing the funnel icons when selecting a new trigger. Right now we are stuck with the default trigger for custom categories which is not a good UI/UX experience.

Please consider changing AdminMenu.php line 427 to

   'funnel_cat_icons' => apply_filters('fluent_crm_funnel_icons',[
                'wordpresstriggers' => fluentCrmMix('images/funnel_icons/wordpress.svg'),
                'woocommerce' => fluentCrmMix('images/funnel_icons/woocommerce.svg'),
                'lifterlms' => fluentCrmMix('images/funnel_icons/lifterlms.svg'),
                'easydigitaldownloads' => fluentCrmMix('images/funnel_icons/easydigitaldownloads.svg'),
                'learndash' => fluentCrmMix('images/funnel_icons/learndash.svg'),
                'memberpress' => fluentCrmMix('images/funnel_icons/memberpress.svg'),
                'paidmembershippro' => fluentCrmMix('images/funnel_icons/paidmembershippro.svg'),
                'restrictcontentpro' => fluentCrmMix('images/funnel_icons/restrictcontentpro.svg'),
                'tutorlms' => fluentCrmMix('images/funnel_icons/tutorlms.svg'),
                'wishlistmember' => fluentCrmMix('images/funnel_icons/wishlistmember.svg'),
            ])

This will allow addition removal and customization of funnel icons without breaking anything for users who dont use a filter there.

[Enhancement] Extract function that retrieves all public list Ids out of ExternalPages.php

 public static function getPublicListIDs()
    {
        $emailSettings = Helper::getGlobalEmailSettings();
        $lists = [];
        $preListType = Arr::get($emailSettings, 'pref_list_type', 'none');
        if ($preListType == 'filtered_only') {
            $prefListItems = Arr::get($emailSettings, 'pref_list_items', []);
            if ($prefListItems) {
                $lists = Lists::whereIn('id', $prefListItems)->get();
                if ($lists->isEmpty()) {
                    return [];
                }
            }
        } else if ($preListType == 'all') {
            $lists = Lists::get();
            if ($lists->isEmpty()) {
                return [];
            }
        }
        return $lists;
    }

This code is a private method inside ExternalPages.php. I think everything that is related to the global settings should be kept inside the Helper class to keep things consistent.

Anyway, this is not something that should belong into ExternalPages.php as its very useful to have access to in other parts of your application.

I just moved it inside Helper.php

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.