EasyEmail — A lightweight Android library for sending inquiries and replies via EmailJS, with optional Firebase Realtime Database integration and IMAP inbox fetching.
EasyEmail handles the full email workflow in Android apps: sending contact/inquiry emails, owner replies, fetching replies from an IMAP inbox, and storing everything in Firebase — all with a single fluent API.
EmailJS Only — This library exclusively supports EmailJS as the email delivery provider. SMTP or other providers are not supported.
- Send inquiry emails via EmailJS with one method call
- Send reply emails back to users via EmailJS
- Fetch owner replies from any IMAP inbox (e.g. Gmail)
- Offline queue — emails are queued locally using WorkManager + Room and sent automatically when connectivity is restored
- Firebase Realtime Database integration — auto-save inquiries, replies, and notifications
- Unread reply count management in Firebase
- Customizable templates via
defaultInquiryParams/defaultReplyParams - Extra params support for flexible EmailJS template variables
- LiveData state — observe send progress (
LOADING,QUEUED,SUCCESS,FAILED) anywhere in your app - Callbacks on the main thread — safe to update UI directly in
onSuccess/onError - Builder pattern config — clean, readable setup with sensible defaults
- Works with both Kotlin and Java projects
- JitPack ready for instant integration
In your root settings.gradle (or settings.gradle.kts):
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}dependencies {
implementation 'com.github.Melikash98:EasyEmail:v1.0.7'
}In your AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />The library requires the following in your app's build.gradle. If you're using JitPack, these are pulled in automatically unless excluded:
dependencies {
// HTTP client for EmailJS
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
// IMAP fetching
implementation 'com.sun.mail:android-mail:1.6.7'
implementation 'com.sun.mail:android-activation:1.6.7'
// Offline queue (WorkManager + Room)
implementation 'androidx.work:work-runtime:2.9.0'
implementation 'androidx.room:room-runtime:2.6.1'
annotationProcessor 'androidx.room:room-compiler:2.6.1'
// Firebase (only if firebaseEnabled = true)
implementation 'com.google.firebase:firebase-database:21.0.0'
}EasyEmail only works with EmailJS. You must create a free account and configure your templates before using this library.
Go to https://www.emailjs.com and sign up for a free account.
In your EmailJS dashboard, go to Email Services → Add New Service and connect your Gmail, Outlook, or any SMTP provider. Copy the Service ID (e.g. service_xxxxxxx).
Go to Email Templates → Create New Template.
This template is used when a user sends an inquiry. The following variables are automatically filled by the library — add any of them to your template:
| Variable | Description |
|---|---|
{{name}} |
User's display name |
{{user_name}} |
User's display name (alias) |
{{user_email}} |
User's email address |
{{user_phone}} |
User's phone number |
{{message}} |
Inquiry message body |
{{term}} |
Term/condition field |
{{time}} |
Formatted send time (dd/MM/yyyy HH:mm) |
{{owner_email}} |
Receiver's (owner's) email |
{{owner_name}} |
Receiver's (owner's) name |
{{owner_photo_url}} |
Receiver's photo URL |
{{app_email}} |
Your app's email (from config) |
{{item_id}} |
Item/listing ID |
{{categories_id}} |
Category ID |
{{inquiry_id}} |
Auto-generated unique inquiry UUID |
Copy the Template ID (e.g. template_xxxxxxx).
Create a second template for owner replies. Variables available:
| Variable | Description |
|---|---|
{{owner_email}} |
Owner's email address |
{{owner_name}} |
Owner's display name |
{{reply_message}} |
The reply message body |
{{user_name}} |
User being replied to |
{{name}} |
User being replied to (alias) |
{{user_photo_url}} |
User's photo URL |
{{item_id}} |
Item/listing ID |
{{inquiry_id}} |
The original inquiry UUID |
{{time}} |
Formatted reply time |
{{app_email}} |
Your app's email (from config) |
Important: For IMAP reply fetching to work, your reply template must include
{{inquiry_id}}somewhere in the email Subject line, so the library can match incoming emails to the correct inquiry.
Copy this template's Template ID as well.
In your EmailJS dashboard, go to Account → General → copy your Public Key.
EmailJsConfig config = new EmailJsConfig.Builder()
.setServiceId("service_xxxxxxx")
.setPublicKey("your_public_key")
.setInquiryTemplateId("template_xxxxxxx")
.setReplyTemplateId("template_yyyyyyy")
// optional IMAP settings (only needed for fetchOwnerReplies)
.setAppEmail("your.app@gmail.com")
.setAppPassword("your_app_password")
.build();All configuration is done via the Builder. Most fields have sensible defaults.
EmailJsConfig config = new EmailJsConfig.Builder()
// ── Required for sending ──────────────────────────────────────
.setServiceId("service_xxxxxxx") // EmailJS Service ID
.setPublicKey("your_public_key") // EmailJS Public Key
.setInquiryTemplateId("template_xxxxxxx") // Template ID for inquiries
// ── Required for replies ──────────────────────────────────────
.setReplyTemplateId("template_yyyyyyy") // Template ID for replies
// ── Required for IMAP fetching ────────────────────────────────
.setAppEmail("your.app@gmail.com") // IMAP login email
.setAppPassword("xxxx xxxx xxxx xxxx") // App password (NOT your real password)
.setImapHost("imap.gmail.com") // Default: "imap.gmail.com"
.setImapPort(993) // Default: 993
// ── Firebase (optional) ───────────────────────────────────────
.setFirebaseEnabled(true) // Default: true
.setFirebaseInquiryRoot("Emails") // Root node for inquiries. Default: "Emails"
.setFirebaseUserRoot("Users") // Root node for users. Default: "Users"
// ── Notifications (optional) ──────────────────────────────────
.setNotificationsEnabled(true) // Default: true
.setNotificationTitle("New Reply") // Default: "New Reply"
.setNotificationBody("You have received a new reply.") // Default value
// ── Custom default template params (optional) ─────────────────
.setDefaultInquiryParams(myInquiryMap) // Extra fixed params for inquiry template
.setDefaultReplyParams(myReplyMap) // Extra fixed params for reply template
// ── Advanced (optional) ───────────────────────────────────────
.setEmailJsApiUrl("https://api.emailjs.com/api/v1.0/email/send") // Default value
.build();Gmail IMAP note: Google no longer allows sign-in with your normal password from third-party apps. You must generate an App Password from your Google Account → Security → 2-Step Verification → App Passwords.
Security note: Never hardcode your
appPasswordorpublicKeyin source files. UseBuildConfigfields or an encrypted secrets store (e.g. Android Keystore, encrypted SharedPreferences) and exclude these values from version control.
It is strongly recommended to create a single instance of EasyEmail (e.g. via a singleton or dependency injection) rather than creating a new instance per call, to avoid unnecessary resource allocation.
EasyEmail easyEmail = new EasyEmail(context, config);Use this when a user wants to contact an owner/seller/host.
easyEmail.sendInquiry(
"owner@example.com", // ownerEmail — recipient
"John Owner", // ownerName
"https://...", // ownerPhotoUrl (can be null or empty)
"Alice User", // userName — sender
"alice@example.com", // userEmail
"+1234567890", // userPhone
"Flexible", // term
"", // time (leave empty to auto-fill current time)
"Hello, is this still available?", // message
"item_001", // itemId
"cat_furniture", // categoriesId
"uid_abc123", // userUid (Firebase UID, nullable)
new EmailCallback() {
@Override
public void onSuccess() {
// called on main thread — safe to update UI
Toast.makeText(context, "Inquiry sent!", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(String error) {
// called on main thread
// If the device is offline, error will begin with "QUEUED:"
// meaning the email has been saved locally and will be sent automatically
// when connectivity is restored.
Log.e("EasyEmail", "Error: " + error);
}
}
);Offline behaviour: When the device has no internet connection, the inquiry is saved to a local Room database and
onErroris called with a message prefixed by"QUEUED:". WorkManager will automatically retry sending the email (with exponential backoff, up to 3 attempts) once connectivity is restored. ObserveEmailStateLiveDatafor real-time queue status updates.
Map<String, String> extras = new HashMap<>();
extras.put("property_type", "Apartment");
extras.put("floor_number", "3");
easyEmail.sendInquiry(
ownerEmail, ownerName, ownerPhotoUrl,
userName, userEmail, userPhone,
term, time, message, itemId, categoriesId, userUid,
extras, // <-- extra params merged into the EmailJS template
callback
);Use this when an owner wants to reply to an inquiry. The reply is sent via EmailJS and optionally saved to Firebase.
easyEmail.sendReply(
"owner@example.com", // ownerEmail — sender of the reply
"John Owner", // ownerName
"Yes, it is available. Please contact me.", // replyMessage
"inquiry-uuid-here", // inquiryId — must match the original inquiry
"item_001", // itemId
"Alice User", // userName — original inquiry sender
"alice@example.com", // userEmail
"https://...", // userPhotoUrl
"uid_abc123", // userUid (Firebase UID, nullable)
new EmailCallback() {
@Override
public void onSuccess() {
// called on main thread — safe to update UI
Toast.makeText(context, "Reply sent!", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(String error) {
Log.e("EasyEmail", "Reply error: " + error);
}
}
);Use this to pull replies that an owner sent from their email client (outside the app) into Firebase, so the user can see them inside the app.
easyEmail.fetchOwnerReplies(
"uid_abc123", // userUid
"inquiry-uuid-here", // inquiryId (must appear in the email subject)
"https://...", // ownerPhotoUrl
new EmailCallback() {
@Override
public void onSuccess() {
// called on main thread — safe to update UI directly
Toast.makeText(context, "Replies fetched!", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(String error) {
Log.e("EasyEmail", "IMAP error: " + error);
}
}
);Deduplication warning:
fetchOwnerRepliesscans the last 50 inbox messages every time it is called. Calling it multiple times for the same inquiry will save duplicate reply entries in Firebase. Implement your own call guard (e.g. check thestatusfield or existing replies in Firebase before calling) to prevent duplicates.
You can observe the send state anywhere in your app. This is the recommended way to show global loading indicators or queue badges:
EmailStateLiveData.getInstance().getLiveData().observe(this, state -> {
switch (state.getStatus()) {
case IDLE:
// Initial state — nothing to show
break;
case LOADING:
// Show progress indicator
progressBar.setVisibility(View.VISIBLE);
break;
case QUEUED:
// Email is queued offline — state.getMessage() contains the queue count
Toast.makeText(this, state.getMessage(), Toast.LENGTH_SHORT).show();
break;
case SUCCESS:
// Email sent successfully
progressBar.setVisibility(View.GONE);
break;
case FAILED:
// Send failed after all retries
progressBar.setVisibility(View.GONE);
Log.e("EasyEmail", state.getMessage());
break;
}
});When firebaseEnabled = true, the library writes to Firebase Realtime Database using the following structure:
{firebaseUserRoot}/ ← default: "Users"
└── {userUid}/
├── inquiries/
│ └── {inquiryId}/
│ ├── userUid
│ ├── inquiryId
│ ├── itemId
│ ├── categoriesId
│ ├── ownerEmail
│ ├── ownerName
│ ├── ownerPhoto
│ ├── userName
│ ├── sentAt ← ServerValue.TIMESTAMP
│ ├── status ← "sent"
│ └── replies/
│ └── {pushId}/
│ ├── replyId
│ ├── replyText
│ ├── isOwnerReply ← true / false
│ ├── replyDate
│ ├── receivedAt
│ └── isRead
├── MessageNotification/
│ └── {pushId}/
│ ├── message
│ ├── type ← "NEW_REPLY"
│ ├── inquiryId
│ ├── replyId
│ ├── isRead
│ └── timestamp
└── unreadReplyCount ← integer
{firebaseInquiryRoot}/ ← default: "Emails"
└── {inquiryId}/
└── (same fields as user inquiry node)
new EmailJsConfig.Builder()
// ...
.setFirebaseEnabled(false)
.build();new EmailJsConfig.Builder()
// ...
.setFirebaseInquiryRoot("Inquiries") // default: "Emails"
.setFirebaseUserRoot("AppUsers") // default: "Users"
.build();- EmailJS only — no SMTP, SendGrid, or other provider support.
- IMAP deduplication —
fetchOwnerRepliesscans the last 50 inbox messages every time it is called. Calling it multiple times for the same inquiry will save duplicate reply entries in Firebase. Implement your own call guard before fetching. - OkHttpClient instances — a new
OkHttpClientis created per send call. For high-frequency use, consider wrappingEasyEmailas a singleton in your app. - App password security — your IMAP app password is held in memory inside
EmailJsConfig. Do not log the config object and do not store the password in plain-text source files. UseBuildConfigfields or an encrypted store instead. - Database migration — the internal Room database uses destructive migration. Upgrading the library version may clear any locally queued (unsent) emails. Ensure pending emails are flushed before updating the library.
- Offline callback behaviour — when the device is offline,
onErroris called with a"QUEUED:"prefix. This does not mean the send failed permanently; the email will be retried automatically by WorkManager.
| Method | Description |
|---|---|
sendInquiry(...) |
Send an inquiry email via EmailJS |
sendInquiry(..., extraParams, callback) |
Same, with extra template variables |
sendReply(...) |
Send a reply email via EmailJS |
sendReply(..., extraParams, callback) |
Same, with extra template variables |
fetchOwnerReplies(userUid, inquiryId, ownerPhotoUrl, callback) |
Fetch IMAP replies and save to Firebase |
public interface EmailCallback {
void onSuccess();
void onError(String error);
}Both
onSuccessandonErrorare delivered on the main thread for all methods, making it safe to update UI directly inside the callbacks.
| Status | Meaning |
|---|---|
IDLE |
Initial state, nothing in progress |
LOADING |
Send request is in progress |
QUEUED |
Device is offline; email saved to local queue |
SUCCESS |
Email was sent successfully |
FAILED |
All retry attempts exhausted |
| Method | Default | Required |
|---|---|---|
setServiceId(String) |
— | ✅ for send |
setPublicKey(String) |
— | ✅ for send |
setInquiryTemplateId(String) |
— | ✅ for send |
setReplyTemplateId(String) |
— | ✅ for reply |
setAppEmail(String) |
— | ✅ for fetch |
setAppPassword(String) |
— | ✅ for fetch |
setImapHost(String) |
imap.gmail.com |
|
setImapPort(int) |
993 |
|
setEmailJsApiUrl(String) |
EmailJS v1.0 URL | |
setFirebaseEnabled(boolean) |
true |
|
setFirebaseInquiryRoot(String) |
"Emails" |
|
setFirebaseUserRoot(String) |
"Users" |
|
setNotificationsEnabled(boolean) |
true |
|
setNotificationTitle(String) |
"New Reply" |
|
setNotificationBody(String) |
"You have received a new reply." |
|
setDefaultInquiryParams(Map) |
empty | |
setDefaultReplyParams(Map) |
empty |
This project is licensed under the MIT License.
android email library, emailjs android, android inquiry email, android imap library, firebase email android, android contact form library, send email android java
Melikash98
If you find EasyEmail useful, please consider giving it a ⭐ star on GitHub — it helps the project grow and motivates further development.
For feature requests, bug reports, or suggestions, please open an issue. Your feedback is highly appreciated.

