Aller au contenu principal

Email API

Activer cette API​

Dans la version SaaS, vous pouvez demander d'accéder à cette API en utilisant le système de support.

Si vous ĂŞtes administrateur de cette instance, vous pouvez autoriser les utilisateurs comme ceci :

emailapi_enable

Formulaire​

Une fois que vous êtes activé, vous pouvez utiliser l'API au travers de cette interface :

emailapi_form

Utiliser l'API​

Vous pouvez utiliser cet endpoint dans vos applications pour envoyer des emails :

emailapi_endpoint

Voici ce que cela donne avec curl :

curl -X 'POST' \
'https://api.cwcloud.tech/v1/email' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'X-Auth-Token: XXXXXX' \
-d '{
"from": "cloud@provider.com",
"to": "recipient@provider.com",
"bcc": "bcc@provider.com",
"subject": "Subject",
"content": "Content"
}'

Notes :

  • Si vous ĂŞtes sur la version Tunisienne, remplacer api.cwcloud.tech par api.cwcloud.tn
  • Vous devez remplacer la valeur XXXXXX avec votre propre token gĂ©nĂ©rĂ© via cette procĂ©dure

Pièces jointes

Il est possible de passer une pièce jointe aux emails avec le bloc optionnel attachment:

curl -X 'POST' \
'https://api.cwcloud.tech/v1/email' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'X-Auth-Token: XXXXXX' \
-d '{
"from": "cloud@provider.com",
"to": "recipient@provider.com",
"bcc": "bcc@provider.com",
"subject": "Subject",
"content": "Content",
"attachment": {
"mime_type": "application/pdf",
"file_name": "invoice.pdf",
"b64": "base64content"
}
}'

Notes :

  • Vous devrez encoder en base64 le contenu du fichier Ă  envoyer en pièce jointe avant de le mettre dans le champs b64. Sous Linux ou mac, vous pouvez utiliser la commande base64 -i invoice.pdf et rĂ©cupĂ©rer la sortie.

Plugins pour CMS​

Plugin pour wordpress​

Installation et configuration​

Vous pouvez télécharger ce plugin:

1/ Télécharger la bonne extension (soit la -tech si vous utilisez l'instance console.cwcloud.tech, soit la -tn si vous utilisez l'instance console.cwcloud.tn)

wpaas_email_ext1

2/ Configurer l'extension :

Générer des clefs d'API. Vous pouvez aller voir ce tutoriel

Ensuite copier/coller la clef secrète ici :

wpaas_email_ext2

Debogage​

Se connecter au conteneur wordpress, installer vim et ouvrir le fichier cwcloud-email-plugin.php

docker exec -it wp_app /bin/bash
root@4d9443458fedapt update -y
root@4d9443458fedapt install -y vim
root@4d9443458fedvim wp-content/plugins/cwcloud-email-plugin-tn/cwcloud-email-plugin.php

Ajouter la ligne suivante :

// ...

function cwcloud_email_send($phpmailer) {
$api_endpoint = 'https://api.cwcloud.tn/v1/email';

if (!$to_addr && !empty($tmp_to_addr = $phpmailer->getToAddresses()) && !empty($tmp_to_addr[0]) && $tmp_to_addr[0][0]) {
$to_addr = $tmp_to_addr[0][0];
}

if (!$to_addr) {
$to_addr = $from_addr;
}

$bcc_addr = $phpmailer->AddBCC ? $phpmailer->AddBCC : null;

# Cette ligne
error_log(sprintf("CWCLOUDEMAIL from = %s, to = %s, bcc = %s", $from_addr, $to_addr, $bcc_addr));

// ...
}

// ...

Puis sortir et monitorer les logs du conteneur en re-jouant le scénario d'envoi de mail qui ne fonctionne pas. Cela vous indiquera s'il manque des infos obligatoire voire même si le phpmailer de wordpress a été invoqué.

docker logs wp_app -f 2>&1 | grep CWCLOUDEMAIL

Prestashop​

Vous pouvez utiliser ce plugin:

1/ Télécharger la bonne extension (soit la -tech si vous utilisez l'instance console.cwcloud.tech, soit la -tn si vous utilisez l'instance console.cwcloud.tn)

emailapi_ps_plugins

Ensuite l'uploader, vous pourrez alors la voir et la configurer ici :

emailapi_ps_configure_1

2/ Configurer l'extension :

Générer des clefs d'API. Vous pouvez aller voir ce tutoriel

