Skip to content

Commit

Permalink
Allow easier SMTP config envs (#1910)
Browse files Browse the repository at this point in the history
* Easier import of SMTP credentials

* EnvVarReader SMTPEncryption

* Add docs

* Add comments
  • Loading branch information
amitaibu committed Feb 15, 2024
1 parent 81a017e commit 3ebcd95
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 21 deletions.
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}|]

0 comments on commit 3ebcd95

Please sign in to comment.