From ac6df00b2568aa8004fb7106268a241ae8eab1e9 Mon Sep 17 00:00:00 2001 From: Hendrik Richter Date: Tue, 21 Dec 2021 07:13:00 +0100 Subject: [PATCH 1/6] improve comment --- IHP/Mail.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IHP/Mail.hs b/IHP/Mail.hs index eeebf4e4f..276f39c1a 100644 --- a/IHP/Mail.hs +++ b/IHP/Mail.hs @@ -119,7 +119,7 @@ class BuildMail mail where bcc :: (?context :: context, ConfigProvider context) => mail -> [Address] bcc mail = [] - -- | Custom headers, excluding `from`, `to`, `cc`, and `bcc` + -- | Custom headers, excluding @from@, @to@, @cc@, @bcc@, @subject@, and @reply-to@ -- -- __Example:__ Add a custom X-Sender header -- From afb072fd145eb4c72237e2d21eadb8c085e4361a Mon Sep 17 00:00:00 2001 From: Hendrik Richter Date: Tue, 21 Dec 2021 07:14:34 +0100 Subject: [PATCH 2/6] lot's -> lots --- Guide/helpful-tips.markdown | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Guide/helpful-tips.markdown b/Guide/helpful-tips.markdown index dc97707ac..34c9d252b 100644 --- a/Guide/helpful-tips.markdown +++ b/Guide/helpful-tips.markdown @@ -127,7 +127,7 @@ In general the `\case ...` can be expanded to `\value -> case value of ...`. ### The Pipe operator [`|>`](https://ihp.digitallyinduced.com/api-docs/IHP-HaskellSupport.html#v:-124--62-) -In IHP code bases you find lot's of usage of the [`|>`](https://ihp.digitallyinduced.com/api-docs/IHP-HaskellSupport.html#v:-124--62-) operator. We usually call it the `pipe operator`. +In IHP code bases you find lots of usage of the [`|>`](https://ihp.digitallyinduced.com/api-docs/IHP-HaskellSupport.html#v:-124--62-) operator. We usually call it the `pipe operator`. The operator allows you to write code like this: @@ -147,7 +147,7 @@ validateField #firstname nonEmpty ( In general: ```haskell -function arg1 arg2 object +function arg1 arg2 object = object |> function arg1 arg2 ``` diff --git a/README.md b/README.md index 6e6073ac0..58d18fa1b 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Auto Refresh can be enabled for IHP views with a single line of code. [Watch it in action!](https://twitter.com/digitallyinduce/status/1312017800223956992) **Longterm Roadmap** -Lot's of frameworks are already gone a year after launch. Especially in the fast moving JS world. But don't worry about IHP. We have been using it at digitally induced since 2017. It's actively used by us and our friends and partners. Even without external contributors we will build new features and do periodic maintenance releases in the future. We have big plans for IHP and as a profitable and independent software company we have the ability to actually execute them over the longterm. +Lots of frameworks are already gone a year after launch. Especially in the fast moving JS world. But don't worry about IHP. We have been using it at digitally induced since 2017. It's actively used by us and our friends and partners. Even without external contributors we will build new features and do periodic maintenance releases in the future. We have big plans for IHP and as a profitable and independent software company we have the ability to actually execute them over the longterm. ## Reviews From 7eb1de654d125bf38672c70d025f4fb2abb298ac Mon Sep 17 00:00:00 2001 From: Hendrik Richter Date: Tue, 21 Dec 2021 08:09:04 +0100 Subject: [PATCH 3/6] a IHP -> an IHP --- Guide/ihp-pro.markdown | 3 +-- Guide/recipes.markdown | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Guide/ihp-pro.markdown b/Guide/ihp-pro.markdown index c3f5e6d22..a1b5ec49b 100644 --- a/Guide/ihp-pro.markdown +++ b/Guide/ihp-pro.markdown @@ -30,7 +30,7 @@ With bugs we have the same story. Instead of just a GitHub issue, it’s now a c Here’s the big plan in a short form: -1. Introduce a IHP Pro subscription +1. Introduce an IHP Pro subscription 2. Use the funding by our new customers to make IHP & the ecosystem a lot better 3. Become the well-known best framework for building web applications @@ -46,4 +46,3 @@ We recommend you try out these features after upgrading to pro: - [Google Login](oauth.html#introduction) - [Email Confirmation](authentication.html#email-confirmation) - diff --git a/Guide/recipes.markdown b/Guide/recipes.markdown index 60f873966..9782e9c59 100644 --- a/Guide/recipes.markdown +++ b/Guide/recipes.markdown @@ -293,7 +293,7 @@ To confirm before a link is fired add an `onclick` to the link. To generate a random string which can be used as a secure token or hash use [`generateAuthenticationToken`](https://hackage.haskell.org/package/wreq): ```haskell -import IHP.AuthSupport.Authentication -- Not needed if you're inside a IHP controller +import IHP.AuthSupport.Authentication -- Not needed if you're inside an IHP controller do token <- generateAuthenticationToken @@ -346,7 +346,7 @@ import Network.HTTP.Types.Header (hContentType) import Network.HTTP.Types (status404) import Network.Wai (responseLBS) -customNotFoundResponse :: (?context :: ControllerContext) => IO () +customNotFoundResponse :: (?context :: ControllerContext) => IO () customNotFoundResponse = do page <- LBS.readFile "static/404.html" respondAndExit $ responseLBS status404 [(hContentType, "text/html")] page @@ -356,7 +356,7 @@ Now you can use your `customNotFoundResponse`: ```haskell action WelcomeAction = do -post <- fetchOneOrNothing ("30a73014-101e-4269-be91-be6c019de289" :: Id Post) +post <- fetchOneOrNothing ("30a73014-101e-4269-be91-be6c019de289" :: Id Post) case post of Nothing -> customNotFoundResponse -- Database record disappeared !!! Just post -> render ShowView { .. } From e2787bca5a60892928e2f5d4819ea8325240f5c9 Mon Sep 17 00:00:00 2001 From: Hendrik Richter Date: Tue, 21 Dec 2021 08:09:56 +0100 Subject: [PATCH 4/6] improve Mail documentation --- Guide/mail.markdown | 93 ++++++++++++++++++++++++++++++++++++++++++--- IHP/Mail.hs | 4 +- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/Guide/mail.markdown b/Guide/mail.markdown index b37d240a9..f44c821bb 100644 --- a/Guide/mail.markdown +++ b/Guide/mail.markdown @@ -38,7 +38,7 @@ instance BuildMail ConfirmationMail where |] ``` -### Changing Subject +### Changing the Subject Let's first change the subject of our mail from `Subject` to something more useful: @@ -66,9 +66,17 @@ to ConfirmationMail { .. } = Address { addressName = Just (get #name user), addr The email sender is set to `hi@example.com` by default. Usually, you want to use your domain here. For this example, we will stick with the `hi@example.com` for now. +### Changing the Reply-To address + +By default the "Reply" button in an email programs creates a reply to the From address. You can change that behavior by setting the `Reply-To` header to another target email address: + +```haskell +replyTo ConfirmationMail { .. } = Just Address { addessName = Just "Support", addressEmail = "support@example.com" } +``` + ### Email Content -Last we need to change the email text a little bit. The mail supports HSX so this is similar to writing a IHP view: +Last we need to change the email text a little bit. The mail supports HSX so this is similar to writing an IHP view: ```haskell html ConfirmationMail { .. } = [hsx| @@ -87,6 +95,21 @@ action MyAction = do sendMail ConfirmationMail { user } ``` +## Custom Headers + +If you need to send specific mail headers you can do so as well: + +```haskell +headers ConfirmationMail { .. } = + [ ("X-Mailer", "mail4j 2.17.0") + , ("In-Reply-To", "<123456@list.example.com>") + ] +``` + +Implementation detail: IHP first adds headers set by itself (like `Subject` and the optional `Reply-To`), then headers provided via `headers`. If you don't want to use the `replyTo` helper from above it's absolutely fine to add the `Reply-To` header manually. + + + ## Mail Servers By default, IHP uses your local `sendmail` to send out the email. IHP also supports sending mail via AWS Simple Email Service (SES), SendGrid (via Azure or directly) or via any standard SMTP server. @@ -107,13 +130,14 @@ config = do option $ SMTP { host = "smtp.myisp.com" , port = 2525 - , credentials = Nothing -- or Just ("myusername","hunter2") + , credentials = Nothing -- or: Just ("myusername","hunter2") + , encryption = TLS -- <-- other options: `Unencrypted` or `STARTTLS` } ``` ### Local (For Debugging) -A convinient way to see sent mails is to use a local mail testing such as [MailHog](https://github.com/mailhog/MailHog). This service will catch all outgoing emails, and show their HTML to you - which is handy while developing. +A convinient way to see sent mails is to use a local mail testing such as [MailHog](https://github.com/mailhog/MailHog). This service will catch all outgoing emails, and show their HTML to you - which is handy while developing. 1. Make sure `sendmail` is locally installed and configured. 2. Install MailHog. @@ -133,6 +157,7 @@ config = do { host = "127.0.1.1" , port = 1025 , credentials = Nothing + , encryption = Unencrypted } ``` @@ -196,4 +221,62 @@ instance BuildMail ConfirmationMail where ## Plain Text Emails -TODO +Every email should have a plain text version for people with reasonable mail clients. If you don't specify one and only set the HTML content via `html` (see above), then IHP automatically creates a plain text version from you by stripping away all HTML tags. This is suboptimal. + +The better option is to manually provide a useful plain text version of your emails: + +```haskell +-- don't use, emails look off + +text ConfirmationMail { .. } = cs [plain| + Hey #{userName}, + + Thanks for signing up! Please confirm your account by following this link: + https://.... +|] + where + userName = get #name user +``` + +Note a few differences here: we use `[plain| ... |]` instead of `[hsx| ... |]` so we can't use HSX's inline Haskell like `{get #userName user}` but have to live with simple substitution. Note the `#` in front of these substitutions: `#{userName}`. We use `cs` to convert the `[Char]` to the required `Text` type. + +If you send this email, due to the way we indented the code it would render as: + +``` +(blank line) + Hey Foobar, + + Thanks for signing up! Please confirm your account by following this link: + https://.... +(blank line) +``` + +To get proper plain text rendering you either need to forgo nice indentation and do it like this: + +```haskell +-- example without nice indentation + +text ConfirmationMail { .. } = cs [plain|Hey #{userName}, + +Thanks for signing up! Please confirm your account by following this link: +https://....|] + where + userName = get #name user +``` + +or use code like `|> T.strip |> T.lines |> map T.strip |> T.unlines` which strips leading newlines and spaces from the whole mail, and then leading spaces from every line: + +```haskell +-- example with nice indentation, and still nice email + +import qualified Data.Text as T + +text ConfirmationMail { .. } = cs [plain| + Hey #{userName}, + + Thanks for signing up! Please confirm your account by following this link: + https://.... +|] |> T.strip |> T.lines |> map T.strip |> T.unlines + where + userName = get #name user +``` diff --git a/IHP/Mail.hs b/IHP/Mail.hs index 276f39c1a..0d9069a08 100644 --- a/IHP/Mail.hs +++ b/IHP/Mail.hs @@ -121,9 +121,9 @@ class BuildMail mail where -- | Custom headers, excluding @from@, @to@, @cc@, @bcc@, @subject@, and @reply-to@ -- - -- __Example:__ Add a custom X-Sender header + -- __Example:__ Add a custom X-Mailer header -- - -- > headers CreateAccountMail { .. } = [("X-Sender", "mail4j 2.17.0")] + -- > headers CreateAccountMail { .. } = [("X-Mailer", "mail4j 2.17.0")] -- headers :: (?context :: context, ConfigProvider context) => mail -> Headers headers mail = [] From 8ae1b1b016fbc518f31cf4d474f678fb3f9579d4 Mon Sep 17 00:00:00 2001 From: Hendrik Richter Date: Tue, 21 Dec 2021 09:05:44 +0100 Subject: [PATCH 5/6] use trimming qq instead of manual trimming --- Guide/mail.markdown | 51 +++++++-------------------------------------- 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/Guide/mail.markdown b/Guide/mail.markdown index f44c821bb..ddb9004af 100644 --- a/Guide/mail.markdown +++ b/Guide/mail.markdown @@ -226,10 +226,10 @@ Every email should have a plain text version for people with reasonable mail cli The better option is to manually provide a useful plain text version of your emails: ```haskell --- don't use, emails look off +import NeatInterpolation -text ConfirmationMail { .. } = cs [plain| - Hey #{userName}, +text ConfirmationMail { .. } = cs [trimming| + Hey ${userName}, Thanks for signing up! Please confirm your account by following this link: https://.... @@ -238,45 +238,8 @@ text ConfirmationMail { .. } = cs [plain| userName = get #name user ``` -Note a few differences here: we use `[plain| ... |]` instead of `[hsx| ... |]` so we can't use HSX's inline Haskell like `{get #userName user}` but have to live with simple substitution. Note the `#` in front of these substitutions: `#{userName}`. We use `cs` to convert the `[Char]` to the required `Text` type. +Note a few differences to the `html` version here: -If you send this email, due to the way we indented the code it would render as: - -``` -(blank line) - Hey Foobar, - - Thanks for signing up! Please confirm your account by following this link: - https://.... -(blank line) -``` - -To get proper plain text rendering you either need to forgo nice indentation and do it like this: - -```haskell --- example without nice indentation - -text ConfirmationMail { .. } = cs [plain|Hey #{userName}, - -Thanks for signing up! Please confirm your account by following this link: -https://....|] - where - userName = get #name user -``` - -or use code like `|> T.strip |> T.lines |> map T.strip |> T.unlines` which strips leading newlines and spaces from the whole mail, and then leading spaces from every line: - -```haskell --- example with nice indentation, and still nice email - -import qualified Data.Text as T - -text ConfirmationMail { .. } = cs [plain| - Hey #{userName}, - - Thanks for signing up! Please confirm your account by following this link: - https://.... -|] |> T.strip |> T.lines |> map T.strip |> T.unlines - where - userName = get #name user -``` +- We use `[trimming| ... |]` instead of `[hsx| ... |]` so we can't use HSX's inline Haskell like `{get #userName user}` but have to live with simple substitution. Note the dollar sign in front of these substitutions: `${userName}`. +- The `[trimming||]` quasiquoter takes care of removing the whitespace that our indentations introduced, which we don't want in the actual emails. +- We use `cs` to convert the `[Char]` to the required `Text` type. From 7698849657ae07c84abe19a8ef0fbec91911598d Mon Sep 17 00:00:00 2001 From: Hendrik Richter Date: Tue, 21 Dec 2021 11:57:07 +0100 Subject: [PATCH 6/6] tabs to spaces --- Guide/mail.markdown | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Guide/mail.markdown b/Guide/mail.markdown index ddb9004af..5acfa2c22 100644 --- a/Guide/mail.markdown +++ b/Guide/mail.markdown @@ -102,8 +102,8 @@ If you need to send specific mail headers you can do so as well: ```haskell headers ConfirmationMail { .. } = [ ("X-Mailer", "mail4j 2.17.0") - , ("In-Reply-To", "<123456@list.example.com>") - ] + , ("In-Reply-To", "<123456@list.example.com>") + ] ``` Implementation detail: IHP first adds headers set by itself (like `Subject` and the optional `Reply-To`), then headers provided via `headers`. If you don't want to use the `replyTo` helper from above it's absolutely fine to add the `Reply-To` header manually. @@ -131,7 +131,7 @@ config = do { host = "smtp.myisp.com" , port = 2525 , credentials = Nothing -- or: Just ("myusername","hunter2") - , encryption = TLS -- <-- other options: `Unencrypted` or `STARTTLS` + , encryption = TLS -- <-- other options: `Unencrypted` or `STARTTLS` } ``` @@ -157,7 +157,7 @@ config = do { host = "127.0.1.1" , port = 1025 , credentials = Nothing - , encryption = Unencrypted + , encryption = Unencrypted } ``` @@ -232,10 +232,10 @@ text ConfirmationMail { .. } = cs [trimming| Hey ${userName}, Thanks for signing up! Please confirm your account by following this link: - https://.... + https://.... |] where - userName = get #name user + userName = get #name user ``` Note a few differences to the `html` version here: