Category Archives: Tech_Friday

WordPress: Troubleshooting Contact Forms 2

In our last episode, I determined that the contact form wasn’t working, even on the server that I thought was working. Further investigation, and a several eMails back and forth from Linode tech support yielded the following:

  1. The problem is not the Contact Form 7, it is with the eMail setup on the server where WordPress is installed. (Well there is a  problem with CF7, but we’ll get to that in a minute.)
  2. Linode has a guide for setting up PostFix (the background eMail program). In general, Linode guides are great for any Linux application, whether you are using Linode or not.
  3. To cut to the chase, there were several changes that needed to made.
    1. It looks like PostFix wasn’t completely configured. It was listening on only localhost. To fix this:Update the “inet_interfaces” value to listen on the actual IP address. in the /etc/postfix/main.cf. file  Change this:
      From: inet_interfaces = loopback-only
      To: inet_interfaces = xx.xxx.xxx.xxx, 127.0.0.1

      where xxx.xxx.xxx.xxx is the machine’s IP address.

      – Restart Postfix

      /etc/init.d/postfix restart

      – Verify that everything is working fine

      netstat -tulpn | grep 25  #Verify listening on port 25
      tail -f /var/log/maillog  #See the result of sending an eMail.
  4. The other piece of the puzzle involved editing the Domain Name nameserver records. These are required for Google to accept eMails for delivery, and involve two addtional entries for the domain name.
    1. A reverse name lookup record.
    2. An SPF record.Discussions for both of these items are located at the following links:

      https://www.linode.com/docs/networking/dns/setting-reverse-dns
      https://www.linode.com/docs/networking/dns/introduction-to-dns-records#spf

  5. Finally, the Contact Form CF7 plugin has a setting for the “from” address that is used as the sending address.  Typically, you want this to be something like customerservice@mydomain.com, but the default Contact Form setting actually allows the user to put in their own email address which is then included in the email generated by the form, so that you can simply reply to the email generated by the form. A more secure option (and the option required by Google), is to have the from address be in your own domain.  I just use the same address for both. If you look at the message body field below, you can see that the email address of the person making the inquiry will show up in the body of the message.Contact Setup

WordPress: Troubleshooting Contact Forms

Down the rabbit holeI’m trying to get a contact form working on our WordPress site, and it is a little more complicated than I thought it might be.

The contact form plugin itself in WordPress. We’re using Contact Form 7. Documentation for Contact Form 7 says something to the effect “we’re sending upteen zillion eMails a day, and CF form “just works” for everybody.  Well, kinda, sorta. There are several other issues involved:

  • an MTA (mail transfer agent) which maybe (?)  has to be on the WordPress server
  • the php_mail() function which is part of PHP (the programming language for WordPress)
  • the wp_mail() function which may or may not call the php_mail() function
  • Restrictions on to and from addresses.

I wanted to just run everything through our Google Mail SMTP account, but that seems to add an additional layer.  So first I started with our blog site which is on another server, and tested whether the contact form worked there. It actually doesn’t,  that is, it appears to send mail correctly from the users’s perspective, but no email was received anywhere.

 

Per this note I checked to see if anything was listening on port 25, the usual SMTP port.

$ netstat -tanpl | grep :25
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 3030/master
tcp6 0 0 ::1:25 :::* LISTEN 3030/master

$ lsof -i :25
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
master 3030 root 12u IPv4 9667 0t0 TCP localhost:smtp (LISTEN)
master 3030 root 13u IPv6 9668 0t0 TCP ip6-localhost:smtp (LISTEN)

Looks promising.  it appears that both IP4 and IP6 are listening.

So, I created a basic PHP script to test at the server command line, and saved this as testphpmail.php. There are three parameters, the receiver’s address, the subject and the body of the message.

//This is a test of the basic PHP eMail function.

<?php
mail('joeblow@blow.com', "Test PHP Mail", "Test mail from PHP Mail");
?>

Run this script from the command line with the following command:

php -f testmail.php

This worked; I received the eMail. It shows that it comes from root.  At least  I knew that at least the fundamentals are in place on the server.

Checking the mail log,  at /var/log/mail.log I see the following:

