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
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
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
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 Design (Schema)
Cloud object layout:
+ directory for root of list
|- confirmed subscribers
|- invited users
|- API endpoints
+ 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
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
Example usage