Download & Extend

Subscriber API: separate subscribers from newsletter management and allow other modules to define subscribers

Project:Simplenews
Version:7.x-1.x-dev
Component:Code
Category:task
Priority:normal
Assigned:Unassigned
Status:closed (fixed)

Issue Summary

Just been discussing this on IRC with sutharsan :)
I'm creating this issue so we've got somewhere to collect use cases and discuss how best to do this.

The main idea I think is to split the part of simplenews that collects and manages subscriptions into a separate module.
An API link between what remains of simplenews core and simplenews_subs would then be open to other modules in some way.

Potential interested parties:
- views: define subscribers from a view
- event registration (not a module yet, just vague plans: see http://groups.drupal.org/node/23834 )
- Ubercart - there's big potential for marketing campaigns based on who bought what

Of course, we could say that any module worth its salt should support views, and so provided that a view can be created of what the module wants, then all simplenews needs to do is support views :)

Comments

#1

Hello. What's the latest on this issue?
I'm looking for a way to add a "sign up to our newsletter" option to the ubercart checkout process.
This simplenews integration would be the ideal solution for me.

#2

I second that. Each new user who creates a new account via the checkout process in ubercart doesn't receive the default "opt-out" setting. It would be great if they had such pane in the checkout.

#3

As a preparation for a subscription API I've scanned simplenews module for subscription related database access and extracted the required functionality.

simplenews_taxonomy() op:delete
// Delete all subscriptions of given tid (delete the whole subscription list).

simplenews_user() op:insert
// Check if a subscriber exist of given email addres.
// alternative: Get subscriber object of given email
// Update UID and Language of given subscriber ID

simplenews_user() op:'update':
// Does a subscriber exist of given UID?
// alternative: Get subscriber object of given uid (alternative: given email)
// Update mail and language of given subscriber ID

// Get subscriber ID with given email addres
// Update mail and language of given subscriber ID

// Activate/de-activate subscriber with given uid

simplenews_user() op:'delete'
// Get subscriber ID of given email addres
// Delete all subscriptions of given subscriber ID
// Delete subscriber of given subscriber ID
// Update UID of subscriber of given UID

simplenews_user() op:'view'
// Get the number of active subscriptions a given UID has)

simplenews_subscribe_user()
// Create subscriber of given email, UID, language, subscriber status. Return the subscriber object
// Create additional subscription of given subscriber ID to newsletter TID using timestamp and subscriptions source.
// Create first subscription of given subscriber ID to newsletter TID using timestamp and subscriptions source.

simplenews_unsubscribe_user()
// Unsubscribe given subscriber ID of netwsletter TID using timestamp and subscription source.

simplenews_user_is_subscribed()
// Check if given email is subscribed to given newsletter TID

simplenews_get_subscription()
// Get subscription object of subscriber with given subscriber ID, email or UID.

simplenews_delete_subscription()
// Get subscriber object using given subscriber ID
// Delete subscriber data of given subscriber ID
// Delete subscription data of given subscriber ID

simplenews_send_node()
// Get email address of all accounts active and subscribed to given newsletter TID.

simplenews_send_status_update()
// Get newsletter object of all newsletter with given sent status.
// Update sent status of given newsletter NID and VID

simplenews_confirm_subscription()
// Get subscriber ID and email of given subscriber ID
// alternative: get subscriber object of given subscriber ID.

simplenews_activate_subscription()
// Activate subscriber of given subscriber ID (multiple)

simplenews_inactivate_subscription()
// De-activate subscriber of given subscriber ID (multiple)

simplenews_count_subscriptions()
// Get number of subscribers who are subscribed to given newsletter TID

simplenews_build_subscription_filter_query()
// Filter query, perhaps too specific.

simplenews_admin_export_after_build()
// Get subscriber mail of given newsletter TID and variable where clause

simplenews_subscription_multiple_delete_confirm()
// Get subscriber mail of given subscriber ID

