Making a Custom Post type only visible to non-users via a specific link

I think what you want is a custom rewrite rule — specifically, a custom endpoint.

This would have to live outside s2member.

To start, wrap everything in a class:

<?php
class WPSE71804
{
    // post type key, whatever this happens to be.
    const TYPE = 'customer';

    // endpoint mask, 2 ^ 18
    const EP = 262144;

    // key prefix, used for options
    const PREFIX = 'wpse71804_key_';

    // container for the instance of this class
    private static $ins = null;

    public static function instance()
    {
        is_null(self::$ins) && self::$ins = new self;
        return self::$ins;
    }

    public static function init()
    {
        add_action('plugins_loaded', array(self::instance(), '_setup'));
    }

    // add actions and such.
    public function _setup()
    {
       // we'll add actions here later.
    }
}

There are some constants there that we’ll use later.

You’ll need to modify your post type registration to include a custom endpoint mask in the rewrite argument.

<?php
class WPSE71804
{
    // snip snip

    // add actions and such.
    public function _setup()
    {
        add_action('init', array($this, 'register'));
    }

    // register the post type
    public function register()
    {
        // rewrite is the args to pay attention to we need 
        // to set a custom endpoint mask
        register_post_type(self::TYPE, array(
            'label'     => __('Customers', 'wpse'),
            'public'    => true,
            'rewrite'   => array(
                'slug'          => 'customer',
                'ep_mask'       => self::EP,
                'with_front'    => false,
            ),
        ));
    }
}

From there, we can hook into init and call add_rewrite_endpoint.

This sets up a rewrite so we can go to yoursite.com/customers/the-post/key/some_key_here.

<?php
class WPSE71804
{
    // snip snip

    // add actions and such.
    public function _setup()
    {
        add_action('init', array($this, 'register'));
        add_action('init', array($this, 'endpoint'), 11);
    }

    // snip snip

    public function endpoint()
    {
        add_rewrite_endpoint('key', self::EP);
    }
}

Now it’s just a matter of hooking into template_redirect and validating the key.

<?php
class WPSE71804
{
    // snip snip

    public static function init()
    {
        add_action('plugins_loaded', array(self::instance(), '_setup'));
        register_activation_hook(__FILE__, array(__CLASS__, 'activate'));
    }

    // add actions and such.
    public function _setup()
    {
        add_action('init', array($this, 'register'));
        add_action('init', array($this, 'endpoint'), 11);
        add_action('template_redirect', array($this, 'validate_key'));
    }

    // snip snip

    public function validate_key()
    {
        // not a a singular customer page? Or have an admin user? bail.
        if(!is_singular(self::TYPE) || current_user_can('manage_options'))
            return;

        if(!($_key = get_query_var('key')) || !($key = self::get_key($_key)))
        {
            global $wp_query;
            $wp_query->set_404();
        }

        // if we're here, the key is okay, let the request go through
    }
}

It might also be helpful to create a nice API to use (the above bit of code uses one of those methods).

<?php
class WPSE71804
{
    // snip snip

    /********** API **********/

    // create a new key
    public static function create_key()
    {
        $k = wp_generate_password(24, false);
        self::update_key($k, 'notdone');
        return $k;
    }

    // update a key
    public static function update_key($key, $val="done")
    {
        return update_option(self::PREFIX . $key, $val);
    }

    // delete a key
    public static function delete_key($key)
    {
        return delete_option(self::PREFIX . $key);
    }

    public static function get_key($key)
    {
        return get_option(self::PREFIX . $key);
    }
}

Now you can use the above something like…

<?php
// create a key
$k = WPSE71804::create_key();

// send mail with key here!

// after they submit the survey, you might want to make a note of that.
WPSE71804::update_key($k, 'done');

// or maybe just delete it and revoke access to the page
WPSE71804::delete_key($k);

Not sure how well that will play along with s2member, but essentially this will block all access to the pages without a key on the front end. You may not need to restrict access with s2member at all. Here is all that as a plugin.