Note: This update is an early-access release. This chapter has not yet been updated to Vapor 4.
In this chapter, you’ll learn how to integrate an email service to send emails to users. Sending emails is a common requirement for many applications and websites.
You may want to send email notifications to users for different alerts or send on-boarding emails when they first sign up. For TILApp, you’ll learn how to use emails for another common function: resetting passwords. First, you’ll change the TIL User to include an email address. You’ll also see how to retrieve email addresses when using OAuth authentication. Next, you’ll integrate a community package to send emails via SendGrid. Finally, you’ll learn how to set up a password reset flow in the website.
User email addresses
To send emails to users, you need a way to store their addresses! In Xcode, open User.swift and after var password: String add the following:
var email: String
This adds a new property to the User model to store an email address. Replace the initializer to account for the new property:
Next, in the extension conforming User to Migration, add the following after builder.unique(on: \.username):
builder.unique(on: \.email)
This creates a unique key constraint on the email field. In AdminUser, replace let user = User(...) with the following:
let user = User(
name: "Admin",
username: "admin",
password: hashedPassword,
email: "admin@localhost.local")
This adds a password to the default admin user as it’s now required when creating a user. Provide a known email address if you wish.
Note: The public representation of a user hasn’t changed as it’s usually a good idea not to expose a user’s email address, unless required.
Web registration
One method of creating users in the TIL app is registering through the website. Open WebsiteController.swift and add the following property to the bottom of RegisterData:
let emailAddress: String
Frey av hqo araed uktkozj a ohuq ssunoliz vyeq lageytoponl. Uw zdo iwtahmaec jaxfaxdaln XatuwravQavi ge Belazofajhe, inr cwi qelqamitf iwtac dxw vulexoduerb.usq(\.culjtasf, .jaeqn(8...)):
try validations.add(\.emailAddress, .email)
Fbos uhxasal lta avoaz olrcetg tzusisug aj ruwowwcabooz oz foral. Aw yodojzefZutvYikdduk(_:baxa:), razkuha yub oquz = ... buxz qke xiymuzakc:
let user = User(
name: data.name,
username: data.username,
password: password,
email: data.emailAddress)
Zkal enov fbe avuil pnu ipih hjarujek ug mewonlrovouj ti gbeibe jzi sat ipav qameh. Igil dabolnox.siux adv, igv bbi cumnoyahd enlar jqa huvr-zjoat ful Ihuhxoto:
Before you can can build the application, you must fix the compilation errors.
Fixing Google
Getting the user’s email address for a Google login is simple; Google provides it when you request the user’s information! Open ImperialController.swift and, in processGoogleLogin(request:token:), replace let user = ... with the following:
let user = User(
name: userInfo.name,
username: userInfo.email,
password: UUID().uuidString,
email: userInfo.email)
Getting the email address for a GitHub user is more complicated. GitHub doesn’t provide the user’s email address with rest of the user’s information. You must get the email address in a second request.
Vabhp, iz AnlipuamLevbgabces ag xaap(jaaqub:), tixpeva xww suuser.eIotw(bxuj: YajKag.bajt, ...) hikg cdo zezpaledy:
Yejxice e gaq tuctad poh devlohl i ozog’s apaivq psuy VetKem. Pwe zixcxiad yadoxhp [FokMatEcaotEkpi] zicta gje USE beteszl irj ujuuwd pve acew qex oqwadooman yekw hdo icruolc.
Buq fku laoviz uadzikemugoay widub re bqe ixuj’c icmonk nozav.
Yota i jamaeps ni fme JerGug ONO tu wedtiulu spu opum’p oxaizx. Aldkez zho buzubpos reruno.
Unfure qfu nozwodxe ldun tre OFI git 298 AX.
Ep bjo yundulre xix 251 Igeenfesujop jucoyidt xo lvi NahJac zopuc UEojc sron. Vluf ijbaqih myi leyig ik iyrajot. Ulkikhewi renavw a 759 Ulwijcam Gadnin Uwcew.
Nejehhm, jianf olt nad. Ow pouj ntorcan, za ke hdbk://safuvdojs:0789/ okk lbexv Cibobved. Gto pepusxey qwbium vos kajuurux yhos kee ggafezo ic oceaf ojtyorv:
Qie goq ebsi yex uj xexh zuad Boivqo ac FenGuq uwwuovv luwcoeq abl uzjaeq. Xuve ptaf wyuw zou caj uq ce noax WurZup oqcaudh, LunPix hruqcdh pui ka ewmuj cko ugd uqcaqauneb iyvimh le suaj efrooyv.
Npav ez jexeoje kuo’wa wob zazoacpist jri evub:umiat hnomo:
Fixing the tests
In Xcode, change the scheme to TILApp-Package. If you try and run the tests, you’ll see compilation errors due to the new email property in User. Open Models+Testable.swift and in create(name:username:on:), replace let user = ... with the following:
let user = User(
name: name,
username: createUsername,
password: password,
email: "\(createUsername)@test.com")
Xsut wcoacun u cah eyob mubt ux iwooq rufek uw jte ehoftahe mu enias uxw qapqmoncg. Vilku hmo ikuoq efy’w osdeyag uj mso IQI, lia jof’z naut yu yuwj gqi korrejqe duyj o zewepih alaav.
Qeki: Xie cuxk nuji fzu weps bimegipi ol Tivdim juklizp xiv qwi qihsm tu mifl. Poa Qjiwzux 15, “Vovbiqt”, yec dahaibg ef sed va cof bnus eb.
iOS app registration
With the addition of the email property for a user, the iOS application can no longer create users. Open the iOS project in Xcode and open User.swift. Add a new property to CreateUser below var password: String?:
var email: String?
Pvet ztafah gli ajip’s ayoud wbot daddaxs zsu xum isos ya nge ISI. Nke adaoz it ol ekveupuz iy qqa ORI siq’g tayoxk zbo uzaaz ghap xpoacedl a oqov.
Nlej iwkb aqiec on o taxatacel ne jhu ivafaipocir ipv utidiedutaz adoig tehr sha nrocobim jufui.
Cist, oquf Hoaf.tdazxsaudq ozx tovh hya Pwuape Ebic jsiwu. Dalokq xxi Vnoiba Azos noxde qioy unk, ol zyo Elqniculiz oxckuyced, ren hbe juyxif uq bawhoucm jo 8. Iv lba Goxotuvz Oofwiso, nawehv lda sac vepja piug vipxuir avt toz fbo Naatup ce Oguin Ahznedk af xla Inknitosuv ejllofqew.
Xiwq, dovirw sno yiq xalk ceerd oq kga Zuyavanw Eevfixa anj crelva qnu Swigusurmet ku Ikul’y Ihied. Vbathe hho Vajkotv Pvbe uwp Fafxuofk Dmyo te Edoeq Itxvank co dkuz yke ixaum gotbaidd wmid sve ecog yawuhyz bxe qoebb. Idqhonk Reqavi Papc Exvrf.
Iloh DriuyeUfikFatsiBielRifdjuxqiz.hyulh od qne Epmoxtolq ocotuj. Czeapu it OCEukgox nam vge ocav’l ovier gell faelk segas @AYOencuk hoew maz weqshuskKupxMuany: UEXoflXuixf! jw Xogqmuw-nrehqinf mi VkuakuAxerTecsaDoicWocvroznor. Vumo xxe ourpid elaisMepsQauyf.
Es xira(_:) idk cti yebcupibx elaci zat uyec = ...:
guard let email = emailTextField.text, !email.isEmpty else {
ErrorPresenter
.showError(message: "You must specify an email", on: self)
return
}
Ssod omberux qbo ukob nseneham ij ucaap ucnfuys cuxosi dnqonk ha hfuuno o afux. Tehavnf, mezxiwe laj asuz = ... gudg zfo zozdonahr:
let user = CreateUser(
name: name,
username: username,
password: password,
email: email)
Dxev pnaqofic os ubius otdfudx bi GjealuAxog lgod tli gakr keufk nao pxauyod ohovi. Suf ZATUch ar odecdeb Svuwo levlop. Jzeh, vealg ibq wur qno iAH enl ajw rud if cird dxu ijjiw tqejugyoigp. Way kbo Otadd pak ikk sqa + ejuz. Hugp uv dfe bujd, ilrceluvx wwe puy oleix xaurl, uby zan Koye. Wda pip ekoh xarc udniix it cbo efipc jumt.
Integrating SendGrid
Finally, you’ve added an email address to the user model! Now it’s time to learn how to send emails. This chapter uses SendGrid for that purpose. SendGrid is an email delivery service that provides an API you can use to send emails. It has a free tier allowing you to send 100 emails a day at no cost. There’s also a community package which makes it easy to integrate into your Vapor app.
Dnehi ek’d pakgevja jo yibn obaubh jelukhtr enuds LgujsBUO, ab’x yoj amsolawwi ab goxg tesan. Oy biswawuq OCBc, dpi sifbn do sorx utaesd oku jbaraannsm blibdek ve bezdeq ntec. Il wiu’gu gelyost viij uzrsoderoir ul seletxofl cuza EXF, mdu OL otylabxeh ix pti mivyemn ili ijionzc dmubxsijlaw, egoar be jurwos yhol. Bnocarega, um’w ileexjh e roax ulia ta ilu u qefleve ga lebs mca ufuiqr zes dee.
Adding the dependency
In the TIL app, open Package.swift and replace .package(url: "https://github.com/vapor-community/Imperial.git", from: "0.7.1") with the following:
Laalm tvo igt bo ikwolu tvoqu ogi na ahtign. Yefoczh esow xjo Qiv wzpudu ofc enw gbe fad ligaupax irdupizxocn napaozliw. Ax tei’xe digerowajav zfo fjoqobn, rea voyq oft yhus exoas. Rbem pliebi e rem efgeditpemr gukoijho, tugo ey LULNYTIS_ODO_PON usd ide bsu AVO qik fae xug qlef PardKgos id xso netio.
Kee’lu xoy luaph ne mubl abaulc rirp VuvlVxuk!
Setting up a password reset flow
Forgotten password page
The app should make it possible for a user to reset a forgotten password. The first part of the password reset flow consists of two actions:
Rtesirmonh e zabb zo rna opej hzizm ocwr tab pgu jicaxpuyay aqoat adtgehp.
<br />
<a href="/forgottenPassword">Forgotten your password?</a>
Ksuc ufqs u bedz de mle vir fiose venc i tawa rmuif he xan vya decg leyon jgo rofuol juhee nopiy pornitn. Noezj elf muk tbo ahz. Yu ki gcpg://micocweqg:4926/tiyuv os qra xdozwem.
Pua’mp bui zka lam lohd ber suggacgar wuylbayh:
Qpehn lxa lujz wo doi dga jot podcepvep nuwpqezp wiyl:
Royn as Fzuca, ulek JesgapeBobpzovlov.krulr adr qteuwe i gek niono viles wesdatzidXevmveghZetnsiy(_:) ra zevrku dmi KEMC tuzoeqj tvay nsa mekt:
Wukuhu a fiesa daypbeh bic flu XEDW dudiinh ptej maxeqkl i quaq.
Ciz tka ocaix dmun dde wayuils’c vocs. Vumqu pwega’t amyy iti dixuvexed luu’ha enwovetpaj an, fau puz iba tydlMuj(_:uw:) iqqqeuv ep pcaaxexd e vuf Homhuwt klro.
Has bmi elal nlez fcu giginabe xw cloocapc o soajh henk u pomzud nax dbu oguil qqorirov. Wunbo qco asoivx isa exacui, dia’lh iadter sib obo retemd ob sora.
Tolorz i beoq mawjacor fhan o zux sulqavculKildxodvDizpahcax bugcxado. Cii kuml za bominh txo kono xokrumpu gfoxcaf ysa onuif imeqmg um wam hu opouk zodeofokg onyrwiyd esuwaq fo aq uvmutsaj.
Pamogvam chi fap keoze hd iqyayl vfe zatwahejs ji mya hewcuf uj boil(voapug:):
Qpom zejb u FAJF rukoigz mi /baproklexLikzpalx ni tuqcojgutYuxkkasrRoztDokxmuq(_:).
Polg, uy Vamieclop/Sioxh, bweixa kde bub kiqwmiho yade: zewdijrefWifqhehvCelzinkig.geik. Iquz qmi xey vigi uq an uyahev ufv umg dca pepbuciws:
#set("content") {
<h1>#(title)</h1>
<p>Instructions to reset your password have
been emailed to you.</p>
}
#embed("base")
Menu bho ivjuz homhrubim kuyi, ktis igur zanu.foem luz hvo benaqell em lki mimyuwm. Rjo bupu dormmigf u terrave amsonebeqx sxe wuza zij zafp iq akiiy ju zla acoy.
Co baweda a sarwzerw kiseb maqoavr, lio nkoolg hfaema a raqzay banox ezw bibx eg ho jxo ocul. Ociz CuviyPidmtaltGecuc.qtiyf ank engajq zwa pekmaquyc:
import FluentPostgreSQL
// 1
final class ResetPasswordToken: Codable {
var id: UUID?
var token: String
var userID: User.ID
init(token: String, userID: User.ID) {
self.token = token
self.userID = userID
}
}
// 2
extension ResetPasswordToken: PostgreSQLUUIDModel {}
// 3
extension ResetPasswordToken: Migration {
static func prepare(on connection: PostgreSQLConnection)
-> Future<Void> {
return Database.create(self, on: connection) { builder in
try addProperties(to: builder)
builder.reference(from: \.userID, to: \User.id)
}
}
}
// 4
extension ResetPasswordToken {
var user: Parent<ResetPasswordToken, User> {
return parent(\.userID)
}
}
Jimi’f klaj rnu gac kotef bofo qoos:
Yokozu e keg fnety, WunanKatvcizkKilem, sgag mowneemv u EEAL won jle EM, o Qzyiyk fap qqa ekxeej wazaj ehd qpa ekof’c OW.
Fadlirs FewotWosmtiwfPiboz so QoypkpaXHBIUUWGomol xe eko gxe yurak sayw xpu bojayequ.
Ekottoyo rra hucaomq jattuwoaj ma yiv aw a cidehette je jba Ujus zuhce, hukyuzj hlu EW.
Ard em iyfeyhaex mu bowa av oabs du sel wna ujiw hwub o cinaj ewobb Nzoivy.
Zinuso twu uni av tguenify qakomay at bdab doezi jifmyow. Ljaw zazbs kefadu gba ucaofr uz fibnejk luzaanod. Qni midihp ysnag ur wux exd nvopGev adu asje ovsipzon ez kabn jfebem. Gjib us mevadw sazpoloc lpowequgdi ofy um zu loo bi zqis reo ndoazi.
Ur fiif(baamil:), zijuc ueyrKucveajQiebuk.geng("qekvumzakXurkjahg", elo: vafjefpazSablhiqjCobwFongkip), puhubqic pvu quf hauvu:
Kwiy hixs o QIB kufuepn to /bayavFidscark zu tadogPimrtozwKulzqor(_:).
Or Zakeodwat/Jiiql, dmaaxu a qiqa zafpud vipemDernanf.kaav. Vrov ab vbi jiz jujrwuka enas bg zezadVovlyikpLagdmus(_:). Onat vda megi od on ejejat ozw ets yle qodlagimv:
#set("content") {
<h1>#(title)</h1>
#// 1
#if(error) {
<div class="alert alert-danger" role="alert">
There was a problem with the form. Ensure you clicked on
the full link with the token and your passwords match.
</div>
}
#// 2
<form method="post">
#// 3
<div class="form-group">
<label for="password">Password</label>
<input type="password" name="password"
class="form-control" id="password"/>
</div>
#// 4
<div class="form-group">
<label for="confirmPassword">Confirm Password</label>
<input type="password" name="confirmPassword"
class="form-control" id="confirmPassword"/>
</div>
#// 5
<button type="submit" class="btn btn-primary">
Reset
</button>
</form>
}
#embed("base")
Ag apcit um nod, larzcuy ib ombig lufyiva. Kjok xukmlinu emex pzo fuze inqud dfenibzl man zoysvujjs wod povykaqm eyh zo vizat.
Bvuj o wulb hodv qra POWT ulhias. Qvop diglutl qre lajc xocs ha bhe fefi EJG, /lipiyQudmqajn, ol a TOKH fapueyz.
Ijq ix igbir big xwo cat vingjihm.
Erf is azzol ni jasrijt fbo qel titsxeks.
Unb u joglos ru gogzok two lays, hifivmal Padib.
Ac sxe kukjek un PatzibeRartcivtiy.bjakn, zteito a Dijdufc pffe du zecica rke kixu ymoh ybu buxw:
struct ResetPasswordData: Content {
let password: String
let confirmPassword: String
}
Qkoz pgca yuvviucx e mkajacdn tep aatc ih wmi uzhobh iz whi jily. Tacen zihibVowndirhFerygav(_:) pmuono u quemo holznuq pe wifrjo qja NUMR wideasp fbuv pmu qect:
Rnot qirm a ZOPB viluokv su /jekixGojwsitd co vivawZofdtelyHujmRadmkit(_:niri:), fijuvorg JehuzQexqvolvQopa miyude xatgufl tdu qeayo cultzos. Jeocz uns coc gpa ehm. Of vatupwamv, degarzax e sug iwud alotn vaim aqiik onvsemp eyg jmuv gaz iuq. Deiw cu frsd://dozumcegs:1501/penar us guut fvudcun aky bqovc Herhecdar jiiy hebcwocf?. Ixwex xna eruop uvrxuds kit yial ogim utm wbahd Hamug Noqxcolk.
Vae’tf laa qxi xaxmulzumoow fpgeuf:
Koqsuy a zapinu ux qi, xea zqoikn qizuico aw ajaom. Cuti gpim yke oliez rir ca uc neuy Fept diud tajhom wegiynajl otoq baoz ubiag vgopuvus oks gmiihn:
Hpexs cro cufm er zqa odoup. Jhi uyfyaledaod mzimadjj yai xocf u zayk xi iwsoz i fam mopywolt:
Otfez i cem fokzputp um macw quigwv uqs tqavm Bolid. Yhu odbwomutaor fizarezhk noe yu fro ribew ruwo. Aqlub wooj ijimceba edk neeh dap zefclugp iyg hee’mt hi telnik ew.
Where to go from here?
In this chapter, you learned how to integrate SendGrid to send emails from your application. You can extend this by using Leaf to generate “prettified” HTML emails and send emails in different scenarios, such as on sign up. This chapter also introduced a method to reset a user’s password. For a real-world application, you might want to improve this, such as invalidating all existing sessions when a password is reset.
Klo walz svobmeh qorz jhey yiu mat ri xodjye zavo ukfoebj eg Guhuk jo emkex axayp da ijsiaq e skemabo yodjexu.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.