Active Campaign Integration - What Do You Need to Remember About?

Automated e-mails is a big business nowadays, and quite a topic to discuss.
Usually when developing a rails application a simple SMTP provider like Sendgrid or Mandrill with an addition of Sidekiq and Cron is more than enough. Nevertheless, you may encounter clients who already used some tools and have a large mailing list in, ActiveCampaign being one of them. Here are a few tips about ActiveCampaign and Rails implementation with the use of CloudFlare.
Basic Architecture
Obviously, we are not going to reinvent the wheel. There is a nice active-campaign-rails gem, which is a wrapper for their API. You may also generate form the inside of ActiveCampaign itself - and then paste iframe inside your view/template, but since we want to have more control over it (e.g. adding styling to iframe is not possible) it’s not the way we will proceed. The other gem I used is reform for form objects. I don’t have a related Enquiry
model, hence OpenStruct.new
is passed to EnquiryForm
instance. After a user submits this form, we just send the information to AC to let it deal with it. Here is the form object I used.
class EnquiryForm < Reform::Form
properties :first_name, :email, :topic, :question
validates :first_name, :email, :topic, :question, presence: true
validates :email, email: true
end
I also wrote a Service Object that will handle submitting logic. I wrapped it in an Operations module (Trailblazer Book suggests it should be done this way, but whatever fits your needs).
module Operations
module ActiveCampaign
class EnquiryManager
attr_reader :form
def initialize(params)
@params = params
@form = EnquiryForm.new(OpenStruct.new)
@list_id = AppConfig.active_campaign.api_enquiry_list_id
@active_campaign = ::ActiveCampaign.new(
api_endpoint: AppConfig.active_campaign.api_endpoint,
api_key: AppConfig.active_campaign.api_key
)
end
def call
return unless form.validate(@params)
sync_form(form)
end
private
def sync_form(form)
form.sync do |hash|
result = parsed_form(hash)
result['result_code'].to_i.positive?
end
end
def parsed_form(hash)
JSON.parse(
@active_campaign.contact_sync(
hash.merge(
"status[#{@list_id}]" => 1,
"p[#{@list_id}]" => [@list_id],
'first_name' => hash['first_name'],
'field[%TOPIC%,0]' => hash['topic'],
'field[%QUESTION%,0]' => hash['question']
)
)
)
end
end
end
end
That I’m am verifying status with the use of the positive?
method - AC doesn’t respond with 200 or 404 here, it’s just 1 or 0. Since, we wanted the user to be able to submit the form many times (and update the question field each time), I used contact_sync
instead of contact_add
.
Ok, good to go, now some html form to handle it.
.row
.col-xs-6
= f.field_container :first_name, class: ['form-group'] do
= f.label :first_name
= f.text_field :first_name, class: 'form-control'
= error_message_on @enquiry_form, :first_name
.col-xs-6
= f.field_container :email, class: ['form-group'] do
= f.label :email
= f.email_field :email, class: 'form-control'
= error_message_on @enquiry_form, :email
.col-xs-12
= f.field_container :topic, class: ['form-group'] do
= f.label :topic
= f.select :topic, @topics,
= error_message_on @enquiry_form, :topic
.col-xs-12
= f.field_container :question, class: ['form-group'] do
= f.label :question
= f.text_field :question, class: 'form-control'
= error_message_on @enquiry_form, :first_name
And we are good to go. After a few tests I went to the ActiveCampaign console, displayed users list and bam! My e-mail was there. Let’s play some fifa and go home then... Well, not so fast!
Tip no. 1 Always add IP.
So in the documentation for contact_sync
there is ip4 field.
|
IP address of the contact. Example: '127.0.0.1' If not supplied, it will default to '127.0.0.1' While documentation stands that this field is not really required, I strongly recommend adding it. Collecting geo-location data might be one of the reasons, but in our case, the more serious one was that we were experiencing problems with setting up automation for some contacts that were added without providing the ip4 field in request. I e-mailed ActiveCampaign support about this issue, but it is hard to recreate it, as only part of our contacts had this problem. Ok then, let’s add the above mentioned field then. I modified my service object a little bit. def parsed_form(hash) Perfect. The line I added is private |
Great, I pushed changes to staging, tested it a few times, and moved this task to ‘Waiting for qa’ column in our Jira. Not for long though.
Tip no.2 Make sure your IP isn’t overwritten
We started getting information that some clients still weren’t added properly (once again, it looked like they were added, but automation wasn’t triggered). The reason was CloudFlare, which works as reverse proxy and is overriding default user IP (the one you access via request.remote_ip
) with its own IP addresses. There are many IP addresses which CF uses, but if they start duplicating inside of AC, which will happen after some time, users won’t get the automated campaign. How to fix it? Easy.
private
def enquiry_params
params
.require(:enquiry)
.permit(:first_name, :email, :topic, :preferred_city, :question)
.merge(client_ip: Rails.env.production? ? request.headers['Cf-Connecting-IP'] : request.remote_ip))
end
Fortunately, Cloudflare uses Cf-Connecting-IP
field and stores real IP there. Once again, I added the above line, pushed changes, checked it - it looked like everything is alright. Once again, it wasn’t.
Tip no.3 Make sure you handle IPv6.
The last problem was connected with proper handling of IPv6. Active Campaign doesn't support adding contacts with IPv6. Someone mentioned it here, but also in API specification there is no info about IP6v support, only ip4. How to solve this issue? Well, CloudFlare complicated things a little bit in the previous case, but was very helpful in this one, so I can’t be mad. Let’s get straight to the solution:
- Cloudflare provides a service called `Pseudo IPv4` which, when enabled, adds custom headers that can be used for tools that don't support IP6. The easiest way would be to switch it on with the 'Overwrite headers' option, here is the info.
- After enabling it,
Cf-Connecting-IP
andX-Forwarded-For
will have either- original IPv4 from a user if he uses IPv4 or
- pseudo-IPv4 if user uses IPv6.
and that should fix the problem with AC non handling IPv6 users. At this point I extracted the IP fetching logic to another method called real_client_ip
. Remember that IP may be used also by other tools, like Spree or devise (e.g. last_sign_in_ip
) so if you use reverse proxy like CF, you need to update your code (and sometimes even overwrite gem code).
def real_client_ip
(Rails.env.production? || Rails.env.beta?) ? request.headers['Cf-Connecting-IP'] : request.remote_ip
end
And updated enquiry_params
method with
def enquiry_params
params
.require(:enquiry)
.permit(:first_name, :email, :topic, :preferred_gym, :question)
.merge(client_ip: real_client_ip)
end
In the meantime we also added a `beta` environment, where CF is enabled, hence Rails.env.beta?
is used above. In our case it was something between staging and production.
Additional debugging tip
Note that last case is hard to be tested if you don’t use IPv6 yourself, or don’t know anyone who uses it in your company. And I don’t. There are solutions like online proxy - but it didn’t work for me (I couldn’t submit a form when using it). We could try some paid tools to fake it, but not to complicate things too much, and not to waste more time on it, we did something different. At netguru we are using Rollbar. And rollbar is a good tool not only for reporting errors, but also debugging. So instead of recreating the case by ourselves, we added something like this:
def real_client_ip
Rollbar.error("DEBUGGING IPv6”, extra: params[:enquiry][:email]) if debugging_ipv6_users?
(Rails.env.production? || Rails.env.beta?) ? request.headers['Cf-Connecting-IP'] : request.remote_ip
end
def debugging_ipv6_users?
request.headers['Cf-Connecting-IPv6'].present? && controller_path.starts_with?('api/active_campaign')
End
If Cf-Connecting-IPv6
headers are present - that means that this specific user uses IPv6. We waited 2 days and some people with IPv6 occurred in rollbar logs. Since we printed params to rollbar, we could check manually if a user is properly added to the AC list and automation is triggered. This time it worked. Yay!
Conclusion
While setting basic API integration with AC seems to be a piece of cake, there are still some things that may surprise you. Additionally, those cases can be hard to spot in the short-term which can cost you some time and anger. To avoid that, make sure to go through that list and take care of your clients IPs when dealing with ActiveCampaign. Good luck!