Packages

Send emails from Gleam with SendGrid

Current section

Files

Jump to
sendgriddle src sendgriddle.gleam
Raw

src/sendgriddle.gleam

import gleam/dynamic/decode
import gleam/http
import gleam/http/request.{type Request}
import gleam/http/response.{type Response}
import gleam/json
import gleam/option.{type Option, None, Some}
pub type Email {
Email(
/// A list of emails to whom this email will be sent.
///
to: List(String),
/// The email address from which messages are sent, it should be a verified
/// sender in your Twilio SendGrid account.
///
sender_email: String,
/// A name or title associated with the email address such as "Support" or
/// "Alice".
///
sender_name: String,
/// The subject of your email.
///
/// > Note how, as per [RFC 2822](https://www.rfc-editor.org/rfc/rfc2822#section-2.1.1),
/// > the subject line should be no more than 78 characters, and must be no
/// > more than 998 characters.
///
subject: String,
content: EmailContent,
)
}
pub type EmailContent {
TextContent(text: String)
RichContent(html: String, text: String)
}
pub type SendGridError {
SendGridError(
/// An id identifying the error, if applicable.
///
id: Option(String),
errors: List(ErrorObject),
)
UnexpectedResponseError(response: Response(String))
}
pub type ErrorObject {
ErrorObject(
message: String,
/// The request's field that generated the error, if applicable.
///
field: Option(String),
/// A helper text or a link to documentation to help you troubleshoot the
/// error.
///
help: Option(String),
)
}
/// A request to send email over SendGrid's v3 Web API.
///
pub fn mail_send_request(email: Email, api_key: String) -> Request(String) {
let Email(to:, sender_name:, sender_email:, subject:, content:) = email
let make_email = fn(email) { json.object([#("email", json.string(email))]) }
let recipients = json.object([#("to", json.array(to, make_email))])
let personalizations = json.preprocessed_array([recipients])
let from =
json.object([
#("email", json.string(sender_email)),
#("name", json.string(sender_name)),
])
let make_content = fn(content, content_type) {
json.object([
#("type", json.string(content_type)),
#("value", json.string(content)),
])
}
let content = case content {
TextContent(text:) ->
json.preprocessed_array([make_content(text, "text/plain")])
RichContent(html:, text:) ->
json.preprocessed_array([
make_content(text, "text/plain"),
make_content(html, "text/html"),
])
}
let body =
json.object([
#("personalizations", personalizations),
#("from", from),
#("subject", json.string(subject)),
#("content", content),
])
request.new()
|> request.set_method(http.Post)
|> request.set_host("api.sendgrid.com")
|> request.set_path("/v3/mail/send")
|> request.set_body(json.to_string(body))
|> request.prepend_header("authorization", "Bearer " <> api_key)
|> request.prepend_header("content-type", "application/json")
}
/// Parse a response to a mail-send request, to determine whether or not it
/// was successful.
///
pub fn mail_send_response(
response: Response(String),
) -> Result(Nil, SendGridError) {
case response.status {
n if 200 <= n && n <= 299 -> Ok(Nil)
_ ->
case json.parse(response.body, sendgrid_error_decoder()) {
Ok(errors) -> Error(errors)
Error(_) -> Error(UnexpectedResponseError(response))
}
}
}
fn sendgrid_error_decoder() -> decode.Decoder(SendGridError) {
use id <- decode.optional_field("id", None, decode.map(decode.string, Some))
use errors <- decode.optional_field(
"errors",
[],
decode.list(error_object_decoder()),
)
decode.success(SendGridError(id:, errors:))
}
fn error_object_decoder() -> decode.Decoder(ErrorObject) {
use message <- decode.field("message", decode.string)
use field <- decode.optional_field("field", None, field_decoder())
use help <- decode.optional_field(
"help",
None,
decode.optional(decode.string),
)
decode.success(ErrorObject(message:, field:, help:))
}
fn field_decoder() -> decode.Decoder(Option(String)) {
use field_value <- decode.map(decode.optional(decode.string))
case field_value {
// According to sendgrid's API, the field "field" can have the literal
// string "null" when not applicable:
// https://www.twilio.com/docs/sendgrid/api-reference/mail-send/mail-send#responses
Some("null") -> None
_ -> field_value
}
}