simplenews_admin_news()
// Get node object, sent status and newsletter TID of variable where clause.
// Count nodes of variable where clause.

simplenews_send_newsletter_action()
// Get newsletter sent status of given nid.
// Set newsletter sent status to 'pending' of given nid.

simplenews_unsubscribe_user_action()
// Check if subscription exists of given email.
// Unsubscribe given subscriber ID and newsletter TID using time stamp and unsubscription source.

This raw data should now be combined, abstracted and converted to API function definitions.

#4

Just to say -- I have a checkout of HEAD on my laptop and a copy of the above comment, just waiting for a long train journey when I've not got other work on!

#5

subscribe.

hope to do some work on it

#6

Just as a reminder: i think it would be perfect to make a generic subscriberAPI to use also with other modules, like this:
http://drupal.org/project/subscriptions

#7

Interesting...

In a way, Simplenews subscriptions are just subscriptions to a taxonomy term.

So on the one side we have Simplenews that wants to say 'Who is interested in this taxonomy term? I want to send them something.'
Here we want to generalize that so the 'who' can be answered by different modules.
On the other hand, Subscriptions module is a system for users to say 'I care about this taxonomy term, tell me when there is new content'.

It does seem like there's potential for joining things up, tantalizingly so. And yet I suspect that we might not be able to turn off the subscriptions module's system for nodes (which are meaningless to us), or the notifications (we don't want email being sent saying 'there is a new node here'). We'd want the UI changing subtly too (we don't want 'subscribe to receive updates about term X').
But worth investigating.

#8

I gave a look to http://drupal.org/project/subscribed

It will do what we need, but it's only for D5
And i don't like so much code in there....

I'd like to give a try to write something interesting. I'll gave information in this thread.

#9

Some notes, only as starter.

I'd like to define some name, so it's simpler later to talk :-)

Definitions

Let's call this new module subscription_api
A user will subscribe to a "pubblication".
Each module using subscription_api will define own pubblication.
Each module is responsible to send pubblication.
Each module can filter subscriber using views2 with parameters.
Each module havo to implement some hook. Call this module "hook implementer"

How to harvest new subscriptions?

subscription_api will manage some block, one for each hook implementer.
The hook implementer, on some hook request, will return his name, his block configure form (ala simplenews), his content on show
On block show and subsequentially form submit, subscription_api will link the user (uid or email) to the pubblication chosen.

How to send pubblication?

Hook implementor will ask subscription_api for a list of subscriber for a pubblication.
We need the possibility to filter subscriber using views. We have to decide/understand if filtering is done by subscription_api or by hook implementor.
If is up to subscription_api to filter, hook implementor needs to define parameters to pass to views in some manner, via GUI
E.g. think ubercart + simplenews: i need the ability to filter simplenews subscribers based on: number of order placed, sum of all orders, type of product buyedm and so on
So i have to define those filters outside the view definition and inside simplenews node creation (or in taxonomy's term associated)

#10

That's not the same direction of abstraction as I was envisioning we take here.

What I'd like to have is Simplenews able to get lists of 'subscribers' from other sources than a subscription-type model.

So, for instance, newsletter X is in fact sent to everyone who bought a product from the site in the last 6 months -- because Simplenews knows that particular newsletter is tied to the Ubercart hook, and Ubercart supplies a list of addresses.

So what we are looking at here is a connection from Simplenews to SubscriberAPI which is open at both ends -- Simplenews can ask other modules to tell it who gets a newsletter, and SubscriberAPI can subscribe users to other things than newsletters.

#11

Joachim, i was thinking this kind of abstraction because in italy i have to ask a user if he wants to receive "adv" emails.
So, the subscriber system is ok for me.

But i like so much your idea:

Simplenews can ask other modules to tell it who gets a newsletter, and SubscriberAPI can subscribe users to other things than newsletters.

#12

Status:active» needs review

Here is a very rough and very little patch on HEAD.
Just to show what I'm thinking about and to get the ball rolling :)

I figure it is easier to make the API happen first, and then pick out all the functions into another module once we're satisfied we've got it right.
Also this means it's easy to see what it's doing and think about the implications and the possibilities; what other modules might need, etc.

So for now, all I have done is split simplenews_send_node(). This now invokes a hook to see who should get the newsletter. The code that gets the list of subscribers has been moved to our implementation of that hook.
So really, all that's changed is a slightly complicated way of making a function call ;)

The list of modules to invoke the hook on is hardcoded for now -- a subsequent step is to store this per newsletter and add a UI for choosing which newsletters do what.
And then this opens up other modules that can implement the hook and provide a list of email addresses that should get a node send to them.

So, todo:
- is this what we want? does it work for the various use cases and needs? could it work with subscription_api?
- have I missed other places where lists of recipients are retrieved from the database?
- change data storage of {simplenews_newsletters} to store list of concerned modules
- add a UI; add default
- split off all subscription stuff into a submodule (including stuff the hook_schema, update functions, etc etc) and see about a subscription_api connection

AttachmentSizeStatusTest resultOperations
536620.simplenews.recipientAPI-rough-1.patch3.03 KBIgnored: Check issue status.NoneNone

#13

I was about to submit a feature request for a hook_simplenews_recipients_alter() hook. Then I saw how similar the hook in your patch is to what I had in mind.

I do have a question, though. Why restrict the modules on which you invoke your hook_simplenews_recipients() hook? Each module can look at the node itself to decide whether it wants to return any recipients.

#14

I guess it's a conceptual thing, and a UI thing.

Method A: (this is what I had in mind)
Edit a newsletter settings and you see a list of checkboxes for the modules that offer to supply subscribers. Eg simplenews_roles, hypothetically simplenews_ubercart, simplenews_webform, etc -- or just the regular simplenews subscribe form.

Method B:
It's up to each of these hypothetical modules to provide a UI and for the user to select which newsletters that module should affect.
So eg, you'd go into Ubercart and a particular product could be edited to say 'Users who buy this receive these newsletters:' and you'd tick the newsletters.

I was thinking of method A because that keeps the newsletter settings together, and also conceptually in my mind a newsletter would *BE* the newsletter for a role / the newsletter for a product category / the newsletter for people who submitted a webform.
But maybe that is less drupal-ish that what you suggest? I will ponder :)

#15

Version:6.x-1.x-dev» 6.x-2.x-dev

I would suggest this:
1. Simplenews exposes a list of available newsletters. Including ID, name, and other relevant data.
2. Modules implement hook_simplenews_recipients() where they add their recipients to one or more of the available newsletters.
3. hook_simplenews_recipients is called with the newsletter ID (Which is now $tid, but may change in the future).

Changing version. This will be added to the 2.x branch or HEAD.

#16

Status:needs review» needs work

#17

To send a newsletter, it is not sufficient to collect email addresses. Simplenews also requires subscriber data; simplenews uses simplenews_get_subsciption(). Currently preferred language and username are used. A hook_simplenews_subscriber_load() should is called by simplenews_get_subscription()

#18

Would be nice to see implemented. Subscribing.

#19

I can add two more methods:
C: using a simplenews API modules add users to a simplenews subcription list.
D: simplenews provides an interface where other target lists can be assigned to a newsletter. E.g. A view of all users with a role, a view of all subscribers to list X who have subscribed after date Y.

#20

Ok, I've just been thinking all this over.

It seems to me that if we go for Method A, Simplenews would have to provide a way for a module to plug in a settings form when you enable it for a newsletter. If you enable the module for a newsletter in one place, the place where you tell the module what you want it do with that newsletter shouldn't be very far.

I like Method B, because it means that you'd naturally be doing both in one place - telling the module to work with that newsletter, and telling it what to do with it. Simplenews would also be much..simpler. I don't think this would really have to fragment newsletter settings all over the place. Right now, Simplenews Roles voluntarily injects its settings into the newsletter edit form without being forced to do anything. Module developers would just have to be intelligent in putting their settings wherever they make sense.

I have a question about Method C. Are you saying here that modules would have to provide actual users (people with accounts) in this case - meaning they can't add anonymous users? It sounds like a form of Method B.

Method D doesn't sound like an api at all. Would other modules be able to plug into it? Don't get me wrong, it does sound useful...

Re: comment #17:
I looked at how simplenews_get_subscription() is used inside Simplenews, and it looks to me like its real purpose is managing simplenews's own inbuilt subscriptions. Making it work with subscription lists from other modules could cause bugs, I think. As far as I can tell, the only time we want it to work with subscriptions handled by other modules is when it's called from within simplenews_mail_mail(), which actually sends the newsletters out. It needs to call it because all it's given about the recipient is the email address, and it uses simplenews_get_subscription() to get the other information it needs.

I think what should happen is the hook that gets recipients should get either objects or arrays that contains all the information needed (uid if available, name, email address, language, etc), and not just email addresses. Then that information should be saved into the spool table (php's serialize function can be handy here). The simplenews_mail_spool() table can then load it back out and pass it into simplenews_mail_mail(). The only other function that calls simplenews_mail_mail() is simplenews_send_test(), and it can just use defaults and pass them in.

It looks to me like that'd remove any need for simplenews_get_subscription() to work with other modules' subscription/recipient data.

#21

I've been working on upgrade to D7 with the above comments in mind. My proposal is to use a hook_simplenews_recipients() to collect subscriber data. This hook is called after a newsletter is send by the admin and before the newsletter content is build. i.e. in simplenews_send_node() and perhaps also in simplenews_send_test(). The collected data is stored in simplenews_spool table for later use, as darktygur suggested, or used immediately in simplenews_send_test().

In the code below $scid is the replacement of $tid because taxonomy is now removed from simplenews. A newsletter category is the collection of newsletters as in 'Sports news' or 'Political news'.
Special precautions are needed to handle unsubscribed users. In the code below a simplenews_array_merge() function is used to only but only if the initial recipient is not marked as unsubscribed.

<?php
/**
* Collect recipients for a simplenews newsletter.
*
* Get both subscribed and unusbscribed recipients per newsletter category.
* Special care should be taken to handle unsubscribed users correctly.
*
* @param $recipients
*   Array of recipient objects with the following as minimum.
*   $recipients['mail@example.com']
*     recipient->mail      Email address
*     recipient->status    Status flag (1, 0)
*                          1 = may receive email, is subscribed;
*                          0 = should not receive email, is unsubscribed.
* @param $scid
*   Newsletter category ID.
*/
function hook_simplenews_recipients(&$recipients, $scid) {
 
$recipients = simplenews_array_merge($recipients, simplenews_get_subscriptions_by_category($scid), 'simplenews_check_status');
}
?>

This API should allow a module like simplenews_roles to add its recipients.

Is does not include an user interface, modules which implement hook_simplenews_recipients() should take care of their interface. For the future a centralized interface might be build to add groups of recipients to a newsletter category.

Some background info on Simplenews D7 terminology:

Newsletter
A node, to be sent as email
Newsletter category
A collection of newsletters. Each newsletter can be tagged with one (optionally multiple) categories.
Mailing list
Where people can subscibe to. Usually the collection of addresses the newsletters of one category get sent to.
Subscriber
Someone with an email address that is (or was) subscribed to a mailing list.
Subscription
The subscription of a subscriber to a mailing list

#22

Version:6.x-2.x-dev» 7.x-1.x-dev
Status:needs work» needs review

I've committed code for this API in 7.x-1.x-dev so you can see it in action.
Slight change to the above: $recipients->language is now also required.

#23

subscribing...

#24

Subscribing...

#25

Status:needs review» fixed

@Sutharsan, 1 year without any comment, I think the review is ok, don't you think ?

#26

Status:fixed» closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

nobody click here