$ cat/var/log/mail.log
May 20 14:28:17 li239-124 postfix/pickup[28709]: 11864F123: uid=0 from=<root>
May 20 14:28:17 li239-124 postfix/cleanup[28806]: 11864F123: message-id=<20160520142817.11864F123@localhost>
May 20 14:28:17 li239-124 postfix/qmgr[3039]: 11864F123: from=<root@localhost>, size=351, nrcpt=1 (queue active)
May 20 14:28:17 li239-124 postfix/smtp[28808]: 11864F123: to=<lkkg@myserver.org>,
relay=aspmx.l.google.com[173.194.204.27]:25, delay=0.21, delays=0.01/0.01/0.1/0.09, 
dsn=2.0.0, status=sent (250 2.0.0 OK 1463754497 e92si18328547qga.115 - gsmtp)

So this shows that the message was sent, using root@localhost as the sender’s address and my work eMail as the target.  And indeed, I received this email at work.

I checked the log file at /var/log/mail.log which presented various points of interest…including the fact that we are running the Postfix sendmail email server.  But there is also a status=bounced message for IP6.

$ cat /var/log/mail.log 
May 20 13:50:15 li239-124 postfix/pickup[13030]: 2973DF23B: uid=33 from=<www-data>
May 20 13:50:15 li239-124 postfix/cleanup[14078]: 2973DF23B: message-id=<49922f80371c574883b8b8d699f47d9b@blog.kidsgardening.org>
May 20 13:50:15 li239-124 postfix/qmgr[3039]: 2973DF23B: from=<www-data@localhost>, size=869, nrcpt=1 (queue active)
May 20 13:50:15 li239-124 postfix/smtp[14080]: 2973DF23B: to=<lkkg@myserver.org>, relay=aspmx.l.google.com[2607:f8b0:400d:c08::1a]:25, delay=0.41, delays=0.01/0.01/0.09/0.3, dsn=5.7.1, 
status=bounced (host aspmx.l.google.com[2607:f8b0:400d:c08::1a] said: 550-5.7.1 [2600:3c03::f03c:91ff:fe6e:916c] Our system has detected that this 550-5.7.1 message does not meet IPv6 sending guidelines regarding PTR records 550-5.7.1 and authentication. 
Please review 550-5.7.1 https://support.google.com/mail/?p=ipv6_authentication_error for more 550 5.7.1 information. a139si18154032qkc.140 - gsmtp (in reply to end of DATA command))
May 20 13:50:15 li239-124 postfix/cleanup[14078]: 8DAAAF23C: message-id=<20160520135015.8DAAAF23C@localhost>
May 20 13:50:15 li239-124 postfix/bounce[14081]: 2973DF23B: sender non-delivery notification: 8DAAAF23C
May 20 13:50:15 li239-124 postfix/qmgr[3039]: 8DAAAF23C: from=<>, size=3381, nrcpt=1 (queue active)
May 20 13:50:15 li239-124 postfix/qmgr[3039]: 2973DF23B: removed
May 20 13:50:15 li239-124 postfix/local[14082]: 8DAAAF23C: to=<www-data@localhost>, relay=local, delay=0.01, delays=0/0.01/0/0, dsn=2.0.0, status=sent (delivered to mailbox)
May 20 13:50:15 li239-124 postfix/qmgr[3039]: 8DAAAF23C: removed
root@li239-124:~# dpkg -S 'which sendmail'
dpkg-query: no path found matching pattern *which sendmail*.
root@li239-124:~# netstat -tanpl | grep:25
grep:25: command not found
root@li239-124:~# netstat -tanpl | grep :25
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 3030/master
tcp6 0 0 ::1:25 :::* LISTEN 3030/master

Just to be sure, I ran the PHP script again having it send to my home email address which is outside my work domain.  This bounced and I didn’t receive it at the home address.  Now,  in theory, this might not be a bad thing, since all of the Contact Form 7  messages from the blog page should be sent to an address within the same domain, like customerservice@myserver.com.

So far, then we’ve established:

  • PostFix is installed on the blog’s server.
  • Contact Form 7 shows that it is sending message, but the messages are bouncing.  (Hmm…I wonder what I’ve been missing all those times? )

This is so typical of IT. You think you’re troubleshooting an issue on one server, and upon investigating the same issue on another server that you think is working…you find that it isn’t and down the rabbit hole you go!

WordPress Woes and .htaccess

After spending months working on our new WordPress site hosted at Linode, we were ready to flip the DNS from xxx.xxx.xxx.xxx to a realdomainname.org.

Not so fast!” said the SEO expert… “you need to have your 301s in place.” So, we went through the drill and identified the top 100 pages on the old site. These were pages that had been visited in the previous month or so, and also ones that we knew were especially popular. We then created an .htaccess file, which is a file containing instructions for any visitor who arrives at the new site via a search on an old URL from the old site.   Here’s a snippet of what it looks like to start.

# LK 5/2/2016 
Options +FollowSymLinks
RewriteEngine on
Redirect 301 /node/96821 http://www.realdomainname.org/toolbox/
Redirect 301 /node/120 http://www.realdomainname.org/designing/
Redirect 301 /node/2 http://www.realdomainname.org/
Redirect 301 /node/11522 http://www.realdomainname.org/basics/
Redirect 301 /node/13152 http://www.realdomainname.org/toolbox/
Redirect 301 /node/12931 http://www.realdomainname.org/activities/

So far so good. Except this is ass-backwards. We should have worked out the WordPress URLs before creating the .htaccess file because WordPress itself has something to say about the contents of the .htaccess file, viz:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

# END WordPress

These entries in the WordPress file are created based on a WordPress setting under Settings->Permalinks which allow you to change what shows as a URL for access. You can choose among a number of options including having the blog post title in the URL as well as the post’s category. (another reason for assigning a post to only one category).

One you have saved the permalinks setting, WordPress will write to your .htaccess file. If this is the first time, you will see that the .htaccess file contains lines similar to those in the # BEGIN WordPress block above.  Otherwise, my guess is that WordPress rewrites the block to accommodate how you want the permalinks to appear.

Other issues:

Note the lines

<IfModule mod_rewrite.c>
RewriteEngine On

refer to an Apache web site module called mod_rewrite. This is a piece of code in the the Apache web server which accomodates the rewriting of URLS.  So, for example, maybe you have a post that is physically located at:

 /var/www/html/wordpress/wp-content/posts/mylovelypost.html

A default URL for a blog post might look something like:

http://www.realdomainname.com/index.php/wp-content/mylovelypost/

However, after implementing your pretty permalinks it will automatically reset itself to

http://www.realdomainname.com/mylovelypsost/

or even

http://www.realdomainname.com/mylovelycategory-mylovelypost/

if you choose to include a category.in the URL formula. This “virtual” URL is the one that gets indexed by the search engines, and so is worth giving some serious thought so that you get it right the first time.

Note that this whole exercise is possible only on self-hosted WordPress sites, not on sites hosted at WordPress.com. So, at this writing, the techfornoprofits.com  site is hosted at WordPress.com, and so URLs are fixed using the formula of http://site+year+month+day+blog_post_title.

https://techfornonprofits.com/2016/04/12/ten-ways-to-improve-a-grant-application/

So, having gotten the permalinks thing squared away, I then added the 301 redirects to the .htaccess file.  These are in the  format of:

Redirect 301 /node/96821 http://www.realdomainname.org/toolbox/

Statement = Redirect 301
Old site page name
New site address.

The /node/96821 address for the old site page is a convention from Drupal. (Drupal is another content management system. Our new WordPress site is  a conversion from a Drupal 6.7 site).

Two notes regarding the .htaccess file:

  1. The .htaccess file is normally a hidden file, so it isn’t visible within a normal directory listing. There is a setting in FileZilla which permanently “show hidden files”  If the file name is prefixed with a period, it is considered a “hidden” file.
    .
  2. Once the .htaccess file has been created the best practice is to turn off the ability for it to be written to by changing the file permissions. Conversely, if you either don’t see any changes or get an error message when attempting to adjust the Settings-Permalinks in WordPress….the .htaccess may either be non-existent, or flagged as read-only. WordPress has to be able to write changes to the .htaccess file.

At this point the permalinks are squared away, and the 301 redirects are in place. But our woes are just beginning!

SalesForce: Donors and Donations

Looking into this further,  the SalesForce non-profit starter pack deals with Donors and Donations.

A typical issue when dealing with donors is that you often want to track relationships between different donors; for example spouses in the same household who contribute independently to your organization. One way of doing this (not just in SalesForce) is the notion of a household.  In database terms, a household would represent many-to-one relationship where a one or more contacts are members of a single household.

SalesForce will accommodate no less than 4 different kinds of contact/account models. The details are explained in their FAQ about account models.  The upshot is that out of the box, the most recent version of their non-profit starter pack uses what they call their household model.

SalesForcehohousehold

If you check out the terminology in the diagram above, you can see that they are mapping non-profit terminology to their sales terminology.

Non-Profit SalesForce
Donor Contact
Household Account
Donation Opportunity

We’ll have to look at this when it is wired up as a online form.

SalesForce NonProfit Starter Pack

We’ve been looking into a Customer Relationship Management system for our nonprofit, that will encompass more than the just the usual fundraising type of database. One option that we’re taking a hard look at is the cloud-based SalesForce nonprofit version. One of the tasks is to learn the workflow jargon such as:

  • Opportunity Stages
  • Opportunity Sales Processes
  • Opportunity Record Types

Salesforce provides a donation for up to 10 users for any qualifying non-profit organization. The Nonprofit Starter Pack is a pre-configured set of functions and data elements that can be overlaid on to a “standard” SalesForce company installation. The donation includes significant online support, documentation,  and forums with members from nonprofits who are using the system.

As a preliminary exercise, I’m going to attempt to import our donor data from our previous Little Green Light system. This consists of two tables; a donor contact table and a transaction table which lists all of the donations made by members in the donor table. Both of these are currently living in FileMaker which can export into a variety of file formats including Excel and comma delimited (text) files.

PowerShell: Shorten URLs with Google’s API

The following PowerShell code will return a shortened URL from a long URL using the Google link shortening URL API. Contrast this code with the code for Bit.ly. There are couple differences: 
1. Calling the Google API is done with a POST. With Bit.ly it is a GET. 
2. I’ve included an interactive prompt in the code below, that will get the long URL from the command line. Once the shortened link is printed, you can paste it to the clipboard. (Or…better yet, avoid mousing around, and pipe the result to the clipboard using the clip.exe utility 


PS >$MyShortURL.id | clip.exe 

3. If it doesn’t matter, and you are already using the Google API,  use Bitly; initial setup is a snap.  I worked out the Google version because we want to be able to track our shortened links using Google Analytics.  Links shortened by Google are automatically made into tracking links. 

4. The Google call uses the PowerShell commandlet Invoke-RestMethod. The Bitly call uses the Invoke-WebRequest commandlet.  

5. For troubleshooting, I used the HttpRequestor FoxFire plugin o make sure I was making the correct API call.  


# Generate shortened URL using the Google API 
# First time set up:  
# * Log in with your Google login name and password  
# * Go to the Google Developer Console at:
# * https://console.developers.google.com/project
# * Create a new project. 
# * Obtain an application key.(Don’t worry about oAuth)
# * Be sure that you allow requests from 
# * “all IP addresses” 

$APIKey=”xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”

# Hard coded long link below,or comment out line 17 and 
# uncomment 20-23 for an interactive command line 

#######################################################
# Paste in the URL that you want to shorten here. 
 $LongURL=”http://twitter.com” 
#
#######################################################
# $Instructions = “You can paste a long URL into the`
# command line by right-clicking the mouse.`n”
# $Instructions 
# $LongURL= Read-Host “Enter or Paste in the Long URL”
#######################################################

#Convert the Long URL to a JSON key/value 
$LongHash=@{“longUrl”=$LongURL}
$MyLongURI = $LongHash | ConvertTo-Json

# Make the API call
$MyShortURL=Invoke-RestMethod `
-Uri https://www.googleapis.com/urlshortener/v1/url?key=$APIKey `
-Body $MyLongURI `
-ContentType application/json `
-Method Post

# Print out the shortened URL 
$LongURL
$MyShortURL.id

BrightPearl API Part V – PowerShell

PowerShell, is a Microsoft download, currently at version 4.0.  It is a batch command language and enhanced command shell which is the current successor to the CMD.exe found in Windows. It seems to be a bit of a mash-up between the old DOS command line, the Unix-style shells like BASH, and includes connections to .NET objects. Unfortunately, it also abstracts a number of parameters to objects, such that you can’t just put stuff on a command line; you have to assign it to an object parameter when passing parameters, or when getting things out from a returned object. 
 
To continue with our Brightpearl example:
Recall that when working with the Brighpearl API, you have to first obtain a temporary authorization code, which is good for roughly 30 minutes. You do this by making an HTTP POST which includes your credentials in the body of the post.
 
Obtain a Brightpearl Authorization Code:
 
This is a two step process:
 
1. First stuff the authorization credentials into a variable. Note that the credentials are formatted in nested JSON notation.
$bpauth = “{apiAccountCredtentials:{emailAddress:”myemail@mycompany.com”, password:”Mypassword”}}”
2. Execute the command line call using the stored log-in credentials in the Body parameter
http://ws-use.brightpearl.com/mybpaccountname/authorise -Body $bpauth -ContentType application/json -Method Post
The Brightpearl server returns an authorization code which is displayed as an object with a series of parameters. The authorization code is contained in the a JSON string in the “Content” parameter.  This is the authorization code that must accompany any subsequent call to the Brightpearl API.
StatusCode        : 200
StatusDescription : OK
Content           : {“response”:”53145c429-x1xx-y2sf-z34a-8abc9cde96f9gh”}
RawContent        : HTTP/1.1 200 OK
                    Pragma: no-cache
                    ruid: a745efb3-2414-428f-8427-5001e3c810b8
                    Connection: keep-alive
                    Content-Length: 51
                    Cache-Control: no-cache, must-revalidate
                    Content-Type: application/json;char…
Forms             : {}
Headers           : {[Pragma, no-cache], [ruid, a745efb3-2414-428f-8427-5001e3c810b8], [Connection, keep-alive], [Content-Length, 51]…}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 51
To isolate the code itself, you have to assign the output of the above command to a variable, and then access it using dot notation.
So, restate the above command to assign the output to a variable:
$bpcode = Invoke-WebRequest -Uri http://ws-use.brightpearl/microdesign/authorize -Body $bpauth -ContentType application/json – Method Post
If you then execute $bpcode.Content, then you’ll get the authorization code, (again in JSON format).
PS C:UsersLarryPowerShell>$bpcode.Content
This returns:
  {“response”:”53145c429-x1xx-y2sf-z34a-8abc9cde96f9gh”}
To see this in a more readable format:
PS C:UsersLarryPowerShell>$bpcode.Content | ConvertFrom-Json
response
——–
53145c429-x1xx-y2sf-z34a-8abc9cde96f9gh
To assign the authorization code to a variable itself, we either have to strip it from the JSON code, or from the returned converted version.  It probably is easier to convert via the JSON code because we can do a text search everything after the colon, and then strip off the double quotes and the last curly bracket.
$bpstring=$bpcode.content
The following command strips off everything up to the start of the actual code.
 
$bpstring = $bpstring. Trimstart( “{`”response`”`:” )
It gives us:   
“53145c429-x1xx-y2sf-z34a-8abc9cde96f9gh”}
The following command strips off the final quote and curly bracket.
$bpstring = $bpstring. Trimend( “`”`}”)
This gives us our final result; what we’re really looking for:
53145c429-x1xx-y2sf-z34a-8abc9cde96f9gh

Final Script:

The final script is pretty close to the interactive commands entered at the PowerShell command line. The main exception is the addition of the back tick escape character within the script. If you use the PowerShell Interactive Shell as an editor,  it will color code things nicely and flag scripting errors. 
# BrightPearl API: Get an authorization code for subsequent API queries
# Note escape character is the “`” (back tick), instead of the usual backslash.
# Double quotes need to be escaped when nested inside.
# LK 10/9/2014
 
 
# Assign credentials to an authorization object.
$bpauth = “{apiAccountCredentials:{emailAddress:`”myeMailAddress@mycompany.com`”,password:`”myPassword`”}}”
 
# Execute the HTTP POST to retrieve the authorization code. The result is assigned to the string $bpstring
# Note use of the back tick as a line continuation character
$bpstring = Invoke-WebRequest `
-Body $bpauth `
-ContentType application/json `
-Method Post
 
$bpAuthCode =$bpstring . Content
$bpAuthCode =$bpAuthCode . Trimstart( “{`”response`”`:” )
$bpAuthCode =$bpAuthCode . TrimEnd( “`”`}” #Note escape codes for the search expression
 
# Print the Authorization Code
$bpAuthCode 

Brightpearl API Part III: Working with Resources

This is the third posting regarding the Shopify / Brightpearl API
Part I is located here.
Part II is located here.

The Brightpearl API describes resources that can be queried using an HTTP GET request. Resources look suspiciously like tables in a database.  The Brightpearl resources include: 

Orders
Contact
Return contact information for a single contract with the ID of #200 
GET https://ws-use.brightpearl.com/2.0.0/myaccountid/contact-service/contact/4 
Postal-Address
GET https://ws-use.brightpearl.com/2.0.0/myaccountid/contact-service/postal-address/107
Recall that each of the GET calls above require a valid authorization code to be included in the call as a header 
brightpearl-auth: xxxxxx-xxxxxxx-xxxxxxxx-xxxxxxx-xxxxxxxxxx
Also, that you’d replace myaccountid with the name of your Brightpearl account. 
Since the order returns only the contact ID,  we need to go to contacts to get the person’s name. 
Since the contact only returns the person’s first and last name, we need to go to the address object to get the address. 
So one way that this might work….    
Dimension an array arWorldShip[10], which will hold the following pieces of information: 
order number, contact id, firstName, lastName, address id, address1, address2, address3, address4, postal code, 
Query to get the orders for the day, and then loop through each order 
For each shop order:  
     Store the order number in an arWolrdShip[1]  and the contact ID in arWorldShip[2]
     Query for the customer – to get the first and last name, and address ID
     Store these in the array 
     Query for the address – to get the address information.  (oh and check…for shipping/vs. postal address). 
     Store these in the array 
Next shop order 
Write out the array variables as a record; converting to .DBF.  

Web Services, REST, Shopify and Brightpearl Part I

Part I.

Background:

I am currently working on a project which involves a Shopify online web store, and the Brightpearl Inventory and CRM system. Both of these cloud-based systems have an Application Programmer’s Interface, (API) which provide a programmatic way to query and manipulate the data that has been entered via the normal web interface. They use these APIs to talk to each other and make them available to programmers who want to create custom functionality or plugins for the systems. Communication with these APIs can be done using a REST compatible client written in PHP, Python, Ruby on Rails, or a host of 3rd-generation languages like C# and Visual Basic.
REST stands for Representational State Transfer. This is the most recent flavor of network programming, similar to SOAP, XML, and XML-RPC, and even good old remote procedure calls.

Use-Case:

I’m looking into a way to extract data from the Brightpearl inventory system; I want to query for each day’s purchases and extract the order number, customer name and shipping information. I want to take this information and format it as an .DBF file for use by the UPS WorldShip program. Note that in this example, I’m interested in being a client of an existing web service, and, for the moment I really just need to query the service for existing data, I don’t need to add or delete records on the server.
To start this odyssey, I’m using my Windows workstation. I’m thinking eventually if I need to have a web server for testing (to run PHP or RAILS for example), that I’ll spin that up as a virtual machine using VirtualBox on Windows with Ubuntu Server as my guest OS with a mySQL backend.
The Brighpearl documentation suggests several tools that can be used to send requests to the API. Perverse as it sounds. I found it was helpful to install no less than three add-ons for FoxFire and Chrome to send the API requests, which enabled learning the mechanics of the process a little easier.
For Chrome:
For FireFox:
Each of these three add-ons allow you to send requests to a web server. Each is slightly different. The Chrome add-on includes a parser for JSON data, which is really helpful when you are working with JSON…which is the case with Brightpearl.
Brightpearl also suggests a book from O’Reilly called RESTful Web Services by Leonard Richardson and Sam Ruby. The book was published in 2007, so although it has some useful information, it is somewhat dated. There is nothing about oAuth in it for example.
 
To get started with the Brightpearl API, you have to make sure that your user account is authorized to work with the API. This is done by accessing the “Staff” under Setup, and making sure that there is a green checkmark next to the user’s name in the API access column. 
Get an Authorization Token
Brightpearl requires that you obtain an authorization token prior to accessing any other requests.  The request for the authorization token takes the form of  a POST request  which includes your user name and password in the request payload. The URI of the payload includes two variables,  your brightpearl server location, the name of your BrightPearl account and a Content-Type of text/xml
Content-Type: text/xml
where
use=”US East”
“microdesign”, is the name of your Brightpearl account id
The user name and password are passed as JSON name pairs to the apiAccountCredentials variable:
{

    apiAccountCredentials:{
        emailAddress:”myname@mydomain.com”,
        password:”mypassword”
    }

}
Note that the double quotes enclosing the eMail address and password are also present.
So, if you look at the raw request that is sent, the full request looks like this:
POST https://ws-use.brightpearl.com/microdesign/authorise
Content-Type: text/xml
{
apiAccountCredentials:{
emailAddress:”myname@mydomain.com”,
        password:”mypassword”    }
}
If the request is successful, you’ll receive a hexedicimal number back which is your authorization token.
{“response”:” xxxxxx-xxxxx-xxxxx-xxxxx-xxxx-xxxxxxxxxx”}
Once you have the authorization token, it is used in subsequent requests as a substitute for your user name and password. The token expires after about 30 minutes of inactivity…so you’ll have to issue another authorization request and obtain a new token after that time. 
Once you have gotten the authorization token, you can start making requests. The basic request is a “resource search” which is a query of the Brightpearl data. Resource searches are issued with GET requests, and must include the API version number. The authorization code is sent as a header along with the request. 
 
As a reminder, the authorization request is a POST, and the resource query is a GET.
(More on resource searches in Brightpearl).
GET https://ws-use.brightpearl.com/2.0.0/microdesign/warehouse-service/goods-note/goods-out-search
brightpearl-auth: xxxxxx-xxxxx-xxxxx-xxxxx-xxxx-xxxxxxxxxx
This request returns a list of the current goods-out notes (Brightpearl’s nomenclature for a packing slip or pick-list).
Example with results: 
The folllowing GET request shows the current orders.
brightpearl-auth: xxxxxx-xxxxx-xxxxx-xxxxx-xxxx-xxxxxxxxxx
This returns a list of current orders, in JSON format. The format shows the structure of the data first, and then the actual records.  Note that there are only three orders!
{“response”:{“metaData”:{“resultsAvailable”:3,”resultsReturned”:3,”firstResult”:1,”lastResult”:3,”columns”:[{“name”:”orderId”,”sortable”:true,”filterable”:true,”reportDataType”:”IDSET”,”required”:false},{“name”:”orderTypeId”,”sortable”:true,”filterable”:true,”reportDataType”:”INTEGER”,”referenceData”:[“orderTypeNames”],”required”:false},{“name”:”contactId”,”sortable”:true,”filterable”:true,”reportDataType”:”INTEGER”,”required”:false},{“name”:”orderStatusId”,”sortable”:true,”filterable”:true,”reportDataType”:”INTEGER”,”referenceData”:[“orderStatusNames”],”required”:false},{“name”:”orderStockStatusId”,”sortable”:true,”filterable”:true,”reportDataType”:”INTEGER”,”referenceData”:[“orderStockStatusNames”],”required”:false},{“name”:”createdOn”,”sortable”:true,”filterable”:true,”reportDataType”:”PERIOD”,”required”:false},{“name”:”createdById”,”sortable”:true,”filterable”:true,”reportDataType”:”INTEGER”,”required”:false},{“name”:”customerRef”,”sortable”:true,”filterable”:true,”reportDataType”:”STRING”,”required”:false},{“name”:”orderPaymentStatusId”,”sortable”:true,”filterable”:true,”reportDataType”:”INTEGER”,”referenceData”:[“orderPaymentStatusNames”],”required”:false}],”sorting”:[{“filterable”:{“name”:”orderId”,”sortable”:true,”filterable”:true,”reportDataType”:”IDSET”,”required”:false},”direction”:”ASC”}]},”results”:[[1,1,207,4,3,”2014-09-18T14:15:50.000-04:00″,4,”#1014″,2],[2,1,207,1,3,”2014-09-29T13:20:52.000-04:00″,4,”#1015″,2],[3,1,207,1,3,”2014-09-29T13:25:39.000-04:00″,4,”#1016″,2]]},”reference”:{“orderTypeNames”:{“1″:”SALES_ORDER”},”orderPaymentStatusNames”:{“2″:”PARTIALLY_PAID”},”orderStatusNames”:{“1″:”Draft / Quote”,”4″:”Invoiced”},”orderStockStatusNames”:{“3″:”All fulfilled”}}}
If you use the “Advanced REST Client Application For Chrome, it will decode the above so that it is readable:
{
response:

{
metaData:

{
resultsAvailable3
resultsReturned3
firstResult1
lastResult3
columns:

[

9]

0:  

{
name: “orderId
sortabletrue
filterabletrue
reportDataType: “IDSET
requiredfalse
}
1:  

{
name: “orderTypeId
sortabletrue
filterabletrue
reportDataType: “INTEGER
referenceData:

[

1]

0:  orderTypeNames
requiredfalse
}
2:  

{
name: “contactId
sortabletrue
filterabletrue
reportDataType: “INTEGER
requiredfalse
}
3:  

{
name: “orderStatusId
sortabletrue
filterabletrue
reportDataType: “INTEGER
referenceData:

[

1]

0:  orderStatusNames
requiredfalse
}
4:  

{
name: “orderStockStatusId
sortabletrue
filterabletrue
reportDataType: “INTEGER
referenceData:

[

1]

0:  orderStockStatusNames
requiredfalse
}
5:  

{
name: “createdOn
sortabletrue
filterabletrue
reportDataType: “PERIOD
requiredfalse
}
6:  

{
name: “createdById
sortabletrue
filterabletrue
reportDataType: “INTEGER
requiredfalse
}
7:  

{
name: “customerRef
sortabletrue
filterabletrue
reportDataType: “STRING
requiredfalse
}
8:  

{
name: “orderPaymentStatusId
sortabletrue
filterabletrue
reportDataType: “INTEGER
referenceData:

[

1]

0:  orderPaymentStatusNames
requiredfalse
}
sorting:

[

1]

0:  

{
filterable:

{
name: “orderId
sortabletrue
filterabletrue
reportDataType: “IDSET
requiredfalse
}
direction: “ASC
}
}
results:

[

3]

0:  

[

9]

0:  1
1:  1
2:  207
3:  4
4:  3
5:  2014-09-18T14:15:50.000-04:00
6:  4
7:  #1014
8:  2
1:  

[

9]

0:  2
1:  1
2:  207
3:  1
4:  3
5:  2014-09-29T13:20:52.000-04:00
6:  4
7:  #1015
8:  2
2:  

[

9]

0:  3
1:  1
2:  207
3:  1
4:  3
5:  2014-09-29T13:25:39.000-04:00
6:  4
7:  #1016
8:  2
}
reference:

{
orderTypeNames:

{
1: “SALES_ORDER
}
orderPaymentStatusNames:

{
2: “PARTIALLY_PAID
}
orderStatusNames:

{
1: “Draft / Quote
4: “Invoiced
}
orderStockStatusNames:

{
3: “All fulfilled
}
}
}

A Database for Grant Research

I put together a grants database screen (click to view full size) to consolidate information for funding sources, and to track dates and interactions.

It is definitely an evolving project, but contains the basic information need to contact the funder, the deadline dates involved, the funder’s areas of interest, and the typical range of a grant award.

So far, I’ve been concentrating on foundation funding. Many foundations typically ask for a letter of interest before you put together a full proposal. So, I’ve included multiple date fields, a deadline for a letter of interest, a deadline for a full proposal, and a date when they announce their award.

Originally I thought that this database would be mostly for research, but after working with the online grants database, Grantstation, I think I will reserve this database for funders that I really expect to submit to. Some ideas for future enhancements include:

  • Links to standard “boilerplate” paragraphs that are used in an application. 
  • Links to edit the proposal or letter directly in Word. 
  • Links to the PDFs of the proposal. 
  • Reports that create a grants calendar. 
Before anyone comments that “you should really use X software” for this purpose, I just want to say that I’ve used several in the past, including DonorPerfect and Blackbaud, and evaluated many others. Right now, I’m in the process of rethinking my entire workflow automation from the ground up, and this very lightweight approach is just what I’m looking for. Plus its in FileMaker, so I can run it on my Windows machines at work, or my Macs at home.