Programatic approach to building a subscription mailing list​
​by Camila Cruz
This shows one way you could build and maintain an email subscription list with Wolfram Cloud. This example works for creating a mailing list of any topic and it’s open to personalized improvements for multiple kinds of subscriber lists.

User Experience (UX) Workflow

This describes what the users do.
​
Roles:
■ Owner - user who created and maintains the list, and sends emails to it
■ Invited user - user that has been invited to be on the list but hasn’t joined yet
■ Subscribed user - user that has responded positively to the invitation and receives emails
◼
  • the owner of the list calls a function to create a new list
  • ◼
  • the owner invites a subscriber by calling a function to send email with an subscription link
  • ◼
  • an invited user clicks the subscription link to “claim it” and become active (this will show a welcome message, and include an unsubscribe link)
  • ◼
  • to send to the subscription list, the owner calls a function with subject and (template can be used for HTML, or plain text where we append things), the sent email has an unsubscribe link
  • ◼
  • the owner can call a function to delete the list
  • WL Function interface

    Create the email list:
    emailListCreate[name]
    Send an invitation email:
    emailListInvite[listobj,"alice@example.com"]
    Send an email to the list:
    emailListSend[listobj,"subject","body"]
    Send an email to the list using a template:
    emailListSend[listobj,"subject",StringTemplate["hi `1`"]]
    List the members of the list:
    emailListMembers[listobj]
    Note: think about listing invited users vs. confirmed subscribers vs. all
    Delete the list:
    emailListDelete[listobj]

    Cloud Object Design (Schema)

    Cloud object layout:
    ​
    + directory for root of list
    |- confirmed subscribers
    |- invited users
    |- API endpoints
    ◼
  • contents of user object (invited and subscribed)
  • <|​​"Email"->"alice@example.com",​​"Code"->"3deh3"​​|>
    ◼
  • APIFunctions that we need:
  • ◼
  • subscribe
  • change the state from invited to subscribed of user, move them to the confirmed subscribers directory
    ◼
  • unsubscribe (unsubscribe link points to)
  • remove user from the subscribers list

    Core code example

    (*Createanewlist:*)​​emailListCreate[name_String]:=​​Module[{dirobj},​​(*createthedirectoryholdingthewholelist*)​​dirobj=Quiet[CreateDirectory[CloudObject[name]]];​​​​(*createAPIendpoints*)​​CloudPublish[APIFunction[{"email"->"EmailAddress","verifier"->String},emailListSubscribe[dirobj,#email,#verifier]&,"Text"],subscribeAPIObj[dirobj]];​​​​(*createunsubscribeAPI*)​​CloudPublish[APIFunction[{"email"->"EmailAddress","verifier"->String},emailListUnsubscribe[dirobj,#email,#verifier]&,"Text"],unsubscribeAPIObj[dirobj]];​​​​(*createdatadirectories*)​​Quiet[CreateDirectory[subscribersDir[dirobj]],CreateDirectory::filex];​​Quiet[CreateDirectory[invitedDir[dirobj]],CreateDirectory::filex];​​dirobj​​]​​​​(*Confirmedsubscribersdirectory*)​​subscribersDir[listObj_CloudObject]:=​​FileNameJoin[{listObj,"subscribers"}]​​​​(*Subscribedusercloudobject*)​​subscribedUserObj[listObj_CloudObject,email_]:=​​Module[{userMail},​​userMail=StringSplit[email,{"@","."}];​​FileNameJoin[{listObj,"subscribers",StringJoin[userMail]<>".wl"}]​​]​​​​(*Invitedusersdirectory*)​​invitedDir[listObj_CloudObject]:=​​FileNameJoin[{listObj,"invited"}]​​​​(*Invitedusercloudobject*)​​invitedUserObj[listObj_CloudObject,email_]:=​​Module[{userMail},​​userMail=StringSplit[email,{"@","."}];​​FileNameJoin[{listObj,"invited",StringJoin[userMail]<>".wl"}]​​]​​​​(*SubscribeAPIobject*)​​subscribeAPIObj[listObj_CloudObject]:=​​FileNameJoin[{listObj,"subscribe"}]​​​​(*UnsubscribeAPIobject*)​​unsubscribeAPIObj[listObj_CloudObject]:=​​FileNameJoin[{listObj,"unsubscribe"}]​​​​(*Returnsifamemberisinthelist.*)​​listMemberQ[listObj_CloudObject,userMail_String]:=​​FileExistsQ[FileNameJoin[{listObj,userMail<>".wl"}]]​​​​(*Createsaninviteduserobject*)​​createInvitedUser[listObj_,email_String]:=​​Module[{userMail,userData,userObj},​​(*userMailseparatestheuseremailfromthe@symbolsoitcanbeavalidpathfortheuserObj.*)​​userMail=StringSplit[email,{"@","."}];​​If[listMemberQ[invitedDir[listObj],StringJoin[userMail]]=!=True,​​userData=<|​​ "Email"->email,​​ "Code"->generateVerificationCode[]​​|>;​​userObj=CloudPut[userData,invitedUserObj[listObj,email]];​​userData​​]​​]​​(*Generatesacodeforeachinviteduser.*)​​generateVerificationCode[]:=IntegerString[RandomInteger[10^9],36]​​​​(**Sendaninvitationemail:​​*ThisisanemailthatsendsalinktoconfirmtheirsubscriptionwithanAPIfunction​​*)​​emailListInvite[list_String,email_String]:=​​emailListInvite[CloudObject[list],email]​​​​emailListInvite[listobj_CloudObject,email_String]:=​​Module[{inviteObj,subscribeURL,userData},​​userData=createInvitedUser[listobj,email];​​subscribeURL=URLBuild[First[subscribeAPIObj[listobj]],{"email"->email,"verifier"->userData["Code"]}];​​SendMail[​​<|​​ "To"->email,​​ "Subject"->"Invitation to our subscribers list",​​ "Body"->{"Here's the invitation ",subscribeURL}​​|>​​];​​]​​​​(*Completelyremovetheemaillist:*)​​emailListDelete[listobj_]:=​​ DeleteObject[CloudObject[listobj]]​​​​(*Functiontoaddusertosubscriberslist*)​​emailListSubscribe[listObj_CloudObject,email_String,code_]:=​​Module[{userData,unsubscribeURL},​​userData=CloudGet[invitedUserObj[listObj,email]];​​unsubscribeURL=emailListUnsubscribeAPIURL[listObj,userData];​​(*ifobjectwasn'tfound,thentheuserwasneverinvited*)​​If[AssociationQ[userData],​​Print["userData: ",InputForm[userData]];​​(*IfcomparingtheuserData"Code"toquecodegiven*)​​If[code===userData["Code"],​​RenameFile[invitedUserObj[listObj,email],subscribedUserObj[listObj,email]];​​"Welcome, "<>email<>"\n\n Unsubscribe from the list: "<>unsubscribeURL,​​(*Elsethecodesdon'tmatch*)​​"Something went wrong."​​],​​(*userwasneversubscribed*)​​"Something went wrong."​​]​​]​​​​(*UnsubscribeAPIURL*)​​emailListUnsubscribeAPIURL[listObj_CloudObject,userData_?AssociationQ]:=​​URLBuild[First[unsubscribeAPIObj[listObj]],{"email"->email,"verifier"->userData["Code"]}]​​​​(*Listssubscribercloudobjects*)​​emailListSubscriberObjects[listObj_]:=​​CloudObjects[subscribersDir[listObj]]​​​​(*Listssubscribersdata*)​​emailListSubscribersData[listObj_CloudObject]:=​​Map[CloudGet,emailListSubscriberObjects[listObj]]​​​​(*Listssubscriberemails*)​​emailListMembers[listObj_CloudObject]:=​​Map[Key["Email"],emailListSubscribersData[listObj]]​​​​(*Sendsemailtosubscriberslist*)​​emailListSend[listObj_CloudObject,subject_String,body_String]:=​​(*TODOwouldbenicetowrapthisinMonitorwithaProgressIndicator*)​​Do[​​SendMail[​​<|​​ "To"->subscriber["Email"],​​ "Subject"->subject,​​ "Body"->StringTemplate["`1`\n\nUnsubscribe: `2`"][body,emailListUnsubscribeAPIURL[listObj,subscriber]]​​|>​​],​​{subscriber,emailListSubscribersData[listObj]}​​]​​​​(*Listsemailsofsubscribedusers*)​​emailListGetSubscriberEmail[listobj_CloudObject]:=​​Module[{subscribers},​​subscribers=Map[Get,emailListSubscriberObjects[listObj]];​​Lookup[subscribers,"Email"]​​]​​​​(*Functiontoremoveuserfromsubscriberslist*)​​emailListUnsubscribe[listObj_CloudObject,email_String,code_]:=​​Module[{userData},​​userData=CloudGet[subscribedUserObj[listObj,email]];​​(*ifobjectwasn'tfound,thentheuserwasnotsubscribed*)​​If[AssociationQ[userData],​​(*IfcomparingtheuserData"Code"toquecodegiven*)​​If[code===userData["Code"],​​DeleteFile[subscribedUserObj[listObj,email]];​​"Bye, "<>email,​​(*Elsethecodesdon'tmatch*)​​"Something went wrong."​​],​​(*userwasneversubscribed*)​​"Something went wrong."​​]​​]

    Example usage