Ensuite copier/coller la clef secrète ici :

emailapi_ps_configure_2

Vous aurez aussi à choisir une adresse email d'envoi et de copie cachée depuis cette interface de configuration.

Formulaires de contact pour sites statiques​

Vous pouvez également générer un backend pour vos formulaires de contact de sites statiques comme ceci :

contact_forms

Ensuite vous pouvez copier l'id et l'utiliser dans la requĂŞte suivante :

curl -X 'POST' \
'https://api.cwcloud.tech/v1/contactreq' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"id": "<uuid du formulaire de contact>",
"email": "foo@bar.com",
"subject": "Votre sujet",
"message": "Votre message",
"name": "Votre nom",
"firstname": "Votre prénom"
}'

Notes :

  • name et firstname sont optionnels
  • le message doit ĂŞtre rĂ©digĂ© dans l'une des langues suivantes (sinon il sera considĂ©rĂ© comme un spam) : anglais, français, italien, allemand, espagnol et arabe
  • il y a un quota d'un mail pour 30 secondes (configurables) par adresse ip1

Voici un exemple de webcomponent en react compatible avec docusaurus:

import { useMemo, useState } from "react";

const RequiredStar = () => (
<span style={{ color: "var(--ifm-color-danger)" }}>*</span>
);

const CWCloudContactForm = ({
apiBaseUrl,
formId,
endpointPath = "/v1/contactreq",
nameLabel = "Name",
namePlaceHolder = "Your name",
firstNameLabel = "First name",
firstNamePlaceHolder = "Your first name",
emailLabel = "Email",
emailPlaceHolder = "Your email",
subjectLabel = "Subject",
subjectPlaceHolder = "Subject",
messageLabel = "Message",
messagePlaceHolder = "Your message...",
sendButtonLabel = "Send",
sendingLabel = "Sending...",
successLabel = "Message sent",
failureLabel = "Failure during request",
formIdIsRequiredLabel = "formId is required",
apiBaseUrlIsRequiredLabel = "apiBaseUrl is required",
invalidEmailLabel = "Invalid email",
requiredFieldsLabel = "Required fields missing",
className = ""
}) => {
const [form, setForm] = useState({
firstname: "",
name: "",
email: "",
subject: "",
message: "",
});

const [status, setStatus] = useState("idle"); // idle | sending | success | error
const [errorMsg, setErrorMsg] = useState("");

const endpoint = useMemo(() => {
const base = (apiBaseUrl || "").replace(/\/+$/, ""); // trim trailing slash
const path = endpointPath.startsWith("/") ? endpointPath : `/${endpointPath}`;
return `${base}${path}`;
}, [apiBaseUrl, endpointPath]);

const isValidEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

const onChange = (key) => (e) => {
const value = e.target.value;
setForm((prev) => ({ ...prev, [key]: value }));
};

const validateRequired = () => {
const missing = [];
if (!form.email.trim()) missing.push(emailLabel);
if (!form.subject.trim()) missing.push(subjectLabel);
if (!form.message.trim()) missing.push(messageLabel);

if (missing.length > 0) {
return `${requiredFieldsLabel}: ${missing.join(", ")}`;
}
return "";
};

const onSubmit = async (e) => {
e.preventDefault();
setErrorMsg("");

if (!formId) {
setStatus("error");
setErrorMsg(formIdIsRequiredLabel);
return;
}
if (!apiBaseUrl) {
setStatus("error");
setErrorMsg(apiBaseUrlIsRequiredLabel);
return;
}

const requiredError = validateRequired();
if (requiredError) {
setStatus("error");
setErrorMsg(requiredError);
return;
}

if (!isValidEmail(form.email.trim())) {
setStatus("error");
setErrorMsg(invalidEmailLabel);
return;
}

setStatus("sending");

const payload = {
id: formId,
email: form.email.trim(),
subject: form.subject.trim(),
message: form.message.trim(),
name: form.name.trim(),
firstname: form.firstname.trim()
};

try {
const res = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});

let bodyText = "";
try {
bodyText = await res.text();
} catch (_) {}

if (!res.ok) {
const details = bodyText ? ` — ${bodyText.slice(0, 500)}` : "";
throw new Error(`Erreur API (${res.status})${details}`);
}

setStatus("success");
setForm((prev) => ({
...prev,
message: "",
}));
} catch (err) {
setStatus("error");
setErrorMsg(err?.message || "Une erreur est survenue lors de l’envoi.");
}
};

