123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332openLwt.SyntaxmoduleCore=Sihl_coremoduleDatabase=Sihl_databasemoduleUtils=Sihl_utilsmoduleQueue=Sihl_queue_coremoduleSig=Sihl.Email.SigmoduleTemplate=Sihl_email_templateletlog_src=Logs.Src.create"sihl.service.http"moduleLogs=(valLogs.src_loglog_src:Logs.LOG)letprintemail=letsender=Sihl.Email.senderemailinletrecipient=Sihl.Email.recipientemailinletsubject=Sihl.Email.subjectemailinlettext_content=Sihl.Email.text_contentemailinlethtml_content=Sihl.Email.html_contentemailinprint_endline@@Printf.sprintf{|
-----------------------
Email sent by: %s
Recpient: %s
Subject: %s
-----------------------
Text:
%s
-----------------------
Html:
%s
-----------------------
|}senderrecipientsubjecttext_contenthtml_content;;letinterceptsenderemail=letis_testing=Sihl.Core.Configuration.is_testing()inletbypass=Option.value~default:false(Sihl.Core.Configuration.read_bool"EMAIL_BYPASS_INTERCEPT")inletconsole=Option.value~default:is_testing(Sihl.Core.Configuration.read_bool"EMAIL_CONSOLE")inlet()=ifconsolethenprintemailelse()inmatchis_testing,bypasswith|true,true->senderemail|true,false->Lwt.return(Sihl.Email.add_to_inboxemail)|false,true->senderemail|false,false->senderemail;;moduleMakeSmtp(TemplateService:Sig.TEMPLATE_SERVICE):Sig.SERVICE=structmoduleTemplate=TemplateServicetypeconfig={sender:string;username:string;password:string;hostname:string;port:intoption;start_tls:bool;ca_path:stringoption;ca_cert:stringoption;console:booloption}letconfigsenderusernamepasswordhostnameportstart_tlsca_pathca_certconsole={sender;username;password;hostname;port;start_tls;ca_path;ca_cert;console};;letschema=letopenConformistinmake[string"SMTP_SENDER";string"SMTP_USERNAME";string"SMTP_PASSWORD";string"SMTP_HOST";optional(int~default:587"SMTP_PORT");bool"SMTP_START_TLS";optional(string~default:"/etc/ssl/certs""SMTP_CA_PATH");optional(string~default:"""SMTP_CA_CERT");optional(bool~default:false"EMAIL_CONSOLE")]config;;letsend'(email:Sihl.Email.t)=letrecipients=List.concat[[Letters.Toemail.recipient];List.map(funaddress->Letters.Ccaddress)email.cc;List.map(funaddress->Letters.Bccaddress)email.bcc]inletbody=matchemail.htmlwith|true->Letters.Htmlemail.html_content|false->Letters.Plainemail.text_contentinletsender=(Core.Configuration.readschema).senderinletusername=(Core.Configuration.readschema).usernameinletpassword=(Core.Configuration.readschema).passwordinlethostname=(Core.Configuration.readschema).hostnameinletport=(Core.Configuration.readschema).portinletwith_starttls=(Core.Configuration.readschema).start_tlsinletca_path=(Core.Configuration.readschema).ca_pathinletca_cert=(Core.Configuration.readschema).ca_certinletconfig=Letters.Config.make~username~password~hostname~with_starttls|>Letters.Config.set_portport|>funconf->matchca_cert,ca_pathwith|Somepath,_->Letters.Config.set_ca_certpathconf|None,Somepath->Letters.Config.set_ca_pathpathconf|None,None->confinLetters.build_email~from:email.sender~recipients~subject:email.subject~body|>function|Okmessage->Letters.send~config~sender~recipients~message|Errormsg->raise(Sihl.Email.Exceptionmsg);;letsendemail=let*email=TemplateService.renderemailininterceptsend'email;;letbulk_send_=Lwt.return()letname="email"letstart()=Lwt.return()letstop_=Lwt.return()letlifecycle=Core.Container.Lifecycle.createname~dependencies:[TemplateService.lifecycle]~start~stop;;letregister()=Core.Container.Service.createlifecycleendmoduleMakeSendGrid(TemplateService:Sig.TEMPLATE_SERVICE):Sig.SERVICE=structmoduleTemplate=TemplateServiceletbody~recipient~subject~sender~content=Printf.sprintf{|
{
"personalizations": [
{
"to": [
{
"email": "%s"
}
],
"subject": "%s"
}
],
"from": {
"email": "%s"
},
"content": [
{
"type": "text/plain",
"value": "%s"
}
]
}
|}recipientsubjectsendercontent;;letsendgrid_send_url="https://api.sendgrid.com/v3/mail/send"|>Uri.of_stringtypeconfig={api_key:string;console:booloption}letconfigapi_keyconsole={api_key;console}letschema=letopenConformistinmake[string"SENDGRID_API_KEY";optional(bool"EMAIL_CONSOLE")]config;;letsend'email=lettoken=(Sihl.Core.Configuration.readschema).api_keyinletheaders=Cohttp.Header.of_list["authorization","Bearer "^token;"content-type","application/json"]inletsender=Sihl.Email.senderemailinletrecipient=Sihl.Email.recipientemailinletsubject=Sihl.Email.subjectemailinlettext_content=Sihl.Email.text_contentemailin(* TODO support html content *)(* let html_content = Sihl.Email.text_content email in *)letreq_body=body~recipient~subject~sender~content:text_contentinlet*resp,resp_body=Cohttp_lwt_unix.Client.post~body:(Cohttp_lwt.Body.of_stringreq_body)~headerssendgrid_send_urlinletstatus=Cohttp.Response.statusresp|>Cohttp.Code.code_of_statusinmatchstatuswith|200|202->Logs.info(funm->m"EMAIL: Successfully sent email using sendgrid");Lwt.return()|_->let*body=Cohttp_lwt.Body.to_stringresp_bodyinLogs.err(funm->m"EMAIL: Sending email using sendgrid failed with http status %i and body %s"statusbody);raise(Sihl.Email.Exception"EMAIL: Failed to send email");;letsendemail=let*email=TemplateService.renderemailininterceptsend'email;;letbulk_send_=Lwt.return()letstart()=Lwt.return()letstop()=Lwt.return()letlifecycle=Core.Container.Lifecycle.create"email"~dependencies:[TemplateService.lifecycle]~start~stop;;letregister()=Core.Container.Service.createlifecycleend(* Use this functor to create an email service that sends emails using the job queue. This
is useful if you need to answer a request quickly while sending the email in the
background *)moduleMakeQueued(EmailService:Sig.SERVICE)(QueueService:Sihl.Queue.Sig.SERVICE):Sig.SERVICE=structmoduleTemplate=EmailService.TemplatemoduleJob=structletinput_to_stringemail=email|>Sihl.Email.to_yojson|>Yojson.Safe.to_string|>Option.some;;letstring_to_inputemail=matchemailwith|None->Logs.err(funm->m"DELAYED_EMAIL: Serialized email string was NULL, can not deserialize \
email. Please fix the string manually and reset the job instance.");Error"Invalid serialized email string received"|Someemail->Result.bind(email|>Utils.Json.parse)Sihl.Email.of_yojson;;lethandle~input=EmailService.sendinput|>Lwt.mapResult.ok(** Nothing to clean up, sending emails is a side effect *)letfailed_=Lwt_result.return()letjob=Queue.create_job~name:"send_email"~input_to_string~string_to_input~handle~failed()|>Queue.set_max_tries10|>Queue.set_retry_delayUtils.Time.OneHour;;endletsendemail=(* skip queue when running tests *)ifSihl.Configuration.is_testing()then(Logs.debug(funm->m"Skipping queue for email sending");EmailService.sendemail)elseQueueService.dispatch~job:Job.jobemail;;letbulk_sendemails=(* TODO [jerben] Implement queue API for multiple jobs so we don't have to use
transactions here *)letrecloopemails=matchemailswith|email::emails->Lwt.bind(sendemail)(fun()->loopemails)|[]->Lwt.return()inloopemails;;letstart()=QueueService.register_jobs~jobs:[Job.job]|>Lwt.mapignoreletstop_=Lwt.return()letlifecycle=Core.Container.Lifecycle.create"delayed-email"~start~stop~dependencies:[EmailService.lifecycle;Sihl.Database.Service.lifecycle;QueueService.lifecycle];;letregister()=Core.Container.Service.createlifecycleend