use std::collections::HashMap;
use std::env;
use std::sync::Arc;
use data_encoding::BASE64;
use reqwest::{header, Client, Method, Request, StatusCode, Url};
use serde::Serialize;
const ENDPOINT: &str = "https://api.sendgrid.com/v3/";
pub struct SendGrid {
key: String,
client: Arc<Client>,
}
impl SendGrid {
pub fn new<K>(key: K) -> Self
where
K: ToString,
{
let client = Client::builder().build();
match client {
Ok(c) => Self {
key: key.to_string(),
client: Arc::new(c),
},
Err(e) => panic!("creating client failed: {:?}", e),
}
}
pub fn new_from_env() -> Self {
let key = env::var("SENDGRID_API_KEY").unwrap();
SendGrid::new(key)
}
pub fn get_key(&self) -> &str {
&self.key
}
fn request<B>(&self, method: Method, path: String, body: B, query: Option<Vec<(&str, String)>>) -> Request
where
B: Serialize,
{
let base = Url::parse(ENDPOINT).unwrap();
let url = base.join(&path).unwrap();
let bt = format!("Bearer {}", self.key);
let bearer = header::HeaderValue::from_str(&bt).unwrap();
let mut headers = header::HeaderMap::new();
headers.append(header::AUTHORIZATION, bearer);
headers.append(header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"));
let mut rb = self.client.request(method.clone(), url).headers(headers);
match query {
None => (),
Some(val) => {
rb = rb.query(&val);
}
}
if method != Method::GET && method != Method::DELETE {
rb = rb.json(&body);
}
rb.build().unwrap()
}
pub async fn send_raw_mail(&self, message: Message) {
let request = self.request(Method::POST, "mail/send".to_string(), message, None);
let resp = self.client.execute(request).await.unwrap();
match resp.status() {
StatusCode::ACCEPTED => (),
s => panic!("received response status: {:?}", s),
};
}
pub async fn send_mail(&self, subject: String, message: String, to: Vec<String>, cc: Vec<String>, bcc: Vec<String>, from: String) {
let mut p = Personalization::new();
for t in to {
p = p.add_to(Email::new().set_email(&t).set_name(&t));
}
for c in cc {
p = p.add_cc(Email::new().set_email(&c).set_name(&c));
}
for b in bcc {
p = p.add_bcc(Email::new().set_email(&b).set_name(&b));
}
let message = Message::new()
.set_from(Email::new().set_email(&from).set_name(&from))
.set_subject(&subject)
.add_content(Content::new().set_content_type("text/plain").set_value(&message))
.add_personalization(p);
self.send_raw_mail(message).await;
}
}
#[derive(Default, Serialize)]
pub struct Message {
from: Email,
subject: String,
personalizations: Vec<Personalization>,
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<Vec<Content>>,
#[serde(skip_serializing_if = "Option::is_none")]
attachments: Option<Vec<Attachment>>,
#[serde(skip_serializing_if = "Option::is_none")]
template_id: Option<String>,
}
#[derive(Clone, Default, Serialize)]
pub struct Email {
email: String,
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
}
#[derive(Clone, Default, Serialize)]
pub struct Content {
#[serde(rename = "type")]
content_type: String,
value: String,
}
#[derive(Default, Serialize)]
pub struct Personalization {
to: Vec<Email>,
#[serde(skip_serializing_if = "Option::is_none")]
cc: Option<Vec<Email>>,
#[serde(skip_serializing_if = "Option::is_none")]
bcc: Option<Vec<Email>>,
#[serde(skip_serializing_if = "Option::is_none")]
subject: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
headers: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
substitutions: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
custom_args: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
dynamic_template_data: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
send_at: Option<u64>,
}
#[derive(Default, Serialize)]
pub struct Attachment {
content: String,
filename: String,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
disposition: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
content_id: Option<String>,
}
impl Message {
pub fn new() -> Message {
Message::default()
}
pub fn set_from(mut self, from: Email) -> Message {
self.from = from;
self
}
pub fn set_subject(mut self, subject: &str) -> Message {
self.subject = String::from(subject);
self
}
pub fn set_template_id(mut self, template_id: &str) -> Message {
self.template_id = Some(String::from(template_id));
self
}
pub fn add_content(mut self, c: Content) -> Message {
match self.content {
None => {
let content = vec![c];
self.content = Some(content);
}
Some(ref mut content) => content.push(c),
};
self
}
pub fn add_personalization(mut self, p: Personalization) -> Message {
self.personalizations.push(p);
self
}
pub fn add_attachment(mut self, a: Attachment) -> Message {
match self.attachments {
None => {
let attachments = vec![a];
self.attachments = Some(attachments);
}
Some(ref mut attachments) => attachments.push(a),
};
self
}
}
impl Email {
pub fn new() -> Email {
Email::default()
}
pub fn set_email(mut self, email: &str) -> Email {
self.email = String::from(email);
self
}
pub fn set_name(mut self, name: &str) -> Email {
self.name = Some(String::from(name));
self
}
}
impl Content {
pub fn new() -> Content {
Content::default()
}
pub fn set_content_type(mut self, content_type: &str) -> Content {
self.content_type = String::from(content_type);
self
}
pub fn set_value(mut self, value: &str) -> Content {
self.value = String::from(value);
self
}
}
impl Personalization {
pub fn new() -> Personalization {
Personalization::default()
}
pub fn add_to(mut self, to: Email) -> Personalization {
self.to.push(to);
self
}
pub fn add_cc(mut self, cc: Email) -> Personalization {
match self.cc {
None => {
let ccs = vec![cc];
self.cc = Some(ccs);
}
Some(ref mut c) => {
c.push(cc);
}
}
self
}
pub fn add_bcc(mut self, bcc: Email) -> Personalization {
match self.bcc {
None => {
let bccs = vec![bcc];
self.bcc = Some(bccs);
}
Some(ref mut b) => {
b.push(bcc);
}
}
self
}
pub fn add_headers(mut self, headers: HashMap<String, String>) -> Personalization {
match self.headers {
None => {
let mut h = HashMap::new();
for (name, value) in headers {
h.insert(name, value);
}
self.headers = Some(h);
}
Some(ref mut h) => {
h.extend(headers);
}
}
self
}
pub fn add_dynamic_template_data(mut self, dynamic_template_data: HashMap<String, String>) -> Personalization {
match self.dynamic_template_data {
None => {
let mut h = HashMap::new();
for (name, value) in dynamic_template_data {
h.insert(name, value);
}
self.dynamic_template_data = Some(h);
}
Some(ref mut h) => {
h.extend(dynamic_template_data);
}
}
self
}
}
impl Attachment {
pub fn new() -> Attachment {
Attachment::default()
}
pub fn set_content(mut self, c: &[u8]) -> Attachment {
self.content = BASE64.encode(c);
self
}
pub fn set_base64_content(mut self, c: &str) -> Attachment {
self.content = String::from(c);
self
}
pub fn set_filename(mut self, filename: &str) -> Attachment {
self.filename = filename.into();
self
}
pub fn set_mime_type(mut self, mime: &str) -> Attachment {
self.mime_type = Some(String::from(mime));
self
}
}