Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow easier SMTP config envs #1910

Merged
merged 4 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 82 additions & 20 deletions Guide/mail.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,16 @@ Remember that the successfull delivery of email largely depends on the from-doma

The delivery method is set in `Config/Config.hs` as shown below.

### Any SMTP Server
### Set SMTP by Environment Variables

It's a good idea to not hardcode your SMTP credentials in your code. Instead, you can use environment variables to set your SMTP credentials. This is especially useful when deploying your application to a cloud provider like AWS, where it will use different credentials in production than on your local machine.


```haskell
-- Add this import
import IHP.Mail


config :: ConfigBuilder
config = do
-- other options here, then add:
Expand All @@ -163,7 +167,7 @@ config = do

### Local SMTP with Mailhog

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 convenient 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.
Expand All @@ -173,21 +177,80 @@ A convinient way to see sent mails is to use a local mail testing such as [MailH


```haskell
-- Add this import
-- Config/Config.hs

-- Add these imports
import IHP.Mail
import Network.Socket (PortNumber)
import IHP.Mail.Types (SMTPEncryption)
import IHP.EnvVar

config :: ConfigBuilder
config = do
-- other options here, then add:
option $ SMTP
{ host = "127.0.1.1" -- On some computers may need `127.0.0.1` instead.
, port = 1025
, credentials = Nothing
, encryption = Unencrypted
}
-- Previous options
-- ...

-- Read SMTP configuration from environment variables.
smtpHost <- env @Text "SMTP_HOST"
smtpPort <- env @PortNumber "SMTP_PORT"
smtpEncryption <- env @SMTPEncryption "SMTP_ENCRYPTION"

smtpUserMaybe <- envOrNothing "SMTP_USER"
smtpPasswordMaybe <- envOrNothing "SMTP_PASSWORD"

-- Determine if credentials are available and set SMTP configuration accordingly.
let smtpCredentials = case (smtpUserMaybe, smtpPasswordMaybe) of
(Just user, Just password) -> Just (user, password)
_ -> Nothing

-- Print out SMTP configuration for debugging purposes.
liftIO $ putStrLn $ "SMTP HOST: " <> show smtpHost
liftIO $ putStrLn $ "SMTP PORT: " <> show smtpPort

-- SMTP to work with MailHog or other SMTP services.
option $
SMTP
{ host = cs smtpHost
, port = smtpPort
, credentials = smtpCredentials
, encryption = smtpEncryption
}
```

### SendGrid
Then set the environment variables in your `.envrc` file:

```
# Add your env vars here
#
# E.g. export AWS_ACCESS_KEY_ID="XXXXX"
export SMTP_HOST="127.0.0.1" # On some computers may need `127.0.1.1` instead.
export SMTP_PORT="1025"
export SMTP_ENCRYPTION="Unencrypted"
```

Finally, when you'll have the app deployed, for example on AWS using SES, you would have to set the environment variables in your `flake.nix`.

```nix
services.ihp = {
domain = "...";
migrations = ./Application/Migration;
schema = ./Application/Schema.sql;
fixtures = ./Application/Fixtures.sql;
sessionSecret = "...";
additionalEnvVars = {
SMTP_HOST = "email-smtp.eu-west-1.amazonaws.com";
SMTP_PORT = "587";
SMTP_ENCRYPTION = "STARTTLS";
SMTP_USER = "your-user-name";
SMTP_PASSWORD = "your-password";
ENV_NAME = "qa";
AWS_ACCESS_KEY_ID = "your key ID";
AWS_SECRET_ACCESS_KEY = "your secret";
};
};
```

### AWS SES

```haskell
-- Add this import
Expand All @@ -196,14 +259,14 @@ import IHP.Mail
config :: ConfigBuilder
config = do
-- other options here, then add:
option $ SendGrid
{ apiKey = "YOUR SENDGRID API KEY"
, category = Nothing -- or Just "mailcategory"
option $ SES
{ accessKey = "YOUR AWS ACCESS KEY"
, secretKey = "YOUR AWS SECRET KEY"
, region = "eu-west-1" -- YOUR REGION
}
```


### AWS SES
### SendGrid

```haskell
-- Add this import
Expand All @@ -212,10 +275,9 @@ import IHP.Mail
config :: ConfigBuilder
config = do
-- other options here, then add:
option $ SES
{ accessKey = "YOUR AWS ACCESS KEY"
, secretKey = "YOUR AWS SECRET KEY"
, region = "eu-west-1" -- YOUR REGION
option $ SendGrid
{ apiKey = "YOUR SENDGRID API KEY"
, category = Nothing -- or Just "mailcategory"
}
```

Expand Down
21 changes: 20 additions & 1 deletion IHP/EnvVar.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ where
import IHP.Prelude
import Data.String.Interpolate.IsString (i)
import qualified System.Posix.Env.ByteString as Posix
import Network.Socket (PortNumber)
import Data.Word (Word16)
import IHP.Mail.Types
import IHP.Environment

-- | Returns a env variable. The raw string
Expand Down Expand Up @@ -88,4 +91,20 @@ instance EnvVarReader ByteString where
instance EnvVarReader Bool where
envStringToValue "1" = Right True
envStringToValue "0" = Right False
envStringToValue otherwise = Left "Should be set to '1' or '0'"
envStringToValue otherwise = Left "Should be set to '1' or '0'"

-- | Allow reading the env var of an SMTP port number.
instance EnvVarReader PortNumber where
envStringToValue string = case textToInt (cs string) of
Just integer -> Right $ convertIntToPortNumber integer
Nothing -> Left [i|Expected integer to be used as a Port number, got #{string}|]

convertIntToPortNumber :: Int -> PortNumber
convertIntToPortNumber int = fromIntegral (int :: Int) :: PortNumber

-- | Allow reading the env var of an SMTP encryption method.
instance EnvVarReader SMTPEncryption where
envStringToValue "Unencrypted" = Right Unencrypted
envStringToValue "TLS" = Right TLS
envStringToValue "STARTTLS" = Right STARTTLS
envStringToValue otherwise = Left [i|Expected 'Unencrypted', 'TLS' or 'STARTTLS', got #{otherwise}|]
Loading