return (
<div
className={className}
style={{
border: "1px solid var(--ifm-toc-border-color)",
borderRadius: 12,
padding: "1rem",
background: "var(--ifm-background-surface-color)",
}}
>
<form onSubmit={onSubmit} style={{ display: "grid", gap: "0.75rem" }}>
<div style={{ display: "grid", gap: "0.5rem", gridTemplateColumns: "1fr 1fr" }}>
<label style={{ display: "grid", gap: "0.25rem" }}>
<span>{firstNameLabel}</span>
<input
type="text"
value={form.firstname}
onChange={onChange("firstname")}
placeholder={firstNamePlaceHolder}
style={inputStyle}
/>
</label>

<label style={{ display: "grid", gap: "0.25rem" }}>
<span>{nameLabel}</span>
<input
type="text"
value={form.name}
onChange={onChange("name")}
placeholder={namePlaceHolder}
style={inputStyle}
/>
</label>
</div>

<label style={{ display: "grid", gap: "0.25rem" }}>
<span>{emailLabel}<RequiredStar /></span>
<input
type="email"
value={form.email}
onChange={onChange("email")}
placeholder={emailPlaceHolder}
required
style={inputStyle}
/>
</label>

<label style={{ display: "grid", gap: "0.25rem" }}>
<span>{subjectLabel}<RequiredStar /></span>
<input
type="text"
value={form.subject}
onChange={onChange("subject")}
placeholder={subjectPlaceHolder}
required
style={inputStyle}
/>
</label>

<label style={{ display: "grid", gap: "0.25rem" }}>
<span>{messageLabel}<RequiredStar /></span>
<textarea
value={form.message}
onChange={onChange("message")}
placeholder={messagePlaceHolder}
required
rows={6}
style={{ ...inputStyle, resize: "vertical" }}
/>
</label>

<div style={{ display: "flex", gap: "0.75rem", alignItems: "center" }}>
<button type="submit" style={buttonStyle(status)}>
{status === "sending" ? sendingLabel : sendButtonLabel}
</button>

{status === "success" && (
<span style={{ color: "var(--ifm-color-success)", fontWeight: 600 }}>
âś… {successLabel}
</span>
)}

{status === "error" && (
<span style={{ color: "var(--ifm-color-danger)", fontWeight: 600 }}>
❌ {errorMsg || failureLabel}
</span>
)}
</div>
</form>
</div>
);
}

const inputStyle = {
width: "100%",
padding: "0.6rem 0.75rem",
borderRadius: 10,
border: "1px solid var(--ifm-toc-border-color)",
background: "var(--ifm-background-color)",
color: "var(--ifm-font-color-base)",
outline: "none",
};

const buttonStyle = (status) => ({
padding: "0.6rem 0.9rem",
borderRadius: 10,
border: "1px solid var(--ifm-color-primary)",
background: "var(--ifm-color-primary)",
color: "white",
cursor: status === "sending" ? "wait" : "pointer",
opacity: status === "sending" ? 0.8 : 1,
});

export default CWCloudContactForm;

Et voici comment l'invoquer dans un fichier .mdx:

import CWCloudContactForm from '@site/src/components/CWCloudContactForm';

# ✉️ Contact

<CWCloudContactForm
apiBaseUrl="https://api.cwcloud.tech"
formId="<uuid du formulaire de contact>"
sendButtonLabel="Envoyer"
sendingLabel="Envoi en cours..."
successLabel="Message envoyé"
failureLabel="Erreur pendant l'envoi"
nameLabel="Nom"
namePlaceHolder="Votre nom"
firstNameLabel="Prénom"
firstNamePlaceHolder="Votre prénom"
emailPlaceHolder="Votre adresse email"
subjectLabel="Sujet"
subjectPlaceHolder="Sujet de votre message"
messagePlaceHolder="Contenu de votre message"
invalidEmailLabel="Email invalide"
requiredFieldsLabel="Champs obligatoires manquants"
/>

Footnotes​

  1. Par défaut ce quota prend directement l'ip du client (avec les headers X-Real-IP ou X-Forwarded-By automatiquement générées par nos reverse proxy si vous utilisez la version SaaS). Mais il est possible, si vous devez invoquer cette API dans un backend côté serveur, d'ajouter l'IP de votre serveur dans le champs des IPs de confiance à la génération du formulaire. Et ensuite votre backend devra faire suivre le header X-Client-IP pour fournir l'ip du vrai expéditeur dans la requête. ↩